先說說併發和並行的區別
1.併發是多個線程同時訪問
2.並行是多個方面一起正在做
volatile是Java虛擬機提供的輕量級同步機制
三大特性:保證可見性,不保證原子性,禁止指令重排
JMM(java內存模型)
高併發系統還是單機版系統(高併發伴隨很多問題,不得不研究底層JMM)
JMM第一大特性之內存可見性
變量值一旦被某個線程優先修改改變 其他線程立刻可見
對象在堆裏面,整個虛擬機在內存裏面
JMM三大特性
可見性舉例
package com.wsx;
import java.util.concurrent.TimeUnit;
class add{
volatile int i = 1;
public void addTo20(){
this.i = 20;
}
}
public class volatileDemo {
public static void main(String[] args) {
final add add = new add();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+'\t'+add.i);//之前的值
try {
TimeUnit.SECONDS.sleep(2);//睡兩秒
} catch (InterruptedException e) {
e.printStackTrace();
}
add.addTo20();//將值設置爲20
},"AAA").start();
while (add.i==1){
//若不同步值則一直在此循環
}
//主線程值
System.out.println(Thread.currentThread().getName()+'\t'+add.i);
}
}
volatile不保證原子性的案例演示
/**
* volatile不保證原子性
*/
class Number{
volatile int number = 0;
public void numberPlus(){
number++;
}
}
public class VolatileNoAtomic {
public static void main(String[] args) {
Number number = new Number();
for (int i = 1; i <= 20; i++) {//二十個線程
new Thread(()->{
for (int j = 1; j <= 1000; j++) {//一個線程打印2000次
number.numberPlus();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){//activeCount是因爲main一個線程gc一個線程然後如果這二十個線程組打印完則會小於2,故停止對main線程的休眠
Thread.yield();//Thread是main線程
}
System.out.println(Thread.currentThread().getName()+"number加後的值:"+number.number);
}
}
volatile不保證原子性理論性解釋
number++底層是三個步驟
三個步驟(先拿number,然後++,然後再 放入主內存)
剛好寫完又特別快,兩個掛起的線程(此時已經累加過,但是沒有被可見性同步,因爲掛起)又被喚醒,沒有拿到最新值,再一次去寫(寫時被掛起,也沒有接到通知(可見性)),然後就出現了值覆蓋現象
不保證原子性就可能出現寫丟失的情況,線程太快了,後面線程會把前面的寫的值覆蓋
字節碼指令集:https://www.cnblogs.com/taomylife/p/8340605.html
我拿的時候,我們兩個拿的都是0,我正準備寫回去是1的時候,突然有人捷足先登了,我這兒被掛起,已經有人從0先寫去1,而這個時候,我又馬上寫回去,那麼相當於跟丟了一次
volatile關鍵字不保證原子性問題解決
原子操作,天生單位最小不可分割
package com.wsx;
import java.util.concurrent.atomic.AtomicInteger;
/**
* volatile不保證原子性
* 解決方案:
* 1.加sync關鍵字
* 2.使用atomicInteger
*/
class MyData{
volatile int i = 0;
public void numberPlusPlus(){
i++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void numberPlus(){
atomicInteger.getAndIncrement();//相當於i++,此構造函數默認無參構造器是從0開始累加,可以傳入int初始值
}
}
public class VolatileSolveNoAtomic {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {//二十個線程
new Thread(()->{
for (int j = 1; j <= 1000; j++) {//一個線程打印2000次
myData.numberPlusPlus();
myData.numberPlus();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){//activeCount是因爲main一個線程gc一個線程然後如果這二十個線程組打印完則會小於2,故停止對main線程的休眠
Thread.yield();//Thread是main線程
}
System.out.println(Thread.currentThread().getName()+"number加後的值:"+myData.i);
System.out.println(Thread.currentThread().getName()+"number加後的值:"+myData.atomicInteger);
}
}
線程安全性獲得保證:可見性,原子性,有序性
volatile指令重排案例1
高考時,做題,先找會的做,做題順序不一定和出題順序一致,這就是指令重排
編譯器,系統重排,內存重排
不可以,理由是數據依賴性禁止指令重排案例2有可能是5也有可能是6
因爲a=1和flag=true沒有依賴性,可能先flag=true先執行然後a=a+5馬上跟着執行然後就成a=0+5
使用volatile關鍵字可以解決指令重排線程安全性獲得保證
單例模式在多線程環境下可能存在的安全性問題
使用volatile寫單例模式
單機版單例模式
package com.wsx;
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
}
public static SingletonDemo getInstance(){
if(instance == null){
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
}
}
多線程版單例模式
package com.wsx;
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
}
public static SingletonDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
單例模式volatile分析
DCL(double Check Lock雙端檢鎖機制)
demo1
這種情況很小几率不安全,因爲存在指令重排情況
package com.wsx;
public class SingletonThreadDemo {
private static SingletonThreadDemo instance = null;
private SingletonThreadDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
}
public static SingletonThreadDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
//雙端檢鎖機制
if(instance == null) {
synchronized (SingletonDemo.class){
if(instance == null){
instance = new SingletonThreadDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonThreadDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
高併發下DCL的volatile的單例模式
最終版本
package com.wsx;
public class SingletonThreadDemo {
//加volatile防止指令重排
private static volatile SingletonThreadDemo instance = null;
private SingletonThreadDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
}
public static SingletonThreadDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
//雙端檢鎖機制
if(instance == null) {
synchronized (SingletonThreadDemo.class){
if(instance == null){
instance = new SingletonThreadDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonThreadDemo.getInstance();
},String.valueOf(i)).start();
}
}
}