Java中synchronized關鍵字使用實踐
1、synchronized修飾類的普通方法
package main.thread; /** * Created by leboop on 2018/11/18. * 測試synchronized關鍵字使用 */ public class SynchronizedClass { public void run(){ for(int i=0;i<5;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執行緒"+Thread.currentThread().getName()+"正在列印i="+i); } } //被synchronized修飾的普通方法 public synchronized void print() { System.out.println(Thread.currentThread().getName()+"執行緒開始執行......"); run(); System.out.println(Thread.currentThread().getName()+"執行緒執行結束......"); } }
測試類如下:
package main.thread; /** * Created by leboop on 2018/11/18. */ public class SychronizedTest { public static void main(String[] args) { SynchronizedClass p=new SynchronizedClass(); //建立t1執行緒 Thread t1=new Thread(new Runnable() { @Override public void run() { p.print(); } }); t1.setName("t1"); //建立t2執行緒 Thread t2=new Thread(new Runnable() { @Override public void run() { p.print(); } }); t2.setName("t2"); //啟動執行緒 t1.start(); t2.start(); } }
測試類執行結果:
t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......
從執行結果來看,執行緒t1和執行緒t2是同步執行print方法的,這是因為兩個執行緒中都是通過同一個物件p來呼叫print方法的,當t1開始執行p.print()時,會取得物件p的鎖,此時執行緒t2拿不到物件p的鎖無法操作p的print方法,等執行緒t1執行完畢釋放物件p的鎖,此時執行緒t2才可以拿到p的鎖來執行print方法。如果建立兩個物件p1和p2,執行緒t1和t2分別使用兩個物件來呼叫print方法,此時測試類修改如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
SynchronizedClass p1=new SynchronizedClass();
SynchronizedClass p2=new SynchronizedClass();
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
p1.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
p2.print();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
程式執行結果如下:
t1執行緒開始執行......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t1正在列印i=0
執行緒t2正在列印i=1
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t2正在列印i=2
執行緒t1正在列印i=3
執行緒t2正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
執行緒t2正在列印i=4
t2執行緒執行結束......
此時,執行緒t1和t2是非同步執行的,因為此時執行緒t1拿到的是p1物件的鎖,和物件p2無關,執行緒t2執行p2.print時會取得p2的鎖,互不影響。
2、synchronized修飾普通方法中的程式碼塊
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
public void run(){
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒"+Thread.currentThread().getName()+"正在列印i="+i);
}
}
public void print() {
synchronized (this){
System.out.println(Thread.currentThread().getName()+"執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName()+"執行緒執行結束......");
}
}
}
這種方式和1相同。
3、synchronized修飾類的靜態方法
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
public static void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
public synchronized static void print() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
}
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
SynchronizedClass.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
SynchronizedClass.print();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
程式執行結果:
t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......
從結果來看,我們發現執行緒t1和執行緒t2是同步執行的。SynchronizedClass.print()改成物件 new SynchronizedClass().print()呼叫也是同步執行的。這是因為執行緒t1執行SynchronizedClass.print()時,取得類的鎖(因為靜態方法是屬於類的),在未執行完前,執行緒t2無法取得該類的鎖,當執行緒t1執行完後,釋放類的鎖,此時執行緒t2可以拿到類的鎖去執行SynchronizedClass.print()。
4、synchronized修飾靜態方法中的程式碼塊
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
public static void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
public static void print() {
synchronized(SynchronizedClass.class){
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
}
}
這種方式與第3種方式相同。
5、如果synchronized修飾了某個靜態方法,該類是否還可以呼叫其他無synchronized關鍵字修飾的靜態方法?
答案是可以的。如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
//無synchronized修飾的靜態方法
public static void otherPrint() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
//synchronized修飾的靜態方法
public synchronized static void print() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
public static void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
}
測試類如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//呼叫synchronized修飾的靜態方法
SynchronizedClass.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//呼叫無synchronized修飾的靜態方法
SynchronizedClass.otherPrint();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
程式執行結果:
t1執行緒開始執行......
t2執行緒開始執行......
執行緒t1正在列印i=0
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t1正在列印i=1
執行緒t2正在列印i=2
執行緒t1正在列印i=2
執行緒t2正在列印i=3
執行緒t1正在列印i=3
執行緒t2正在列印i=4
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒執行結束......
從程式執行結果來看,互不影響。這是因為執行緒t1執行SynchronizedClass.print();時取得類鎖,但是執行緒t2執行SynchronizedClass.otherPrint();不需要鎖,可以直接執行。
6、如果synchronized修飾了某個普通方法,當某個執行緒通過物件呼叫該普通方法時,該物件是否還可以呼叫其他無synchronized關鍵字修飾的普通方法?
答案是可以的。自行測試。
7、如果類中有兩個synchronized修飾的普通方法,那麼兩個執行緒是否可以通過同一個物件併發呼叫這兩個方法?
答案是不可以的。原因是執行synchronized修飾的方法必須取得物件鎖。如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
//synchronized修飾的普通方法
public synchronized void otherPrint() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
//synchronized修飾的普通方法
public synchronized void print() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
}
測試類如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
SynchronizedClass p=new SynchronizedClass();
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//呼叫synchronized修飾的靜態方法
p.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//呼叫無synchronized修飾的靜態方法
p.otherPrint();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
測試類執行結果如下:
t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......
8、如果類中有兩個synchronized修飾的靜態方法,那麼兩個執行緒是否可以併發呼叫這兩個方法?
答案是不可以的。這是因為執行synchronized修飾的靜態方法必須取得類鎖。如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
//synchronized修飾的靜態方法
public synchronized static void otherPrint() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
//synchronized修飾的靜態方法
public synchronized static void print() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
public static void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
}
測試類如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//呼叫synchronized修飾的靜態方法
SynchronizedClass.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//呼叫無synchronized修飾的靜態方法
SynchronizedClass.otherPrint();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
測試類執行結果:
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......
t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
9、如果類中一個是synchronized修飾的靜態方法,另一個是synchronized修飾的普通方法,那麼兩個執行緒是否可以併發呼叫這兩個方法?
答案是可以的。這是因為一個是物件鎖,一個是類鎖,不是同一把鎖。如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
* 測試synchronized關鍵字使用
*/
public class SynchronizedClass {
//synchronized修飾的普通方法
public synchronized void otherPrint() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
//synchronized修飾的靜態方法
public synchronized static void print() {
System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
run();
System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
}
public static void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
}
}
}
測試類如下:
package main.thread;
/**
* Created by leboop on 2018/11/18.
*/
public class SychronizedTest {
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//呼叫synchronized修飾的靜態方法
SynchronizedClass.print();
}
});
t1.setName("t1");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//呼叫無synchronized修飾的靜態方法
new SynchronizedClass().otherPrint();
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
執行結果如下:
t1執行緒開始執行......
t2執行緒開始執行......
執行緒t1正在列印i=0
執行緒t2正在列印i=0
執行緒t1正在列印i=1
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t2正在列印i=3
執行緒t1正在列印i=4
執行緒t2正在列印i=4
t2執行緒執行結束......
t1執行緒執行結束......
注意:類鎖和物件鎖不是一把鎖。每個物件都有一把鎖與之相關。