第2章對象及變量的并發通路
标簽: Java多線程程式設計
《Java多線程程式設計核心技術》 個人筆記
synchronized同步方法
方法内的變量為線程安全
執行個體變量非線程安全
多個對象多個鎖
synchronized方法與鎖對象
髒讀
synchronized鎖重入
出現異常鎖自動釋放
同步具有不可繼承性
synchronized同步語句塊
synchronized方法的弊端
synchronized同步代碼塊的使用
用同步代碼塊解決同步方法的弊端
一半異步一半同步
synchronized代碼間的同步性
驗證同步synchronizedthis代碼塊是鎖定目前對象的
将任意對象作為對象螢幕
細化驗證3個結論
靜态同步synchronized方法與synchronizedclass代碼塊
資料類型String的常量池特性
同步synchronized方法無線等待與解決
多線程死鎖
内置類與靜态内置類
鎖對象的改變
volatile關鍵字
關鍵字volatile與死循環
解決同步死循環
解決異步死循環
volatile非原子性的特性
使用原子類進行i 操作
原子類也并不完全安全
synchronized代碼塊有volatile同步的功能
本章主要介紹Java多線程中的同步,寫出線程安全的程式,解決非線程安全的相關問題
本章應該着重掌握如下技術點:
synchronized對象螢幕為Object是的使用
synchronized對象螢幕為Class時的使用
非線程安全是如何出現
非關鍵volatile的主要uzoyong
關鍵字volatile與synchronized的差別及使用情況
“非線程安全”其實會在多個線程對同一個對象中的執行個體變量進行并發通路時發生,産生的後果就是“髒讀”,也就是取到的資料其實是被更改過的
而“線程安全”就是獲得的執行個體對象的值是經過同步處理的,不會出現髒讀現象
“非線程安全”問題存在于“執行個體變量”中,如果是方法内部的私有變量,則不存在“非線程安全”問題,所得的結果是“線程安全”的。
如果多個線程共同通路1個對象中的執行個體變量,則可能出現“非線程安全”問題
用線程通路的對象中如果有多個執行個體變量,則運作的結果有肯出現交叉的情況(第一章示範過)
如果對象僅有1個執行個體變量,則有可能出現覆寫的情況(這個變量不是在方法内的局部變量,而是屬于對象的私有變量),要解決隻需要在方法的前面加關鍵字synchronized即可
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
}
兩個線程分别通路同一個類的兩個不同執行個體的相同名稱的同步方法,效果卻是異步的
關鍵字synchronized取得的鎖是對象鎖,而不是把一段代碼或方法當作鎖,
哪個線程先執行帶synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖Lock,那麼其他線程隻能呈等待狀态,前提是多個線程通路的是同一個對象
實驗結論:調用關鍵字synchronized聲明的方法一定是排隊運作的。另外需要牢牢記住“共享”這兩個字,隻有共享資源的讀寫才需要同步化,如果不是共享資源,那麼根本就沒有同步的必要
對與同一個對象的不同方法:
public class MyObject {
synchronized public void methodA() { //保留synchronized
try {
System.out.println("begin methodA threadName="
Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
//public void methodB() { //去掉synchronized做對比
synchronized public void methodB() {
System.out.println("begin methodB threadName="
Thread.currentThread().getName() + " \nbegin time="
System.currentTimeMillis());
System.out.println("end");
//-------自定義線程A------
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;br/>}
@Override
public void run() {
super.run();
object.methodA(); //執行的是methodA()方法
//--------自定義線程B方法
public class ThreadB extends Thread {
public ThreadB(MyObject object) {
object.methodB(); //執行的是methodB()方法
//----------主函數--------
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
結果:methodA 一直保留synchronized,而methodB一次保留,另一次不保留,當methodB保留synchronized時,線程A和線程B執行的methodA 和methodB 為同步的; 當不保留時,是異步的
結論:
A線程先持有object對象的Lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法
A線程先持有object對象的Lock鎖,B線程如果在這時調用object對象中的synchronize類型的方法則需要等待,即同步。
雖然在指派時進行了同步,但在取值時有可能出現一些意想不到的意外,即“髒讀 dirtyRead”
出現髒讀是因為getValue()擷取值時不是同步的,是以加上synchronized關鍵字即可解決。
關鍵字synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個線程得到一個對象鎖後,再次請求此對象鎖時是可以再次得到該對象的鎖的。這也證明在一個synchronized方法/塊内部調用本類的其他synchronized方法時,是永遠可以得到鎖的。
public class Service {
//三個同步方法
synchronized public void service1() {
System.out.println("service1");
service2(); //方法一内部又調用方法二,從這裡 重入 ,再次擷取鎖
synchronized public void service2() {
System.out.println("service2");
service3(); //方法二内部又調用方法三
synchronized public void service3() {
System.out.println("service3");
//----------自定義線程類-----
public class MyThread extends Thread {br/>@Override
Service service = new Service();
service.service1();
//----------主函數-------
MyThread t = new MyThread();
t.start();
可重入鎖的概念:自己可以再次擷取自己的内部鎖。(如果不可以重入,就會造成死鎖)
可重入鎖也支援在父子環境中(如果方法是同步的,則繼承的方法也是同步的?)
出現異常,鎖自動釋放
當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放
如果父類的方法是同步的,則子類繼承後這個方法仍然是同步的
但是如果子類自己的新方法中使用了父類的同步方法,則子類的新方法除了使用父類的同步方法那一句是同步的,其餘代碼還是異步的;如果要使子類的新方法也為同步的,加關鍵字synchronized在新方法前面即可
用synchronized聲明方法在某些情況下是有弊端的,比如A線程調用同步方法執行一個長時間的方法,那麼B線程則必須等待比較長的時間。在這樣的情況下可以使用synchronized同步語句塊。
當并發線程通路同一個對象object中的synchronized(this)同步代碼塊時,一段時間内隻能有一個線程必須等待目前線程執行完這個代碼塊後才能執行該代碼塊。
public void doLongTimeTask() {
System.out.println("begin task");
Thread.sleep(3000);
String privateGetData1 = "長時間處理任務後從遠端傳回的值1 threadName="
Thread.currentThread().getName();
String privateGetData2 = "長時間處理任務後從遠端傳回的值2 threadName="
//同步代碼塊
synchronized (this) {
getData1 = privateGetData1;
getData2 = privateGetData2;
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
一半異步,一半同步
不在synchronized塊中就是異步執行,在synchronized中就是同步執行
當一個線程通路object的一個synchronized(this)同步代碼塊時,其他線程對同一個object中所有其他synchronized(this)同步代碼塊的通路将被阻塞,這說明synchronized使用的“對象螢幕”是同一個。
public class ObjectService {
public void serviceMethodA() {
synchronized (this) { //兩個同步代碼塊在不同的方法裡邊
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end=" + System.currentTimeMillis());
public void serviceMethodB() {
System.out.println("B begin time=" + System.currentTimeMillis());
System.out.println("B end end=" + System.currentTimeMillis());
//----------線程A----------
private ObjectService service;
public ThreadA(ObjectService service) {
this.service = service;br/>}
service.serviceMethodA();//方法A
//---------線程B------
public ThreadB(ObjectService service) {
service.serviceMethodB();//方法B
//---------主函數---------
驗證同步synchronized(this)代碼塊是鎖定目前對象的
synchronized(非this對象)格式的作用:synchronized(非this對象x)同步代碼塊
在多個線程持有“對象螢幕”為同一個對象的前提下,同一時間隻有一個線程可以執行synchronized(非this對象x)中的代碼
當持有“對象螢幕”為同一個對象的前提下,同一時間隻有一個線程可以執行synchronized(非this對象x)同步代碼塊中的代碼
鎖非this對象具有一定的優點:如果在一個類中有很多個synchronized方法,這時雖然能夠實作同步,但會受到阻塞,是以影響運作效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程式與同步方法是異步的,不與其他鎖this的同步方法争搶this鎖,則可大大提高運作效率。
private String usernameParam;
private String passwordParam;
//String anyString = new String();
//作為類的私有變量時,是共享的,同一個鎖
可見,使用“synchronized(非this)同步代碼塊”格式進行同步操作時,對象螢幕必須是同一個對象,否則鎖的不是同一個對象,結果就是異步,就會交叉運作
“synchronized(非this對象x)”格式的寫法是将x對象本身作為“對象螢幕”,這樣就可以得到3個結論:
當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果。
當其他線程執行x對象中的synchronized同步方法時呈同步效果
當其他線程執行x對象方法裡面的synchronized(this)代碼時呈現同步效果。
靜态同步synchronized方法與synchronized(class)代碼塊
關鍵字synchronized還可以應用在static靜态方法上,如果這樣寫,那是對目前*.java檔案對應的Class類進行持鎖
都是同步的效果,和将synchronized關鍵字加到非static方法上使用的效果是一樣的。
synchronized public static void printA() {
System.out.println("線程名稱為:" + Thread.currentThread().getName()
"在" + System.currentTimeMillis() + "進入printA");
"在" + System.currentTimeMillis() + "退出printA");
synchronized public static void printB() {
System.out.println("線程名稱為:" + Thread.currentThread().getName() + "在"
System.currentTimeMillis() + "進入printB");
System.currentTimeMillis() + "退出printB");
synchronized public void printC() {
System.currentTimeMillis() + "進入printC");
System.currentTimeMillis() + "退出printC");
//------------A----------
public class ThreadA extends Thread {br/>@Override
Service.printA();
//------B---------------
public class ThreadB extends Thread { br/>@Override
Service.printB();
//-------------C---------
public class ThreadC extends Thread {
private Service service;
public ThreadC(Service service) {
service.printC();
//----------主函數-----
public class Run {s
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
ThreadC c = new ThreadC(service);
c.setName("C");
c.start();
本質上的不同:類鎖和對象鎖:
synchronized關鍵字加到static靜态方法上是給Class類上鎖,而synchronized關鍵字加到非static靜态方法上是給對象上鎖,這是兩把不同的鎖,是以會異步。
Class鎖可以對類的所有對象執行個體起作用
同步synchronized(class)代碼塊的作用和synchronized static方法的作用一樣,用法如:synchronized(Service.class){ }
在JVM中具有String常量池緩存的功能,是以下面代碼結果是true:
當兩個線程使用的鎖都是相同的字元串,可能造成其中一個不能執行。是以大多數情況下,同步synchronized代碼塊不能使用String作為鎖對象
一個線程調用methodA()後就會死循環,導緻其他線程不能調用methodB()
synchronized public void methodA() {
System.out.println("methodA begin");
boolean isContinueRun = true;
while (isContinueRun) {
System.out.println("methodA end");
System.out.println("methodB begin");
System.out.println("methodB end");
可以改為如下:
public void methodA() {
synchronized(object1){ //同步代碼塊,鎖其他對象
}
public void methodB() {
synchronized(){ //同步代碼塊,鎖自身對象
死鎖:不同的線程都在等待根本不可能被釋放的鎖,進而導緻所有任務都無法繼續完成
public class DealThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
//-----------主函數-----
DealThread t1 = new DealThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(100);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
……
将任何資料類型作為同步鎖時,需要注意的是,是否有多個線程同時持有鎖對象,如果同時持有相同的鎖對象,則這些線程之間是同步的;如果分别獲得鎖對象,則這些線程之間就是異步的
public class MyService {
private String lock = "123";
public void testMethod() {
synchronized (lock) { //此時鎖對象是“123”
System.out.println(Thread.currentThread().getName() + " begin "
lock = "456"; //鎖對象改變為“456”
System.out.println(Thread.currentThread().getName() + " end "
//-----------主函數------
public class Run1 {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
ThreadB b = new ThreadB(service);
Thread.sleep(50); //此處,等待了50毫秒
b.start(); //是以b取得的鎖是 “456”,A、B異步
如果把Thread.sleep(50)注釋掉,則a,b搶的是“123”,表現為同步
隻要對象不變,即使對象的屬性被改變,運作的結果還是同步
關鍵字volatile的主要作用是使變量在多個線程間可見
如果不是在多繼承的情況下,使用繼承Thread類和實作Runnale接口在程式運作的結果上并沒有太大的差別。如果一旦出現“多繼承”的情況,則用實作Runnable接口的方式來處理多線程問題就是很有必要的。
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() {
while (isContinuePrint == true) {
System.out.println("run printStringMethod threadName="
Thread.sleep(1000);
} catch (InterruptedException e) {br/>e.printStackTrace();
printStringMethod();
//--------------主函數----------
PrintString printStringService = new PrintString();
new Thread(printStringService).start(); //啟動線程
System.out.println("我要停止它stopThread="
printStringService.setContinuePrint(false); //設定為false
關鍵字volatile的作用是強中從公共堆棧中取得變量的值,而不是從線程私有資料棧中取得變量的值
volatile private boolean isRunning = true;
這樣私有堆棧和公共堆棧中的isRunning的值就同步了,以後強制從公共堆棧中取值
+
關鍵字synchronized和volatile進行比較:
關鍵字volatile是線程同步的輕量級實作,是以volatile的性能肯定比synchronized要好,并且volatile隻能修飾變量,而synchronized可以修飾方法,以及代碼塊。
多線程通路volatile不會發生阻塞,而synchronized會發生阻塞
volatile能保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會将私有記憶體和公共記憶體中的資料做同步。
關鍵字volatile解決的是變量在多個線程之間的可見性;而synchronized關鍵字解決的是多個線程之間通路資源的同步性
線程安全包含原子性和可見性兩個方面,Java的同步機制都是圍繞這兩個方面來確定線程安全的
使用原子類進行i++ 操作
AtomicInteger
synchronized可以使多個線程通路同一個資源具有同步性,而且它還具有将線程工作記憶體中的私有變量與公共記憶體中的變量同步的功能。