学习问题记录(6) --- 线程
1.volatile关键字有什么作用?
1.volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
先看一段代码,假如线程1先执行,线程2后执行:
1
2
3
4
5
6
7
8
|
//线程1 boolean stop = false ; while (!stop){ doSomething(); } //线程2 stop = true ; |
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
那么线程1读取到的就是最新的正确的值。
2.编写Java程序模拟烧水泡茶最优工序
1 package com.uinnova.ftpsynweb.test; 2 3 import java.util.concurrent.*; 4 5 /** 6 * 分工、同步、互斥 烧水泡茶 Thread.join() CountDownLatch FutureTask 7 * 9 */ 10 public class FutureTaskMain { 11 12 public static void main(String[] args) throws ExecutionException, InterruptedException { 13 // FutureTask futureTask = new FutureTask(() -> 1 + 2); 14 // ExecutorService service = Executors.newCachedThreadPool(); 15 // service.submit(futureTask); 16 // Object o = futureTask.get(); 17 // System.out.println(o); 18 new FutureTaskMain().run(); 19 } 20 21 //创建任务T2的FutureTask 22 FutureTask<String> ft2 = new FutureTask<>(new T2Task()); 23 24 //创建任务T1的FutureTask 25 FutureTask<String> ft1 = new FutureTask<>(new T1Task(ft2)); 26 27 //线程T1执行任务 28 29 30 public void run() throws ExecutionException, InterruptedException { 31 Thread T1 = new Thread(ft1); 32 T1.start(); 33 34 Thread T2 = new Thread(ft2); 35 T2.start(); 36 37 System.out.println(ft1.get()); 38 39 } 40 41 42 //洗水壶、烧水、泡茶 43 class T1Task implements Callable<String> { 44 45 FutureTask<String> ft2; 46 47 T1Task(FutureTask<String> ft2) { 48 this.ft2 = ft2; 49 } 50 51 @Override 52 public String call() throws Exception { 53 System.out.println("T1:洗水壶..."); 54 TimeUnit.SECONDS.sleep(1); 55 56 System.out.println("T1:烧开水..."); 57 TimeUnit.SECONDS.sleep(15); 58 59 //获取T2线程的茶叶 60 String tf = ft2.get(); 61 System.out.println("T1:拿到茶叶:" + tf); 62 63 System.out.println("T1:泡茶..."); 64 return "上茶: " + tf; 65 } 66 } 67 68 //洗茶壶、洗茶杯、拿茶叶 69 class T2Task implements Callable<String> { 70 71 72 @Override 73 public String call() throws Exception { 74 System.out.println("T2 :洗茶壶..."); 75 TimeUnit.SECONDS.sleep(1); 76 77 System.out.println("T2:洗茶杯..."); 78 TimeUnit.SECONDS.sleep(2); 79 80 System.out.println("T2:拿茶叶... "); 81 TimeUnit.SECONDS.sleep(1); 82 return "龙井"; 83 } 84 } 85 }