三、synchronized鎖機制
一、synchronized鎖機制
1、同步鎖
髒讀:一個常見的概念。在多執行緒中,難免會出現在多個執行緒中對同一個物件的例項變數進行併發訪問的情況,如果不做正確的同步處理,那麼產生的後果就是"髒讀",也就是取到的資料其實是被更改過的。
示例:
public class Lock_JobRun extends Thread{ private String str; private Lock_RunFuncion lockRunFuncion; public Lock_JobRun(String str,Lock_RunFuncion lockRunFuncion) { this.str=str; this.lockRunFuncion=lockRunFuncion; } public void run(){ try { lockRunFuncion.addNumber(str); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Lock_RunFuncion{ private int num=0; public void addNumber(String str) throws InterruptedException { if(str.equals("a")){ num=100; System.out.println(Thread.currentThread().getName()+" str: "+str+" 執行完畢睡覺去了.."); Thread.sleep(1000); }else{ num=200; System.out.println(Thread.currentThread().getName()+" str: "+str+" 執行完畢"); } System.out.println(Thread.currentThread().getName()+" 執行結果:"+num); } } public class RunThread { public static void main(String[] args) throws InterruptedException { Lock_RunFuncion lockRunFuncion=new Lock_RunFuncion(); Lock_JobRun lockJobRunA=new Lock_JobRun("a",lockRunFuncion); Lock_JobRun lockJobRunB=new Lock_JobRun("b",lockRunFuncion); lockJobRunA.start(); lockJobRunB.start(); } }
結果:
Thread-0 str: a 執行完畢睡覺去了…
Thread-1 str: b 執行完畢
Thread-1 執行結果:200
Thread-0 執行結果:200
結論:一個物件分別讓兩個執行緒去執行,都是非同步的,Thread-0呼叫完後休眠1S,過程中被Thread-1給修改了,因此最後Thread-0列印到的是被Thread-1修改後的
這樣資料就不正確了啊,這時可以使用同步鎖 synchronized
加上synchronized 時呢?
程式碼示例:
public class Lock_RunFuncion{ private int num=0; public synchronized void addNumber(String str) throws InterruptedException { if(str.equals("a")){ num=100; System.out.println(Thread.currentThread().getName()+" str: "+str+" 執行完畢睡覺去了.."); Thread.sleep(1000); }else{ num=200; System.out.println(Thread.currentThread().getName()+" str: "+str+" 執行完畢"); } System.out.println(Thread.currentThread().getName()+" 執行結果:"+num); } }
執行結果:
Thread-0 str: a 執行完畢睡覺去了…
Thread-0 執行結果:100
Thread-1 str: b 執行完畢
Thread-1 執行結果:200
總結:Thread-1 必須等Thread-0執行完後才去執行
2、多個物件多個鎖
程式碼示例:
Lock_RunFuncion lockRunFuncionA=new Lock_RunFuncion(); Lock_RunFuncion lockRunFuncionB=new Lock_RunFuncion(); Lock_JobRun lockJobRunA=new Lock_JobRun("a",lockRunFuncionA); Lock_JobRun lockJobRunB=new Lock_JobRun("b",lockRunFuncionB); lockJobRunA.start(); lockJobRunB.start();
結果:
Thread-1 str: b 執行完畢
Thread-0 str: a 執行完畢睡覺去了…
Thread-1 執行結果:200
Thread-0 執行結果:100
總結:看到沒有,同步鎖沒有同步執行了我。
之前例項的同步鎖,應該是物件執行完後到下一個物件。其實這只是針對同一個物件才會有等待的情況。物件都不同了都不互相干擾。
3、synchronized方法與鎖物件
(1)物件內方法鎖與非方法鎖
程式碼示例:
public class Lock_RunFuncion_1 {
public synchronized void synchrMethod() throws InterruptedException {
System.out.println("synchrMethod "+Thread.currentThread().getName()+" start");
Thread.sleep(3000);
System.out.println("synchrMethod "+Thread.currentThread().getName()+" end");
}
public void method() throws InterruptedException {
System.out.println("method "+Thread.currentThread().getName()+" start");
System.out.println("method "+Thread.currentThread().getName()+" end");
}
}
public class Lock_JobRun_2 extends Thread{
private Lock_RunFuncion_1 lockRunFuncion1;
public Lock_JobRun_2(Lock_RunFuncion_1 lockRunFuncion1) {
this.lockRunFuncion1 = lockRunFuncion1;
}
public void run(){
try {
lockRunFuncion1.synchrMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Lock_JobRun_1 extends Thread{
private Lock_RunFuncion_1 lockRunFuncion1;
public Lock_JobRun_1(Lock_RunFuncion_1 lockRunFuncion1) {
this.lockRunFuncion1 = lockRunFuncion1;
}
public void run(){
try {
lockRunFuncion1.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Lock_RunFuncion_1 lockRunFuncion1=new Lock_RunFuncion_1();
Lock_JobRun_1 lockJobRun1=new Lock_JobRun_1(lockRunFuncion1);
Lock_JobRun_2 lockJobRun2=new Lock_JobRun_2(lockRunFuncion1);
lockJobRun1.start();
lockJobRun2.start();
}
結果:
method Thread-0 start
synchrMethod Thread-1 start
method Thread-0 end
synchrMethod Thread-1 end
總結:一個物件中可以存在 非同步和同步方法,非同步方法當然也不需要等待同步執行完了,如果兩個方法都是同步的,會依次執行
驗證一個物件裡都加方法鎖:
程式碼示例:
public synchronized void method() throws InterruptedException {
System.out.println("method "+Thread.currentThread().getName()+" start");
System.out.println("method "+Thread.currentThread().getName()+" end");
}
結果:
method Thread-0 start
method Thread-0 end
synchrMethod Thread-1 start
synchrMethod Thread-1 end
(2)synchronized鎖重入
程式碼示例:
public class Lock_RunFuncion_2 {
public synchronized void method1() {
System.out.println("m1");
method2();
}
public synchronized void method2() {
System.out.println("m2");
method3();
}
public synchronized void method3() {
System.out.println("m3");
}
}
public class Lock_JobRun_3 extends Thread{
public void run(){
Lock_RunFuncion_2 lock_runFuncion_2=new Lock_RunFuncion_2();
lock_runFuncion_2.method1();
}
}
public static void main(String[] args) {
Lock_JobRun_3 lockJobRun3=new Lock_JobRun_3();
lockJobRun3.start();
}
結果:
m1
m2
m3
總結:可以看到依次列印了。這證明了物件可以再次獲取自己的內部鎖。這種鎖重入的機制,也支援在父子類繼承的環境中。
注意: 發生異常是自動釋放鎖
(3)synchronized同步程式碼塊
有沒有想過用synchroized同對象執行的方法都要進行等待,那麼方法裡含有不需要等待的程式碼嗎?
程式碼示例:
public class sync_RunFuncion {
int count=1000000000;
public void method() throws InterruptedException {
System.out.println("not synchronized start --- "+Thread.currentThread().getName());
for (int i=0;i<count;i+=2) {
i--;
}
System.out.println("not synchronized find --- "+Thread.currentThread().getName());
synchronized (this){
System.out.println("synchronized start --- "+Thread.currentThread().getName());
for (int i=0;i<count;i+=2) {
i--;
}
System.out.println("synchronized find --- "+Thread.currentThread().getName());
}
}
}
public class Sync_JobRun extends Thread {
private sync_RunFuncion syncRunFuncion;
public Sync_JobRun(sync_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
syncRunFuncion.method();
}
}
public static void main(String[] args) throws InterruptedException {
sync_RunFuncion syncRunFuncion=new sync_RunFuncion();
Sync_JobRun syncJobRun=new Sync_JobRun(syncRunFuncion);
syncJobRun.start();
Sync_JobRun syncJobRun2=new Sync_JobRun(syncRunFuncion);
syncJobRun2.start();
}
結果:
not synchronized start — Thread-1
not synchronized start — Thread-0
not synchronized find — Thread-1
synchronized start — Thread-1
not synchronized find — Thread-0
synchronized find — Thread-1
synchronized start — Thread-0
synchronized find — Thread-0
總結:可以看到執行緒1還沒呼叫完not synchronized find的方法,執行緒0就開始呼叫not synchronized start方法了。
而synchronized 方法卻老老實實地執行完start-find…。
可以得出在synchronized內當某個執行緒在訪問時,會被阻塞。而之外的卻不會。
這樣做大大地提高了效率,非同步方法也不需要等待同步方法釋放鎖了
。
(4)兩個synchronized塊之間具有互斥性
程式碼示例:
public class sync_RunFuncion {
public void opposite_Tom(){
synchronized (this){
try {
System.out.println("opposite_Tom start.. --- "+Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("opposite_Tom end.. --- "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void opposite_Jones(){
synchronized (this) {
try {
System.out.println("opposite_Jones start.. --- "+Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("opposite_Jones end.. --- "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Sync_Op_Tom_JobRun extends Thread{
private sync_RunFuncion syncRunFuncion;
public Sync_Op_Tom_JobRun(sync_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
syncRunFuncion.opposite_Jones();
}
}
public class Sync_Op_Jones_JobRun extends Thread{
private sync_RunFuncion syncRunFuncion;
public Sync_Op_Jones_JobRun(sync_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
syncRunFuncion.opposite_Tom();
}
}
public static void main(String[] args) {
sync_RunFuncion syncRunFuncion=new sync_RunFuncion();
for(int i=0;i<2;i++) {
Sync_Op_Jones_JobRun syncOpJonesJobRun = new Sync_Op_Jones_JobRun(syncRunFuncion);
Sync_Op_Tom_JobRun syncOpTomJobRun = new Sync_Op_Tom_JobRun(syncRunFuncion);
syncOpJonesJobRun.start();
syncOpTomJobRun.start();
}
}
結果:
opposite_Tom start… — Thread-2
opposite_Tom end… — Thread-2
opposite_Jones start… — Thread-3
opposite_Jones end… — Thread-3
opposite_Jones start… — Thread-1
opposite_Jones end… — Thread-1
opposite_Tom start… — Thread-0
opposite_Tom end… — Thread-0
總結:這裡看得不是很仔細,到sleep會暫停一會,依次執行,synchronized 塊的是獲取物件鎖,鎖的是整個物件,要麼是opposite_Tom執行要麼opposite_Jones執行,看哪個執行緒先獲取鎖,誰先執行,執行期間會將物件阻塞。
(5)synchronized塊和synchronized方法
程式碼示例:
public class sync_RunFuncion {
public void sync_Block(){
synchronized (this){
try {
System.out.println("opposite_Tom start.. --- "+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("opposite_Tom end.. --- "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sync_Function(){
System.out.println("opposite_Jones start.. --- "+Thread.currentThread().getName());
System.out.println("opposite_Jones end.. --- "+Thread.currentThread().getName());
}
}
public class Sync_Op_Jones_JobRun extends Thread{
private sync_RunFuncion syncRunFuncion;
public Sync_Op_Jones_JobRun(sync_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
//---呼叫sync碼塊與sync方法特性
syncRunFuncion.sync_Block();
}
}
public class Sync_Op_Tom_JobRun extends Thread{
private sync_RunFuncion syncRunFuncion;
public Sync_Op_Tom_JobRun(sync_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
//---呼叫sync碼塊與sync方法特性
syncRunFuncion.sync_Function();
}
}
public static void main(String[] args) {
sync_RunFuncion syncRunFuncion=new sync_RunFuncion();
for(int i=0;i<3;i++) {
Sync_Op_Jones_JobRun syncOpJonesJobRun = new Sync_Op_Jones_JobRun(syncRunFuncion);
Sync_Op_Tom_JobRun syncOpTomJobRun = new Sync_Op_Tom_JobRun(syncRunFuncion);
syncOpJonesJobRun.start();
syncOpTomJobRun.start();
}
}
}
結果:
opposite_Jones start… — Thread-1
opposite_Jones end… — Thread-1
opposite_Tom start… — Thread-0
…等待2S 自己加的忽略就好
opposite_Tom end… — Thread-0
opposite_Jones start… — Thread-5
opposite_Jones end… — Thread-5
opposite_Tom start… — Thread-4
…等待2S 自己加的忽略就好
opposite_Tom end… — Thread-4
opposite_Tom start… — Thread-2
…等待2S 自己加的忽略就好
opposite_Tom end… — Thread-2
opposite_Jones start… — Thread-3
opposite_Jones end… — Thread-3
結論:synchronized方法和synchronized程式碼塊都是呼叫同一個物件鎖的。
(6)將任意物件作為物件監視器
前面都使用synchronized(this)的格式來同步程式碼塊,其實Java還支援對"任意物件"作為物件監視器來實現同步的功能。
這個"任意物件"大多數是例項變數及方法的引數,使用格式為synchronized(非this物件)。看一下將任意物件作為物件監視器的使用例子:
程式碼示例:
public class Sync_Object_JobRun extends Thread {
private sync_Object_RunFuncion syncRunFuncion;
public Sync_Object_JobRun(sync_Object_RunFuncion syncRunFuncion) {
this.syncRunFuncion=syncRunFuncion;
}
@Override
public void run(){
try {
syncRunFuncion.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class RunThread {
public static void main(String[] args) {
sync_Object_RunFuncion syncRunFuncion=new sync_Object_RunFuncion();
for(int i=0;i<4;i++){
Sync_Object_JobRun syncObjectJobRun=new Sync_Object_JobRun(syncRunFuncion);
syncObjectJobRun.start();
}
}
}
public class sync_Object_RunFuncion {
private String str=new String();
public void method() throws InterruptedException {
int count=1000000000;
synchronized (str){
System.out.println("synchronized start --- "+Thread.currentThread().getName());
for (int i=0;i<count;i+=2) {
i--;
}
Thread.sleep(1000);
System.out.println("synchronized find --- "+ Thread.currentThread().getName());
}
}
}
結果:
synchronized start — Thread-1
synchronized find — Thread-1
synchronized start — Thread-2
synchronized find — Thread-2
synchronized start — Thread-3
synchronized find — Thread-3
synchronized start — Thread-0
synchronized find — Thread-0
總結:synchronized放非this的物件也實現了同步的效果,實際上在new的時候就已經例項化了物件,呼叫的都是同一個。
只要物件的引用不變,即使改變了物件的屬性,執行結果依然是同步的。