Java多线程编程与线程机制笔记

多线程编程与线程机制笔记

线程分类

  • 用户线程:由用户自定义的线程,通常执行应用逻辑。
  • 守护线程:运行在后台的特殊线程,例如垃圾回收线程。

多线程编程步骤

  1. 创建资源类
    • 资源类应包含操作方法,设计上保持高内聚低耦合。
  2. 资源类中的操作方法
    1. 判断:检查条件,决定是否需要等待。
    2. 干活:执行具体任务。
    3. 通知:完成后通知其他线程。
  3. 创建线程:通过线程调用资源类的操作方法。
  4. 防止虚假唤醒问题:使用 while 循环代替 if 条件检查。

Lock 与 synchronized 对比

Lock 特点

  • 是一个接口,而 synchronized 是关键字。
  • 提供更强大的功能,如可重入锁、条件变量等。
  • 手动加锁和解锁,灵活性高。

两者区别

  1. 异常处理
    • synchronized 发生异常时会自动释放锁,不会导致死锁。
    • Lock 不会自动释放锁,需手动释放,否则可能引发死锁。
  2. 性能与适用场景
    • Lock 更适合高并发场景,提供非阻塞的尝试加锁机制。
    • synchronized 更简洁,适合简单同步。

虚假唤醒问题

虚假唤醒是由于 wait 方法的特点引起的,线程可能在没有满足条件的情况下被唤醒。

修正方案:用 while 循环替代 if 条件。

public synchronized void decreament() throws InterruptedException {
    // 判断
    while (number != 1) {
        this.wait();
    }
    number--;
    System.out.println(Thread.currentThread() + " :: " + number);
    this.notifyAll(); // 通知其他线程
}

线程的定制化通信

案例:线程交替打印

需求:A 线程打印 5 次,B 线程打印 10 次,C 线程打印 15 次,循环 10 轮。

实现方案

  • 每个线程有一个对应的标志位(flag):1、2、3。
  • 每次操作后修改标志位,并通知下一个线程继续执行。

Java 集合的线程安全问题

线程不安全的集合

  1. ArrayList
    • 替代方案:
      • Vector
      • 工具类方法:Collections.synchronizedList
      • CopyOnWriteArrayList
        • 使用写时复制技术,支持并发读,独立写入。
  2. HashSet
    • 替代方案:CopyOnWriteArraySet
  3. HashMap
    • 替代方案:ConcurrentHashMap

synchronized 用法

  1. 同步方法:锁住实例对象。
  2. 静态同步方法:锁住类的 Class 对象。
  3. 同步代码块synchronized 锁定括号内指定的对象。

锁的分类

公平锁与非公平锁

  • 公平锁:线程按照请求锁的顺序获得锁(性能较低)。
  • 非公平锁:线程可以插队获得锁(性能较高,但可能导致线程饿死)。

可重入锁(递归锁)

  • synchronizedLock 都是可重入锁。
  • 特点
    • 同一个线程获取锁后,可以多次进入同一个锁保护的代码块。
    • synchronized 是隐式实现,Lock 需要显式加锁和解锁。

死锁

定义

死锁是指两个或多个线程因为资源竞争而相互等待,导致程序无法继续执行的现象。

死锁产生的原因

  1. 系统资源不足。
  2. 线程获取锁的顺序不合适。
  3. 资源分配方式不当。

如何检测死锁

  1. 使用 jps 命令
    • 列出所有 JVM 进程。
  2. 使用 jstack 命令
    • 配合进程号查看线程堆栈,定位死锁问题。

代码示例

死锁代码示例
public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock1...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lock2) {
                    System.out.println("Thread 1: Acquired lock2...");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock2...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lock1) {
                    System.out.println("Thread 2: Acquired lock1...");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

总结

  • Locksynchronized 的选择
    • 简单场景用 synchronized,复杂并发用 Lock
  • 死锁检测与避免
    • 注意锁获取顺序。
    • 使用工具如 jstack 定位问题。
  • 虚假唤醒
    • 使用 while 包裹判断条件。
  • 集合线程安全
    • 使用线程安全的集合,如 ConcurrentHashMapCopyOnWriteArrayList
上一篇:【es6进阶】如何使用Proxy实现自己的观察者模式-业务分析