天天看點

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

關鍵字volatile 主要的功能是使變量在多個線程間可見

1 volatile 的作用就是強制從公共堆棧中取得變量的值,而不是從私有資料棧中取得變量的值

1 多線程的方式 -------- 解決同步死循環

下面的例子是産生死循環的代碼

package printstring;
public class PrintString {
	private boolean isContinuePrint = true;
	public boolean isContinuePrint() {
		return isContinuePrint;
	}
	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}
	public void printStringMethod() {
		try {
			while (isContinuePrint == true) {
				System.out.println("run printStringMethod threadName="
						+ Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
           
package test;
import printstring.PrintString;
public class Run {
	public static void main(String[] args) {
		PrintString printStringService = new PrintString();
		printStringService.printStringMethod();
		System.out.println("我要停止它!stopThread="
				+ Thread.currentThread().getName());
		printStringService.setContinuePrint(false);
	}
}
           

1 多線程的方式 -------- 解決同步死循環代碼

package printstring;
public class PrintString implements Runnable {
	private boolean isContinuePrint = true;
	public boolean isContinuePrint() {
		return isContinuePrint;
	}
	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}
	public void printStringMethod() {
		try {
			while (isContinuePrint == true) {
				System.out.println("run printStringMethod threadName="
						+ Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		printStringMethod();
	}
}
           
package test;

import extthread.RunThread;

public class Run {
	public static void main(String[] args) {
		try {
			RunThread thread1 = new RunThread();
			Thread thread =new Thread(thread1);
			thread.start();
			/**
			 * 線程被不被停止起決定性作用的是Thread.sleep(1000);
			 * 如果不設定時間的話,由于主線程比分線程運作的快,就一定能夠停止,如果設定時間為3000 的話,由于主線程睡了3s ,
			 * 是以線程就無法停止了,還有一種情況就是如果線程設定了3s 但是while 循環有循環體的話:例如
			 * public void printStringMethod() {
		            while (isContinuePrint == true) {
			           System.out.println("run printStringMethod threadName="
					+ Thread.currentThread().getName());
		            }
	              }
		                    線程這種情況下也是可以被停止的,但是如果沒有循環體的話
		           public void printStringMethod() {
		            while (isContinuePrint == true) {
		            }
	              }
		                               線程依然是不能停止的        
	          }
			 */
			Thread.sleep(3000);
			thread1.setRunning(false);
			System.out.println("已經指派為false");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
           
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能
我要停止它!stopThread=main
run printStringMethod threadName=Thread-0
           

像這種情況線程是不能停止的

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

同樣 ,如果當上面的代碼運作在-server 服務模式中64bit 的JVM 上時,會出現死循環,解決方法就是volatile

關鍵字volatile 的作用就是強制從公共堆棧中取得變量的值,而不是從私有資料棧中取得變量的值

是什麼原因造成将JVM 設定為-server 時就出現死循環的呢,在啟動RunThread.java 線程時,變量 private boolean isRunning = true; 存在于公共對戰及線程私有堆棧中,在JVM 被設定為-server 模式時為了線程運作的效率,線程一直在私有堆棧中取得isRunning 的值是trrue,而代碼thread.setRunning(false) 雖然被執行,更新的卻是公共堆棧的isRunning 變量值false 是以一直就是死循環的狀态,

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

2 加入volatile 的目的就是使變量在多個線程間可見類似于final(底層實作原理)

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

正常情況下,如果不加volatile 的話,例如在主記憶體中存在一個共享變量x  的值為0 如果線程1 想用這個變量x 那麼會在工作記憶體1 中存在一個副本,把副本的值從0 改為10 ,如果線程2 想用這個變量x 也是用的是工作記憶體2 當中的副本從0 變為20 兩個線程沒有任何的關系在沒有加volatile 之前,但是線上程1 中的變量x 的變化是沒有辦法讓線程2 知道的但是volatile 就是做的這個,隻要是加了這個關鍵字的話,就可以變量的變化在多個線程間是可見的

原來傳統的做法就是,在這個變量上加一把鎖或者在方法上加一把鎖保證資料的一緻性,不管哪個一個線程來都是嘗試擷取這個鎖,隻要是能拿到鎖了

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

源碼就是

package extthread;
public class MyThread extends Thread {
	public static int count;
	synchronized private static void addCount() {
		for (int i = 0; i < 100; i++) {
			count++;
		}
		System.out.println("count=" + count);
	}
	@Override
	public void run() {
		addCount();
	}
}
           

但是這種方式效率是不高的,因為同一個時間隻能有一個線程進來

線程無法停止的問題,隻能是用volatile 就可以解決

package printstring;
public class PrintString implements Runnable {
	volatile private boolean isContinuePrint = true;
	public boolean isContinuePrint() {
		return isContinuePrint;
	}
	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}
	public void printStringMethod() {
		while (isContinuePrint == true) {
		}
	}
	@Override
	public void run() {
		printStringMethod();
	}
}
           
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能
package test;
import printstring.PrintString;
public class Run {
	public static void main(String[] args) throws InterruptedException {
		PrintString printStringService = new PrintString();
		new Thread(printStringService).start();
		Thread.sleep(3000);
		/**
		 * 線程被不被停止起決定性作用的是Thread.sleep(1000);
		 * 如果不設定時間的話,由于主線程比分線程運作的快,就一定能夠停止,如果設定時間為3000 的話,由于主線程睡了3s ,
		 * 是以線程就無法停止了,還有一種情況就是如果線程設定了3s 但是while 循環有循環體的話:例如
		 * @Override
           public void run() {
	             System.out.println("進入run了");
	             while (isRunning == true) {
		         System.out.println("run printStringMethod threadName="
				+ Thread.currentThread().getName());
	       }
	       System.out.println("線程被停止了!");
	                    線程這種情況下也是可以被停止的,但是如果沒有循環體的話
	           @Override
               public void run() {
	             System.out.println("進入run了");
	             while (isRunning == true) {
	             } 
	                               線程依然是不能停止的        
          }
		 */
		Thread.sleep(3000);
		System.out.println("我要停止它!stopThread="
				+ Thread.currentThread().getName());
		printStringService.setContinuePrint(false);
	}
}
           
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

jdk 中就是每一個線程都有自己獨立的記憶體空間就是工作記憶體,在jdk 1.5 以後,不單單是有自己的獨立的工作記憶體還有一塊工作記憶體,主要就是用來放從主記憶體copy過來的副本,這樣做的目的就是增加效率

volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

證明volatile 不具備原子性,同步性

package extthread;
public class MyThread extends Thread {
	 volatile private static int count;
	 private static void addCount() {
		for (int i = 0; i < 1000; i++) {
			count++;
		}
		System.out.println("count=" + count);
	}
	@Override
	public void run() {
		addCount();
	}
}
           
package test.run;

import extthread.MyThread;

public class Run {
	public static void main(String[] args) {
		MyThread[] mythreadArray = new MyThread[10];
		for (int i = 0; i < 10; i++) {
			mythreadArray[i] = new MyThread();
		}

		for (int i = 0; i < 10; i++) {
			mythreadArray[i].start();
		}

	}

}
           

一共是10 個線程,每一個線程對這個count 變量加了1000次,一共應該是10000,但是結果不是這樣的,雖然,這個變量用了volatile 修飾

count=2000
count=2000
count=3265
count=4215
count=4956
count=6288
count=6707
count=7299
count=8044
count=9044
           

但是結果不是10000證明volatile 不具備原子性

package extthread;
public class MyThread extends Thread {
	  private static int count;
	 synchronized private static void addCount() {
		for (int i = 0; i < 1000; i++) {
			count++;
		}
		System.out.println("count=" + count);
	}
	@Override
	public void run() {
		addCount();
	}
}
           
count=1000
count=2000
count=3000
count=4000
count=5000
count=6000
count=7000
count=8000
count=9000
count=10000
           

也可以采用這種但是線程是不安全的

package extthread;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThread extends Thread {
	  private static AtomicInteger  count = new AtomicInteger();
	 synchronized private static void addCount() {
		for (int i = 0; i < 1000; i++) {
			count.incrementAndGet();
		}
		System.out.println("count=" + count);
	}
	@Override
	public void run() {
		addCount();
	}
}
           
volatile 的可見性解讀,synchronized 代碼塊有volatile 同步的功能

雖然不具備原子性,但是性能要比synchronized 要多,不會造成阻塞,但是在很多開源的架構裡面,比如netty的底層代碼就大量使用volatile ,可見netty性能一定是不錯的,這裡需要注意的是,volatile 用于隻針對多個線程可見的變量操作,并不能代替synchronized 的同步功能。

synchronized 代碼塊有volatile 同步的功能

package extthread;
import service.Service;
public class ThreadA extends Thread {
	private Service service;
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.runMethod();
	}
}
           
package extthread;
import service.Service;
public class ThreadB extends Thread {
	private Service service;
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.stopMethod();
	}
}
           
package service;
public class Service {
	private boolean isContinueRun = true;
	public void runMethod() {
		String anyString = new String();
		while (isContinueRun == true) {
			synchronized (anyString) {
			}
		}
		System.out.println("停下來了!");
	}
	public void stopMethod() {
		isContinueRun = false;
	}
}
           
package test;
import service.Service;
import extthread.ThreadA;
import extthread.ThreadB;
public class Run {
	public static void main(String[] args) {
		try {
			Service service = new Service();
			ThreadA a = new ThreadA(service);
			a.start();
			Thread.sleep(1000);
			ThreadB b = new ThreadB(service);
			b.start();
			System.out.println("已經發起停止的指令了!");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
           
已經發起停止的指令了!
停下來了!