synchronized是Java語言的關(guān)鍵字,可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執(zhí)行這段代碼。當兩個并發(fā)線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。然而,當一個線程訪問object的一個加鎖代碼塊時,另一個線程仍可以訪問該object中的非加鎖代碼塊。百度百科
看下面的代碼,使用
synchronized鎖定方法的方式,鎖定了method1方法,期望同一時間只有一個線程可以訪問該方法。
public class SynchronizedTest implements Runnable{
public synchronized void method1(String threadName) throws InterruptedException {
System.out.println("線程" + threadName + ":開始!");
Thread.sleep(3000);
System.out.println("線程" + threadName + ":結(jié)束!");
}
@Override
public void run() {
try {
method1(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for(int i = 0; i < 4; i++) {
SynchronizedTest st = new SynchronizedTest();
Thread t = new Thread(st);
t.start();
}
}
}
運行結(jié)果
線程Thread-0:開始!
線程Thread-2:開始!
線程Thread-1:開始!
線程Thread-0:結(jié)束!
線程Thread-2:結(jié)束!
線程Thread-1:結(jié)束!
開始和結(jié)束沒有成對兒輸出,這顯然不是我們想要的結(jié)果,下面換成synchronized鎖定代碼塊的方式。
public void method1(String threadName) throws InterruptedException {
synchronized(this) {
System.out.println("線程" + threadName + ":開始!");
Thread.sleep(3000);
System.out.println("線程" + threadName + ":結(jié)束!");
}
}
運行結(jié)果
線程Thread-0:開始!
線程Thread-1:開始!
線程Thread-2:開始!
線程Thread-0:結(jié)束!
線程Thread-2:結(jié)束!
線程Thread-1:結(jié)束!
仍然不是我們期望的結(jié)果,為什么這兩種方式都沒能達到我們的預期呢?
原因是在創(chuàng)建多個線程時,創(chuàng)建了多個實例對象,也就是說上面兩種方式只有在同一個實例的時候才會生效。
修改main方法
public static void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
for(int i = 1; i < 4; i++) {
Thread t = new Thread(st);
t.start();
}
}
運行結(jié)果
線程Thread-0方法1:開始!
線程Thread-0方法1:結(jié)束!
線程Thread-2方法1:開始!
線程Thread-2方法1:結(jié)束!
線程Thread-1方法1:開始!
線程Thread-1方法1:結(jié)束!
可以看到現(xiàn)在結(jié)果正確了,下面考慮一下不同實例的情況該如何實現(xiàn)呢?
public class SynchronizedTest implements Runnable{
public static synchronized void method1(String threadName) throws InterruptedException {
System.out.println("線程" + threadName + "開始!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "結(jié)束!");
}
@Override
public void run() {
try {
method1(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for(int i = 1; i < 4; i++) {
SynchronizedTest st = new SynchronizedTest();
Thread t = new Thread(st);
t.start();
}
}
}
運行結(jié)果
線程Thread-0:開始!
線程Thread-0:結(jié)束!
線程Thread-2:開始!
線程Thread-2:結(jié)束!
線程Thread-1:開始!
線程Thread-1:結(jié)束!
可以看到,在同步方法上加上static關(guān)鍵字就對多實例線程起作用了,那么下面再看看代碼塊的方式如何實現(xiàn)?
public void method1(String threadName) throws InterruptedException {
synchronized(SynchronizedTest.class) {
System.out.println("線程" + threadName + "開始!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "結(jié)束!");
}
}
或
public void method1(String threadName) throws InterruptedException {
synchronized("obj") {
System.out.println("線程" + threadName + "開始!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "結(jié)束!");
}
}
以上兩種寫法都是正確的,就不再展示運行結(jié)果?,F(xiàn)在對上面五種寫法做一個總結(jié):

再思考一個問題,如果有兩個
synchronized修飾的方法或代碼段,那么這兩個方法在同一時刻是否可以分別被不同的線程訪問?下面修改原來的方法,再新增一個method2:
public class SynchronizedTest implements Runnable{
public void method1(String threadName) throws InterruptedException {
synchronized(SynchronizedTest .class) {
System.out.println("線程" + threadName + "方法1:準備睡覺!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "方法1:睡覺結(jié)束!");
}
}
public void method2(String threadName) throws InterruptedException {
synchronized(SynchronizedTest .class) {
System.out.println("線程" + threadName + "方法2執(zhí)行!");
}
}
@Override
public void run() {
try {
method1(Thread.currentThread().getName());
method2(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for(int i = 1; i < 4; i++) {
SynchronizedTest st = new SynchronizedTest();
Thread t = new Thread(st);
t.start();
}
}
}
運行結(jié)果
線程Thread-0方法1:準備睡覺!
線程Thread-0方法1:睡覺結(jié)束!
線程Thread-2方法1:準備睡覺!
線程Thread-2方法1:睡覺結(jié)束!
線程Thread-1方法1:準備睡覺!
線程Thread-1方法1:睡覺結(jié)束!
線程Thread-2方法2執(zhí)行!
線程Thread-0方法2執(zhí)行!
線程Thread-1方法2執(zhí)行!
在方法1睡覺的過程中,沒有方法2的輸出,這說明在線程鎖定方法1的時候方法2同樣是被鎖定的,那么反之,非同步方法或代碼塊在其他線程沒有獲取鎖的情況下還是可以訪問的。下面去掉方法2的synchronized鎖:
public void method2(String threadName) throws InterruptedException {
System.out.println("線程" + threadName + "方法2執(zhí)行!");
}
運行結(jié)果
線程Thread-0方法1:準備睡覺!
線程Thread-0方法1:睡覺結(jié)束!
線程Thread-0方法2執(zhí)行!
線程Thread-1方法1:準備睡覺!
線程Thread-1方法1:睡覺結(jié)束!
線程Thread-2方法1:準備睡覺!
線程Thread-1方法2執(zhí)行!
線程Thread-2方法1:睡覺結(jié)束!
線程Thread-2方法2執(zhí)行!
-
彩蛋
看看下面兩種寫法是否效果一樣?
String obj = "obj";
public void method1(String threadName) throws InterruptedException {
synchronized(obj) {
System.out.println("線程" + threadName + "開始!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "結(jié)束!");
}
}
String obj = new String("obj");
public void method1(String threadName) throws InterruptedException {
synchronized(obj) {
System.out.println("線程" + threadName + "開始!");
Thread.sleep(1000);
System.out.println("線程" + threadName + "結(jié)束!");
}
}
上面驗證過第一種寫法是正確的,貼一下第二個的運行結(jié)果
線程Thread-0開始!
線程Thread-2開始!
線程Thread-1開始!
線程Thread-0結(jié)束!
線程Thread-2結(jié)束!
線程Thread-1結(jié)束!
區(qū)別就在于String s = "s";會被存在常量區(qū),而String s = new String("s");是對象會放在堆中,這樣的話第二種寫法就變成鎖定的是實例對象了,故而結(jié)果是錯的。想要了解更多關(guān)于String的知識還請搜索引擎。