synchronized關鍵字詳解
參考資料:Java 多執行緒程式設計核心技術——高洪巖
java語言的關鍵字,可用來給物件和方法或者程式碼塊加鎖,當它鎖定一個方法或者一個程式碼塊的時候,同一時刻最多隻有一個執行緒執行這段程式碼。當兩個併發執行緒訪問同一個物件object中的這個加鎖同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。然而,當一個執行緒訪問object的一個加鎖程式碼塊時,另一個執行緒仍然可以訪問該object中的非加鎖程式碼塊。
一。synchronized同步方法
①方法內部的變數是執行緒安全,實類變數非執行緒安全
package com.synch; public class HalfSelNum { private int num = 0 ; synchronized public void add(String username) { if("a".equals(username)) { num = 100; try { Thread.sleep(100); System.out.println("a set over"+num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { num = 200; System.out.println("b set over"+num); } System.out.println(username+" num="+num); } }
package com.synch; public class ThreadA extends Thread{ private HalfSelNum halfSelNum; public ThreadA( HalfSelNum halfSelNum) { // TODO Auto-generated constructor stub this.halfSelNum = halfSelNum; } @Override public void run() { // TODO Auto-generated method stub //super.run(); halfSelNum.add("a"); } }
package com.synch; public class ThreadB extends Thread{ private HalfSelNum halfSelNum; public ThreadB( HalfSelNum halfSelNum) { // TODO Auto-generated constructor stub this.halfSelNum = halfSelNum; } @Override public void run() { // TODO Auto-generated method stub //super.run(); halfSelNum.add("b"); } }
package com.synch;
public class Run {
public static void main(String[] args) {
HalfSelNum numRef = new HalfSelNum();
HalfSelNum numRef2 = new HalfSelNum();
ThreadA ta = new ThreadA(numRef);
ThreadB tb = new ThreadB(numRef2);
ta.start();
tb.start();
}
}
②髒讀現象
package com.synch.zd;
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username ,String password){
this.username = username;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.password = password;
System.out.println("set method thread name is "+Thread.currentThread().getName()
+";username:"+username+";password:"+password);
}
public void getValue(){
System.out.println("get method thread name is "+Thread.currentThread().getName()
+";username:"+username+";password:"+password);
}
}
package com.synch.zd;
public class ThreadA extends Thread{
public PublicVar publicVar;
public ThreadA(PublicVar publicVar) {
this.publicVar = publicVar;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
publicVar.setValue("B", "BB");
}
public static void main(String[] args) {
PublicVar pv = new PublicVar();
ThreadA ta = new ThreadA(pv);
ta.start();
try {
ta.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pv.getValue();
}
}
列印結果:
get method thread name is main;username:B;password:AA
set method thread name is Thread-0;username:B;password:BB
當threadA呼叫同步方法setValue時,獲取了setValue的所在物件的物件鎖其它執行緒必須等threadA執行完後才可以呼叫setValue,但是可以呼叫其它非同步方法,
這時只需要在getValue方法上也加上synchronized,這樣threadA呼叫setValue時得到了該方法所在物件的物件鎖,其它執行緒呼叫getValue同步方法時就無法獲取到物件鎖只能等threadA釋放物件鎖。列印結果如下:
set method thread name is Thread-0;username:B;password:BB
get method thread name is main;username:B;password:BB
③鎖重入
synchronized有鎖重入功能,也就是在使用synchronized時,當一個執行緒得到一個物件鎖後,再次請求該物件鎖是可以得到的,執行緒執行service1時得到了物件鎖,而service1內部又呼叫service2同步方法,這是要再次獲取物件鎖,是可以得到物件鎖的。
package com.synch.cr;
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............");
}
}
package com.synch.cr;
public class MyThread extends Thread{
public void run() {
Service service = new Service();
service.service1();
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
列印結果:
service1............
service2............
service3............
④出現異常會自動釋放鎖
package com.synch.yc;
import java.util.Random;
public class Service {
synchronized public void testMethod() {
if("a".equals(Thread.currentThread().getName())) {
System.out.println("threadName:"+Thread.currentThread().getName()+
" run beginTime:"+System.currentTimeMillis());
while(true) {
if((Math.random()+"").substring(0,8).equals("0.123456")) {
System.out.println("threadName:"+Thread.currentThread().getName()+
" run exception:"+System.currentTimeMillis());
Integer.parseInt("a");
}
}
} else {
System.out.println("threadName:"+Thread.currentThread().getName()+
" run beginTime:"+System.currentTimeMillis());
}
}
}
package com.synch.yc;
public class Threada extends Thread{
private Service service;
public Threada(Service service) {
// TODO Auto-generated constructor stub
this.service = service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.testMethod();
}
}
package com.synch.yc;
public class Threadb extends Thread{
private Service service;
public Threadb(Service service) {
// TODO Auto-generated constructor stub
this.service = service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.testMethod();
}
}
package com.synch.yc;
public class Run {
public static void main(String[] args) {
Service s = new Service();
Threada ta = new Threada(s);
ta.setName("a");
ta.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Threadb tb = new Threadb(s);
tb.setName("b");
tb.start();
}
}
列印結果:
threadName:a run beginTime:1468229383956
threadName:a run exception:1468229384429
Exception in thread "a" threadName:b run beginTime:1468229384429
java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at com.synch.yc.Service.testMethod(Service.java:15)
at com.synch.yc.Threada.run(Threada.java:15)
執行緒threada出現異常並釋放鎖,執行緒theadb執行列印
⑤同步不能繼承
package com.synch.extend;
public class Main {
synchronized public void serviceMethod() {
System.out.println("in main begin threadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("in main end threadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
package com.synch.extend;
public class Sub extends Main {
public void serviceMethod() {
System.out.println("in Sub begin threadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("in Sub end threadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
package com.synch.extend;
public class Threada extends Thread{
private Sub sub;
public Threada( Sub sub) {
// TODO Auto-generated constructor stub
this.sub = sub;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
sub.serviceMethod();
}
}
package com.synch.extend;
public class Threadb extends Thread{
private Sub sub;
public Threadb( Sub sub) {
// TODO Auto-generated constructor stub
this.sub = sub;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
sub.serviceMethod();
}
}
package com.synch.extend;
public class Client {
public static void main(String[] args) {
Sub sub = new Sub();
Threada ta = new Threada(sub);
ta.setName("a");
ta.start();
Threadb tb = new Threadb(sub);
tb.setName("b");
tb.start();
}
}
列印結果:
in Sub begin threadName:a time:1468230402003
in Sub begin threadName:b time:1468230402004
in Sub end threadName:b time:1468230402104
in Sub end threadName:a time:1468230402104
可以看出sub的serviceMetod方法是butongbu
二synchronized同步語句塊
使用synchronized關鍵字宣告的方法是有弊端的,比如A執行緒執行的同步方法需要很長時間,那麼另外一個執行緒B想要執行這個方法就需要等待較長的時間,在這個情況下就需要使用synchronized同步語句塊。
程式碼 :
package com.synch.dmk;
public class HalfSelNum {
public void add() {
synchronized (this) {
System.out.println("threadName:"+Thread.currentThread().getName()+" start:"+System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("threadName:"+Thread.currentThread().getName()+" end:"+System.currentTimeMillis());
}
}
}
package com.synch.dmk;
public class ThreadA extends Thread{
private HalfSelNum halfSelNum;
public ThreadA( HalfSelNum halfSelNum) {
// TODO Auto-generated constructor stub
this.halfSelNum = halfSelNum;
}
@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
halfSelNum.add();
}
}
package com.synch.dmk;
public class ThreadB extends Thread{
private HalfSelNum halfSelNum;
public ThreadB( HalfSelNum halfSelNum) {
// TODO Auto-generated constructor stub
this.halfSelNum = halfSelNum;
}
@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
halfSelNum.add();
}
}
package com.synch.dmk;
public class Run {
public static void main(String[] args) {
HalfSelNum numRef = new HalfSelNum();
ThreadA ta = new ThreadA(numRef);
ThreadB tb = new ThreadB(numRef);
ta.start();
tb.start();
}
}
列印結果:
threadName:Thread-0 start:1468311785240
threadName:Thread-0 end:1468311785341
threadName:Thread-1 start:1468311785341
threadName:Thread-1 end:1468311785441
可以看出和使用synchronized 修飾的方法一樣是同步執行的,效率還是沒有提高
package com.synch.dmk;
public class HalfSelNum {
private String name1 ;
private String name2 ;
public void doTask() {
System.out.println("begin Task....................");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String str1= "start run ..........."+Thread.currentThread().getName();
String str2= "start run ..........."+Thread.currentThread().getName();
synchronized (this) {
name1=str1;
name2=str2;
}
System.out.println("name1=0"+name1);
System.out.println("name2=0"+name2);
}
}
列印結果:
begin Task....................
begin Task....................
name1=0start run ...........Thread-1
name1=0start run ...........Thread-1
name2=0start run ...........Thread-1
name2=0start run ...........Thread-1
當一個執行緒訪問一個物件的同步程式碼塊時,其它執行緒可以訪問該物件的非同步程式碼塊
在使用synchronized(this)時要注意,當一個執行緒訪問synchronized(this)程式碼塊時其它的synchronized(this)程式碼塊將被阻塞。這說明synchronized(this)使用的‘物件監視器’是一個。鎖非this物件有一定的優勢,若果一個類中有多個synchronized方法,這是使用鎖this,會實現同步,但會阻塞,影響效率。如果使用鎖非this物件。則synchronized中的程式與同步方法是非同步的,不與其他this鎖同步方法爭搶this鎖,則可以大大提高執行效率。
程式碼:
package com.synch.syb;
public class HalfSelNum {
protected String lock = new String();
public void doTask1() {
synchronized (lock) {
System.out.println(" a begin....................");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(" a end....................");
}
}
synchronized public void doTask2() {
System.out.println(" b begin....................");
System.out.println(" b begin....................");
}
}
package com.synch.syb;
public class ThreadA extends Thread{
private HalfSelNum halfSelNum;
public ThreadA( HalfSelNum halfSelNum) {
// TODO Auto-generated constructor stub
this.halfSelNum = halfSelNum;
}
@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
halfSelNum.doTask1();
}
}
package com.synch.syb;
public class ThreadB extends Thread{
private HalfSelNum halfSelNum;
public ThreadB( HalfSelNum halfSelNum) {
// TODO Auto-generated constructor stub
this.halfSelNum = halfSelNum;
}
@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
halfSelNum.doTask2();
}
}
package com.synch.syb;
public class Run {
public static void main(String[] args) {
HalfSelNum numRef = new HalfSelNum();
ThreadA ta = new ThreadA(numRef);
ThreadB tb = new ThreadB(numRef);
ta.start();
tb.start();
}
}
列印結果:
a begin....................
b begin....................
b begin....................
a end....................
關鍵字synchronized還可以對靜態方法持鎖,如果這樣寫那是對當前Java檔案的Class 類進行持鎖。
三。死鎖
死鎖是程式設計的bug,在程式設計中因避免雙方互相持有對方鎖,只要互相等待對方鎖,就可能出現死鎖。 死鎖示例:package com.synch.ss;
public class HalfSelNum extends Thread{
private String username ;
private String locka = new String();
private String lockb = new String();
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public void run() {
if("a".equals(username)) {
synchronized (locka) {
System.out.println("a start time:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lockb) {
System.out.println("lockb 獲取到了");
}
}
} else {
synchronized (lockb) {
System.out.println("b start time:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (locka) {
System.out.println("locka 獲取到了");
}
}
}
}
public static void main(String[] args) {
HalfSelNum a = new HalfSelNum();
a.setUsername("a");
Thread ta = new Thread(a);
ta.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.setUsername("b");
Thread tb = new Thread(a);
tb.start();
}
}