如下图代码所示,生产者Producer类和消费者Consumer类共同操作同一个资源resource,二者产生了如下所示的线程安全问题
分析:
上面的例子中,我们开启了4个线程,其中线程t1,t2是生产者。t3,t4是消费者
(1)线程t1正常生产后进入阻塞状态
假设开始的时候:t1进入resource类中,flag初始值为false,t1直接给name赋值,然后执行notify()(线程池此时为空)。此时t1还处于运行状态,因此run方法还会从头再执行,而此时flag变为true,t1进入阻塞状态wait()
(2)线程t2进入阻塞状态
当线程t1进入阻塞状态后,此时t2,t3,t4线程都处于就绪状态。这里我们假设t2进入运行状态,此时flag为真,t2也进入阻塞状态wait()
(3)线程t3正常消费后进入阻塞状态,并唤醒线程t1
t3正常消费,唤醒线程池中的t1(此时t1处于就绪状态)t3还处于运行状态,flag变为false,t3进入阻塞状态wait()
(4)线程t1与线程t4争抢执行权,t4进入阻塞状态
由于flag为false,线程t4进入阻塞状态。线程t1开始执行
(5)t1正常生产,唤醒最早进入线程池中的t2,并进入阻塞状态
(6)t2异常生产
此时线程中仅剩t2处于就绪状态,t2不会重新判断flag,而是继续执行wait()之后的代码
解决方案1:追加自旋锁
判断flag真假可以使用 while (condition) {},不能使用 if(condition) {}。其中 while(condition)循环,它又被叫做“自旋锁”。为防止该线程没有收到notify()调用也从wait()中返回(也称作虚假唤醒),这个线程会重新去检查condition条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false(这种做法要慎重,目前的JVM实现自旋会消耗太大的CPU)
解决方案2: JDK1.5版本新特性
ReentrantLock(重入锁):效果synchronized一样,都可以同步执行,lock方法获得锁,unlock方法释放锁
Condition类的awiat方法和Object类的wait方法等效
Condition类的signal方法和Object类的notify方法等效
Condition类的signalAll方法和Object类的notifyAll方法等效
网友评论