您好,登錄后才能下訂單哦!
synchronized關鍵字是java并發編程中常使用的同步鎖,用于鎖住方法或者代碼塊,鎖代碼塊時可以是synchronized(this){}、synchronized(Object){}、synchronized(類class){}。
當鎖住的內容執行完或者在執行過程中拋出異常,才會自動釋放鎖。如果想手動釋放鎖,需要調用鎖住的對象的wait()方法釋放掉鎖并且置于等待狀態,切換到其他線程運行,而notify()方法只是喚醒一個調用了該對象wait()方法的其他線程,但不會釋放鎖,選擇順序也不由代碼控制,由虛擬機實現。因此,對象的wait()、notify()、notifyAll()方法只能是配合synchronized關鍵字使用的,來完成線程間的調度。
其中鎖住方法等同于synchronized(this){方法的所有代碼作為代碼塊},如下:
public synchronized void test() {
...
}
等同于
public void test() {
synchronized (this) {
...
}
}
上面的例子鎖住的是該類的對象,如果鎖住的是個靜態的方法,我們知道靜態方法是屬于類的而不屬于對象的,所以,synchronized修飾的靜態方法鎖定的是這個類的所有對象,即就算是兩個實例對象,只要他們都是這個類的,那都會鎖住。
public synchronized static void test() {
...
}
等同于
public static void test() {
synchronized (所在類.class) {
...
}
}
無論是鎖方法還是鎖代碼塊,無論鎖代碼塊時的參考對象是什么,只要記住一個原則就一目了然了,那就是當參考對象相同時,同步鎖才起作用,否則鎖不會互斥,可以并發執行。
synchronized(this)表示當前類的對象實例相同時鎖起作用,synchronized(Object)表示該Object對象相同時鎖起作用,synchronized(類class)表示當都是該class類時鎖起作用。
舉一個簡單的例子:
public class TestController {
public class Task implements Runnable{
private String str;
Task(String str){
this.str=str;
}
@Override
public void run() {
synchronized (str) {
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str);
}
}
}
public static void main(String[] args) throws InterruptedException {
TestController testController = new TestController();
Thread thread1 = new Thread(testController.new Task("1"));
Thread thread2 = new Thread(testController.new Task("1"));
thread1.start();
thread2.start();
}
}
上述代碼,參照對象str都是"1",在java中,String字符串如果通過this.str="1"這樣的方式賦值就等同于str=String.valueOf("1"),如果字符串"1"之前已經初始化過,那就會直接拿之前的,所以是同一個對象。根據上面介紹的原則,那鎖就會起作用,所以結果是3秒之后輸出1,再過3秒再輸出1。
如果把thread2改成
Thread thread2 = new Thread(testController.new Task("2"));
這時參考對象一個是"1",另一個是"2",不是同一個對象,所以鎖不會互斥,就不會起作用,所以結果是3秒后幾乎同時輸出1和2。
以上都是多個線程同時調用同一個方法,如果調用不同的方法呢?
public class Test{
public synchronized void m1(){
System.out.println("m1 running...");
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1 end");
}
public synchronized void m2(){
System.out.println("m2 running...");
System.out.println("m2 end");
}
public static void main(String[] args) {
Test test = new Test();
new Thread(new Runnable() {
@Override
public void run() {
test.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.m2();
}
}).start();
}
}
上面代碼的輸出結果是:
m1 running...
//過3秒
m1 end
m2 running...
m2 end
上面就說過synchronized修飾在方法上等同于synchronized(this){方法的所有代碼作為代碼塊},而this就代表是對象,也就是說,第一個Thread獲得的是test對象的鎖,因為對象都是同一個test,所以第二個Thread無法獲取到鎖,而被阻塞。
把上面的例子改造成如下:
private String str = "1";
public void m1(){
synchronized(str){
System.out.println("m1 running...");
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1 end");
}
}
public void m2(){
synchronized(str){
System.out.println("m2 running...");
System.out.println("m2 end");
}
}無錫婦科醫院排名 http://www.csfk0731.com/
第一個Thread調用m1()時獲取到的是對象str的鎖,第二個Thread調用m2()時也需要獲取對象str的鎖,而且因為是同一個Test對象,所以兩個str也是同一個對象,所以第二個Thread會因為獲取不到鎖而被阻塞,輸出結果和之前的例子一樣。
如果再把上面的例子改造成如下:
public class M1 {
public void m(String str){
synchronized (str) {
System.out.println("m1 runing");
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1 end");
}
}
}
public class M2 {
public void m(String str){
synchronized (str) {
System.out.println("m2 runing");
System.out.println("m2 end");
}
}
}
public class Test {
public static void main(String[] args) {
String str = "1";
new Thread(new Runnable() {
@Override
public void run() {
new M1().m(str);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
new M2().m(str);
}
}).start();
}
}
這次調用的方法在兩個類里面,但是結果和之前的兩個例子是一樣的,因為鎖住的都是傳進來的str對象,同一個對象只有一把鎖,第一個Thread拿了,第二個Thread就只能等待。
總結:
A. 無論synchronized關鍵字加在方法上還是對象上,如果它作用的對象是非靜態的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。
B. 每個對象只有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以運行它所控制的那段代碼。
C. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。