前言
首先,我們使用多線程的目的在於提高程序的效率,但是如果使用不當,不僅不能提高效率,反而會使程序的性能更低,因為多線程涉及到線程之間的調度、CPU上下文的切換以及包括線程的創建、銷毀和同步等等,開銷比單線程大,因此需謹慎使用多線程。
在jdk1.5以后,提供了一個強大的java.util.concurrent包,這個包中提供了大量的應用於線程的工具類。
下面開始介紹volatile關鍵字和內存可見性,雖然volatile是在jdk1.5之前就有的,但還是想放在這里講一下。
舉例說明
首先,我們先看一段小程序。
1 package com.ccfdod.juc; 2 3 public class TestVolatile { 4 public static void main(String[] args) { 5 ThreadDemo td = new ThreadDemo(); 6 new Thread(td).start(); 7 8 while(true) { 9 if (td.isFlag()) { 10 System.out.println("--------------"); 11 break; 12 } 13 } 14 } 15 } 16 17 class ThreadDemo implements Runnable { 18 private boolean flag = false; 19 20 @Override 21 public void run() { 22 try { 23 Thread.sleep(200); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 flag = true; 28 System.out.println("flag = " + isFlag()); 29 } 30 31 public boolean isFlag() { 32 return flag; 33 } 34 35 public void setFlag(boolean flag) { 36 this.flag = flag; 37 } 38 }
程序運行結果:
flag = true
並且程序不會停止。
按理來說,應該會在td線程修改flag值后,主線程會打印出“--------------”,但是為什么沒有出現預期效果呢?下面來分析這段程序,涉及到內存可見性問題。
內存可見性問題
當程序運行時,JVM會為每一個執行任務的線程分配一個獨立的緩存空間,用於提高效率。
不難理解,程序開始執行時,由於線程td修改flag操作之前,sleep了200ms,td線程和main線程獲取到的flag都為false,但為什么td線程將flag改為true后,main線程沒有打印出“--------------”呢?原因在於:while(true)是執行效率很高,使得main線程沒有時間再次從主存中獲取flag的值,因此程序在td線程將flag修改為true后,沒有停止運行的原因。其實在while(true)后面稍微延遲一點(比如說,打印一句話),都會使main線程將主存中的flag=true讀取。
產生這種情況的原因就在於,兩個線程在操作共享數據時,對共享數據的操作是彼此不可見的。
那么為了不讓這種問題出現,怎么解決呢?
一、使用synchronized同步鎖
while(true) { synchronized (td) { if (td.isFlag()) { System.out.println("--------------"); break; } } }
使用synchronized同步鎖能保證數據的及時更新。但是效率太低。
二、使用volatile關鍵字
當多個線程進行操作共享數據時,可以保證內存中的數據可見。底層原理:內存柵欄。使用volatile關鍵字修飾時,可理解為對數據的操作都在主存中進行。
private volatile boolean flag = false;
相較於synchronized是一種較為輕量級的同步策略。
注意:
- volatile不具備“互斥性”
- volatile不能保證變量的“原子性”
關於“原子性”的問題將在下一篇博客中討論。