method | 註釋 |
---|---|
run() |
run() 方法是咱們建立線程時必需要實現的方法,可是實際上該方法只是一個普通方法,直接調用並無開啓線程的做用。 |
start() |
start() 方法做用爲使該線程開始執行;Java虛擬機調用該線程的 run 方法。 可是該方法只能調用一次,若是線程已經啓動將會拋出IllegalThreadStateException 異常。 |
yield() |
yield() 方法讓出CPU而且不會釋放鎖,讓當前線程變爲可運行狀態,因此CPU下一次選擇的線程仍多是當前線程。 |
wait() |
wait() 方法使得當前線程掛起,放棄CPU的同時也放棄同步資源(釋放鎖),讓其餘等待這些資源的線程能繼續執行,只有當使用notify()/notifyAll() 方法是纔會使得等待的線程被喚醒,使用此方法的前提是已經得到鎖。 |
notify()/notifyAll() |
notify()/notifyAll() 方法將喚醒當前鎖上的一個(所有)線程,須要注意的事通常都是使用的notifyAll() 方法,由於notify() 方法的喚醒是隨機的,咱們沒有辦法控制。 |
上面已經介紹了比較經常使用的api,如今咱們能夠了解一下在多線程中佔據着重要地位的鎖了。api
在上一篇文章中有提到在如今操做系統中進程是做爲資源分配的基本單位,而線程是做爲調度的基本單位,通常而言,線程本身不擁有系統資源,但它能夠訪問其隸屬進程的資源,即一個進程的代碼段、數據段及所擁有的系統資源,如已打開的文件、I/O設備等,能夠供該進程中的全部線程所共享,一旦有多個線程在操做一樣的資源就可能形成線程安全的問題。安全
在咱們熟悉的Java中存在着局部變量和類變量,其中局部變量是存放在棧幀中的,隨着方法調用而產生,方法結束就被釋放掉,而棧幀是獨屬於當前線程的,因此不會有線程安全的問題。而類變量是被存放在堆內存中,能夠被全部線程共享,因此也會存在線程安全的問題。bash
在Java中咱們見得最多的同步的方法應該就是使用synchronized
關鍵字了。實際上synchronized
就是一個互斥鎖,當一個線程運行到使用了synchronized
的代碼段時,首先檢查當前資源是否已經被其餘線程所佔用,若是已經被佔用,那麼該線程則阻塞在這裏,直到擁有資源的線程釋放鎖,其餘線程才能夠繼續申請資源。多線程
public static void test(){
synchronized (SyncDemo.class){
}
}
//編譯後的代碼
public static void test();
Code:
0: ldc #3 //將一個常量加載到棧中這裏既是class com/learn/set/mutilthread/sync/SyncDemo
2: dup //複製棧頂元素(SyncDemo.class)
3: astore_0 //將棧頂元素存儲到局部變量表
4: monitorenter //以字節碼對象(SyncDemo.class)爲鎖開始同步操做
5: aload_0 //將局部變量表slot_0入棧(SyncDemo.class)
6: monitorexit //退出同步
7: goto 15 //到這裏程序跳轉到return語句正常結束,下面代碼是異常路徑
10: astore_1
11: aload_0
12: monitorexit
13: aload_1
14: athrow
15: return
複製代碼
到這裏就差很少了,詳細的原理後面再談,這裏主要是談談synchronized
的使用。ide
在Java語言中,synchronized
關鍵字能夠用來修飾方法以及代碼塊:測試
//修飾普通方法
public synchronized void say(){
}
//修飾靜態方法
public synchronized static void fun(){
}
複製代碼
public void fun1(){
//使用當前對象爲鎖
synchronized (this){
//statement
}
}
public void fun2(){
//使用當前類字節碼對象爲鎖
synchronized (SyncDemo.class){
//statement
}
}
複製代碼
實體類:ui
public class User {
private static int age = 20;
public synchronized void say(String user) throws InterruptedException {
// synchronized (User.class){
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//當前線程休眠,判斷別的線程是否還能調用
Thread.sleep(1000);
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
// }
}
public synchronized void say1(String user) throws InterruptedException {
// synchronized (User.class){
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
Thread.sleep(1000);
age = 15;
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
// }
}
}
複製代碼
測試類:this
public class SyncTest{
private static User user1 = new User();
private static User user2 = new User();
private static class Sync1 extends Thread{
@Override
public void run() {
try {
user1.say("user1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Sync2 extends Thread{
@Override
public void run() {
try {
user2.say("user2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Sync1 sync1 = new Sync1();
Sync2 sync2 = new Sync2();
sync1.start();
sync2.start();
}
}
運行結果:
第一次運行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
第二次運行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-1:user2
20:Thread-0:user1
複製代碼
運行結果表示在普通方法上加synchronized關鍵字其實是鎖的當前對象,因此不一樣線程操做不一樣對象結果可能出現不一致。修改實體類User的say(...)方法爲靜態方法:spa
public synchronized void say(String user) throws InterruptedException {
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
Thread.sleep(1000);
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
}
複製代碼
運行結果始終按照順序來:操作系統
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2
複製代碼
說明在靜態(類)方法上加synchronized關鍵字其實是鎖的當前類的字節碼對象,由於在JVM中任何類的字節碼對象都只有一個,因此只要對該字節碼對象加鎖那麼任何對該類的操做也都是同步的。
在最初類的基礎上修改類Sync2,使得兩個線程操做統一對象:
private static class Sync2 extends Thread{
@Override
public void run() {
try {
user1.say("user2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
運行結果始終按照順序來:
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2
複製代碼
同理可測試在使用synchronized修飾代碼塊的做用,可得結果使用this
對象實際是鎖當前對象,與synchronized
修飾普通方法相似,使用User.class
字節碼對象實際是鎖User類的字節碼對象,與synchronized
修飾靜態方法相似。須要說明的事鎖代碼塊實際上並非必須使用當前類的this對象和字節碼對象,而能夠是任意的對象。而實際效果和使用當前類的對象一致。