上一次说道,线程操作记住:线程 操作 资源类,高内聚低耦合,这是上
下(wait和notify)口诀: 判断/干活/通知 ,虚假唤醒
一、案例 1(两个线程)
现在两个线程,可以操作初始值为零的一个变量,实现一个线程对该变量加1,一个线程对该变量减1,交替,来10轮。
资源类:可以结合判断 干活 通知,注意是if判断只判断一次,后面会讲到
class ShareData
{
private int number = 0;
public synchronized void increment()throws Exception
{
//1 判断
if(number != 0)
{
this.wait();
}
//2 干活
++number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知
this.notifyAll();
}
public synchronized void decrement()throws Exception
{
//1 判断
if(number == 0)
{
this.wait();
}
//2 干活
--number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知
this.notifyAll();
}
}
测试类:
public class ProdConsumerDemo
{
public static void main(String[] args)
{
ShareData sd = new ShareData();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.increment();
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.decrement();
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
}
}
二、案例 2(多个线程)解决虚假唤醒
如果说将上面的改成两个生产者,两个消费者,而其他的不变则出现线程不安全问题了, 为什么呢?
分析: 例如,当一个生产者生产了一个当抢占到cpu会处于wait等待阶段,此时万一另一个生产者抢占了cpu资源,发现已经生成了一个,也处于等待,此时另外两个消费者任意一个可以抢占cpu,进行消费,然后消费完后,资源为0唤醒生产者,此时,一个生产者会唤醒,进行下一步的生成生产结束,而另一个生产者也将有可能在次进行生产,重要的是,用if已经判断过一次了,不用在判断,所以会在生产出两个,与生产一个消费一个不符,所以 解决措施就是使其多次判断,即使用while代替if。
而JDK官方文档也指出,最好使用while代替if
- As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
所以本段代码是:
资源段:注意while
class ShareData
{
private int number = 0;
public synchronized void increment()throws Exception
{
//1 判断
while(number != 0)
{
this.wait();
}
//2 干活
++number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知
this.notifyAll();
}
public synchronized void decrement()throws Exception
{
//1 判断
while(number == 0)
{
this.wait();
}
//2 干活
--number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知
this.notifyAll();
}
}
测试:
public class ProdConsumerDemo
{
public static void main(String[] args)
{
ShareData sd = new ShareData();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.increment();
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.decrement();
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.increment();
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
try {
sd.decrement();
Thread.sleep(400);
} catch (Exception e) {
e.printStackTrace();
}
}
},"D").start();
}
}