目录
1.线程通信
2.指令重排
1.线程通信
前段时间面试笔试题,手写两个线程之间的通信。
问题回顾:
一个生产者,一个消费者,一个桌子媒介。两个线程分别是消费者和生产者。流程是生产者生产一个件商品,通知消费者消费一个商品。
普通的线程使用的等待和唤醒是使用Object类下的wait()、notify()、notifyAll()、interrupt()四个方法。
wait()阻塞线程
notify()随机唤醒线程
notifyAll()唤醒全部线程
interrupt()打断线程。
如果要唤醒指定的线程需要使用Condition newCondition()的这个方法
实现demo
Desk.class
public class Desk {//上货标识public static boolean flag = false;//订单数量public static Integer orderCount = 0;//商品数量public static Integer productCount = 0;//最终剩余数量public static Integer finalCount =0;//定义可重入锁public static final Lock lock = new ReentrantLock();//给consumer使用的conditionpublic static Condition condition4Consumer = lock.newCondition();//给producer用的conditionpublic static Condition condition4Producer = lock.newCondition();
}
Producer.class
public class Producer extends Thread {@Overridepublic void run() {
// int i=10;while (true) {Desk.lock.lock();try {if (!Desk.flag) {Thread.sleep(new Random().nextInt(10));Desk.flag = true;System.out.println(Thread.currentThread().getName() + ":用户生产食物" + (++Desk.productCount));Desk.finalCount++;System.out.println("商家唤醒consumer--");Desk.condition4Consumer.signalAll();} else {try {Desk.condition4Producer.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {Desk.lock.unlock();}}}
}
Consumer.class
public class Consumer extends Thread {@Overridepublic void run() {
// int i=10;while (true) {//不断地拿食物Desk.lock.lock();try {if (Desk.flag) {//如果有食物 拿走并通知厨师Thread.sleep(new Random().nextInt(10));System.out.println(Thread.currentThread().getName() + ":顾客拿走食物" + (++Desk.orderCount));Desk.finalCount--;Desk.flag = false;System.out.println("consumer 转杯唤醒producer--");Desk.condition4Producer.signalAll();} else {Desk.condition4Consumer.await();}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {Desk.lock.unlock();}}}
}
测试类
public class TestCondition {public static void main(String[] args) throws InterruptedException {Producer producer = new Producer();Consumer consumer = new Consumer();producer.start();consumer.start();int numRandom = new Random().nextInt(200)*10;Thread.sleep(numRandom);producer.interrupt();consumer.interrupt();producer.join();consumer.join();System.out.println("等待时间:"+numRandom+"生产数量:"+Desk.productCount+"消费数量:"+Desk.orderCount+"最终数量:"+Desk.finalCount);producer.wait();}
}
最终结果
2.指令重排
指令重排的复现代码
在代码执行的过程中会进行必要的优化,印美每一次操作都需要消耗内存的操作
每一条指令的操作都是1.加载2.设置3.存储。那么对一个值的操作中会存在反复读取的操作,反复存储,那么需要把反复读取和存储的最终保障一次就可以。
在多线程中,现在要先修改ab的值,在把x=b.y=a,正常线程中cpu争抢线程顺序不一样,那么结果是1,1)(1,0)(0,1)。单如果是(0,0),此时就表示已经出现指令重排的现象。
public class TestReorder {static int x = 0;static int y = 0;static int a = 0;static int b = 0;static int count=0;public static void main(String[] args) throws InterruptedException {while (true) {CountDownLatch countDownLatch = new CountDownLatch(1);Thread t1 = new Thread(() -> {try {countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}//操作1a = 1;//操作2x = b;});Thread t2 = new Thread(() -> {try {countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}//操作1b = 1;//操作2y = a;});t1.start();t2.start();countDownLatch.countDown();t1.join();t2.join();//三种情况(1,1)(1,0)(0,1)//如果两个两个线程都发生重排序结果是(0,0)System.out.println("x" + x + "y" + y+"count"+count);if (x == 0 && y == 0) {break;}count++;x=0;y=0;a=0;b=0;}}
}
那么这个时候就需要使用使用volatile这个关键字。它是通过前后两个操作中添加屏障防止指令重排。
volatile通过使用内存屏障防止指令重排
写volatile操作前加storestore屏障(写写屏障)
写volatile操作后加storeload屏障(写读屏障)
读volatile操作后加loadload屏障(读读屏障)
读volatile操作后加loadstore屏障(读写屏障)
ps:
volatile中主要又有两个作用,一个是可见性和防止指令重排。
可见性表示在多线程中,有一个变量修改,其他线程能立即察觉出它的变化中,而不是从缓存行中读取数据。
指令重排表示的是。正常是初始化是三步骤,第一开辟空间,第二初始化数据结构,第三指针指向堆空间。这这个的过车中其实就是,int a=1;的过程。但是从上到下在代码执行优化的时候会导致指令重排。