乖乖兒這章東西真多,對(duì)于我這個(gè)不愛(ài)看書(shū)的人岗屏,真是受不了啊辆琅,這章代碼多,理論也不少这刷,需要靜心整理婉烟,不過(guò)這章也是多線程技術(shù)的重中之重,所以必須要堅(jiān)持下去暇屋。come on似袁! ----- 這里筆記基本都是一個(gè)理論一個(gè)代碼示例,用代碼證明理論這種抽象的東西
synchronized同步方法
線程安全
:獲得的實(shí)例變量的值是經(jīng)過(guò)同步處理的,不會(huì)出現(xiàn)臟讀的現(xiàn)象昙衅。
非線程安全
:多個(gè)線程對(duì)同一個(gè)對(duì)象中的實(shí)例變量進(jìn)行并發(fā)訪問(wèn)時(shí)發(fā)生扬霜,產(chǎn)生的后果就是“臟讀”。
同步方法的一些特性
- 方法內(nèi)的變量為線程安全(每一個(gè)線程操作自己的局部變量)
class HashSelfPrivateNum{
public void setNum(String name){
int num = 0; // This is important
if("a".equals(name)){
num = 100;
System.out.println("a set over");
}else{
num = 200;
System.out.println(name +" set over");
}
try{
Thread.sleep(1000);
System.out.println(name + " : num = " + num);
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.setNum("over");
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
運(yùn)行結(jié)果:
over set over
a set over
over : num = 200
a : num = 100
- 實(shí)例變量是非線程安全(多個(gè)線程操作同一個(gè)實(shí)例變量)
class HashSelfPrivateNum{
private int num; // This is important
public void setNum(String name){
if("a".equals(name)){
num = 100;
System.out.println("a set over");
}else{
num = 200;
System.out.println(name +"set over");
}
try{
Thread.sleep(1000);
System.out.println(name + " : num = " + num);
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.setNum("over");
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
運(yùn)行結(jié)果:
a set over
overset over
over : num = 200
a : num = 200
在這里我們可以清晰的看見(jiàn)數(shù)據(jù)發(fā)生了錯(cuò)誤而涉,明明a應(yīng)該是100結(jié)果卻是200.這就是多個(gè)線程訪問(wèn)同一個(gè)實(shí)例變量帶來(lái)的隱患著瓶。
避免這種隱患的方式就是在setNum方法前加一個(gè)synchronized關(guān)鍵字,讓setNum方法成為同步方法啼县。
- 多個(gè)對(duì)象對(duì)應(yīng)多個(gè)鎖(每個(gè)對(duì)象都有自己的鎖材原,并不是公用同一個(gè))
先將上面的代碼加上synchronized看看運(yùn)行結(jié)果
class HashSelfPrivateNum{
private int num; // This is important
public synchronized void setNum(String name){
if("a".equals(name)){
num = 100;
System.out.println("a set over");
}else{
num = 200;
System.out.println(name +"set over");
}
try{
Thread.sleep(1000);
System.out.println(name + " : num = " + num);
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.setNum("over");
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
//HashSelfPrivateNum numRefTwo = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
運(yùn)行結(jié)果:
a set over
a : num = 100
overset over
over : num = 200
從結(jié)果可以看出線程O(píng)ne先執(zhí)行完setNum方法,線程Two才執(zhí)行setNum季眷。
接下來(lái)證明本小節(jié)這個(gè)理論
將第53行注釋拿掉余蟹,再將55行的numRef改為numRefTwo。
運(yùn)行結(jié)果:
a set over
overset over
a : num = 100
over : num = 200
我們發(fā)現(xiàn)線程O(píng)ne執(zhí)行setNum方法還沒(méi)執(zhí)行完子刮,線程Two就開(kāi)始執(zhí)行setNum方法威酒,這里說(shuō)明線程O(píng)ne執(zhí)行setNum方法是拿的numRef對(duì)象的鎖,線程Two執(zhí)行setNum方法拿的是numRefTwo對(duì)象的鎖话告,他們互不干擾兼搏。
- synchronized 方法鎖的是對(duì)象
- synchronized 鎖的重入(當(dāng)一個(gè)線程的一個(gè)對(duì)象鎖后,再次請(qǐng)求此對(duì)象鎖是可以再次得到該對(duì)象的鎖的)
- 出現(xiàn)異常鎖自動(dòng)釋放
class HashSelfPrivateNum{
public synchronized void setNum(String name){
System.out.println(Thread.currentThread().getName() + " set over");
if("a".equals(name)){
Integer.parseInt("adb");
}
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " end");
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.setNum("over");
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
運(yùn)行結(jié)果:
ThreadOne set over
Exception in thread "ThreadOne" ThreadTwo set over
java.lang.NumberFormatException: For input string: "adb"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at HashSelfPrivateNum.setNum(Demo.java:7)
at ThreadOne.run(Demo.java:29)
ThreadTwo end
我們可以清楚看見(jiàn)當(dāng)ThreadOne拋出異常ThreadTwo立刻拿到numRef對(duì)象的鎖沙郭,繼續(xù)執(zhí)行了下去佛呻。
- 同步不具有繼承性
當(dāng)我們?cè)诟割悓?xiě)了一個(gè)同步方法,如果在子類重寫(xiě)此方法還需要我們重新寫(xiě)上synchronized關(guān)鍵字病线。
synchronized 同步語(yǔ)句塊
效率吓著! 效率! 效率送挑!
synchronized 同步語(yǔ)句塊的一些特性
- 彌補(bǔ)同步方法的低效性
同步方法的局限性導(dǎo)致的原因是synchronized的作用域在整個(gè)方法绑莺,這將導(dǎo)致方法中無(wú)需同步的代碼也必須要同步。
例如在同步方法里有一個(gè)非常耗時(shí)的請(qǐng)求操作惕耕,然而我們只需要在請(qǐng)求得到數(shù)據(jù)后在同步設(shè)置方法纺裁。
優(yōu)化代碼:
class HashSelfPrivateNum{
private int num; // This is important
public void setNum(String name){
System.out.println(Thread.currentThread().getName() + " set over");
try{
Thread.sleep(5000); // 模擬非常大的數(shù)據(jù)請(qǐng)求
}catch(Exception e){
e.printStackTrace();
}
synchronized(this){
this.num = 100;
System.out.println(Thread.currentThread().getName() + " end");
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.setNum("over");
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
輸出結(jié)果:
ThreadOne set over
ThreadTwo set over
ThreadOne end
ThreadTwo end
- synchronized 代碼塊間的同步性
class HashSelfPrivateNum{
public synchronized void setNum(String name){
System.out.println(Thread.currentThread().getName() + " set over");
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " end");
}catch(Exception e){
e.printStackTrace();
}
}
public void print(){
synchronized(this){
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " print");
}catch(Exception e){
e.printStackTrace();
}
}
}
}
class ThreadOne extends Thread{
private HashSelfPrivateNum numRef;
public ThreadOne(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadOne");
}
public void run(){
numRef.setNum("a");
}
}
class ThreadTwo extends Thread{
private HashSelfPrivateNum numRef;
public ThreadTwo(HashSelfPrivateNum numRef){
this.numRef = numRef;
this.setName("ThreadTwo");
}
public void run(){
numRef.print();
}
}
public class Demo{
public static void main(String[] args) throws Exception{
HashSelfPrivateNum numRef = new HashSelfPrivateNum();
ThreadOne one = new ThreadOne(numRef);
ThreadTwo two = new ThreadTwo(numRef);
one.start();
two.start();
}
}
輸出結(jié)果:
ThreadOne set over
ThreadOne end
ThreadTwo print
- 將任意對(duì)象作為對(duì)象監(jiān)聽(tīng)器
對(duì)于一個(gè)實(shí)例我們可以讓讀和寫(xiě)方法間進(jìn)行同步,但是如果是其他無(wú)關(guān)方法呢司澎?如果我們也讓他們保持同步那該多么影響效率啊~