多執行緒學習-day-03synchronized內建鎖
執行緒基礎、執行緒之間的共享和協作
(目前會將一些概念簡單描述,一些重點的點會詳細描述)
執行緒常用方法和執行緒的狀態
start():呼叫start()方法後,使執行緒從新建狀態處於就緒狀態。
sleep():呼叫sleep()方法後,設定休眠時間,使執行緒從執行狀態處於阻塞(休眠)狀態,休眠時間到,執行緒從阻塞狀態轉變為就緒狀態。
wait():呼叫wait()方法後,使執行緒從執行狀態處於阻塞(休眠)狀態,只有通過notify()或者notifyAll()方法重新使執行緒處於就緒狀態。(後續補充notify()和notifyAll()方法)。
interrupt():呼叫interrupt()方法後,不是強制關閉執行緒,只是跟執行緒打個招呼,將執行緒的中斷標誌位置為true,執行緒是否中斷,由執行緒本身決定。
isInterrypt():執行緒中斷標誌位,true/false兩個Boolean值,用來判斷是否呼叫interrupt()方法,告訴執行緒是否中斷。
interrupted():判斷執行緒是否處於中斷狀態,並將中斷標誌位改為false。
run():執行執行緒的方法。
synchronized內建鎖
1、用處
synchronized作為執行緒同步的關鍵字,設計到鎖的概念,下面就對鎖的概念進行詳細介紹。
Java內建鎖是一個互斥鎖,這就說明最多隻有一個執行緒能夠獲得該鎖,例如兩個執行緒:執行緒A和執行緒B,如果執行緒A嘗試去獲得執行緒B的內建鎖,則執行緒A必須等待或者阻塞,直到執行緒B釋放這個鎖為止;如果執行緒B永不釋放這個鎖,則執行緒A則永遠處於等待或阻塞狀態。
Java的物件鎖和類鎖在鎖的概念上,與內建鎖幾乎是一致的,但是物件鎖和類鎖的區別是非常大的。
2、物件鎖
用synchronized修飾非靜態方法、用synchronized(this)作為同步程式碼塊、用synchronized(非this物件)的用法鎖的是物件,執行緒想要執行對應的同步程式碼,需要先獲得物件鎖。
3、類鎖
用synchronized修飾靜態方法、用synchronized(類.class)的用法鎖的是類,執行緒想要執行對應的同步程式碼,需要先獲得類鎖。
以下對synchronized關鍵字的用法,物件鎖,類鎖用實際程式碼為例子進行介紹:
1、先看一個非執行緒安全的例項,看看synchronized的用途
public class SynRun {
public static void main(String[] args) {
// 定義HasSelfNum物件
HasSelfNum hasSelfNum = new HasSelfNum();
// 定義ThreadZS多執行緒類
ThreadZS threadZS = new ThreadZS(hasSelfNum);
threadZS.start();
// 定義ThreadLS多執行緒類
ThreadLS threadLS = new ThreadLS(hasSelfNum);
threadLS.start();
}
}
// 定義一個類HasSelfNum,作為同步設定num的變化
class HasSelfNum {
// 定義一個變數num
private int num = 0;
// 定義一個類的方法addNum,並傳一個字串作為形參
public void addNum(String name) {
try {
if (name.equals("zs")) {
num = 100;
System.out.println("zs設定了num引數...");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("ls設定了num引數...");
}
// 將num引數輸出
System.out.println(name + " 設定的 num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 定義一個多執行緒類ThreadZS
class ThreadZS extends Thread {
// 定義HasSelfNum
private HasSelfNum hasSelfNum;
// 設定建構函式,將物件賦值
public ThreadZS(HasSelfNum hasSelfNum) {
super();
this.hasSelfNum = hasSelfNum;
}
// 定義run方法
@Override
public void run() {
super.run();
hasSelfNum.addNum("zs");
}
}
// 定義一個多執行緒類ThreadLS
class ThreadLS extends Thread {
// 定義HasSelfNum
private HasSelfNum hasSelfNum;
// 設定建構函式,將物件賦值
public ThreadLS(HasSelfNum hasSelfNum) {
super();
this.hasSelfNum = hasSelfNum;
}
// 定義run方法
@Override
public void run() {
super.run();
hasSelfNum.addNum("ls");
}
}
控制檯輸出結果:
zs設定了num引數...
ls設定了num引數...
ls 設定的 num = 200
zs 設定的 num = 200
看到輸出結果是非執行緒安全的。
接下來將HasSelfNum類裡面的addNum()方法加上synchronized關鍵字,其餘程式碼不變
// 定義一個類HasSelfNum,作為同步設定num的變化
class HasSelfNum {
// 定義一個變數num
private int num = 0;
// 定義一個類的方法addNum,並傳一個字串作為形參,加上了synchronized關鍵字
public synchronized void addNum(String name) {
try {
if (name.equals("zs")) {
num = 100;
System.out.println("zs設定了num引數...");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("ls設定了num引數...");
}
// 將num引數輸出
System.out.println(name + " 設定的 num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行後,控制檯輸出結果:
zs設定了num引數...
zs 設定的 num = 100
ls設定了num引數...
ls 設定的 num = 200
由此可看出,兩個執行緒訪問同一個物件中的同步方法一定是執行緒安全的。因為物件的方法加上了synchronized關鍵字成為同步方法,所以先把zs打印出來,再把ls打印出來。
2、不同執行緒呼叫不同物件
只修改了main方法裡面的寫法,其餘程式碼不變
public static void main(String[] args) {
// 定義2個HasSelfNum物件
HasSelfNum hasSelfNum1 = new HasSelfNum();
HasSelfNum hasSelfNum2 = new HasSelfNum();
// 定義ThreadZS多執行緒類,用hasSelfNum1物件的方法
ThreadZS threadZS = new ThreadZS(hasSelfNum1);
threadZS.start();
// 定義ThreadLS多執行緒類,用hasSelfNum2物件的方法
ThreadLS threadLS = new ThreadLS(hasSelfNum2);
threadLS.start();
}
控制檯輸出結果:
zs設定了num引數...
ls設定了num引數...
ls 設定的 num = 200
zs 設定的 num = 100
可以看出輸出結果是非同步的,因為執行緒threadZS獲得的是hasSelfNum1的物件鎖,threadLS獲得的是hasSelfNum2的物件鎖,他們沒有獲得同一個物件鎖,沒有出現競爭情況,因此是非同步的結果
3、運用synchronized(tihs)同步程式碼塊
public class SynRun1 {
public static void main(String[] args) {
// 初始化ShowTime物件
ShowTime showTime = new ShowTime();
// 初始化ThreadA多線層物件
ThreadA threadA = new ThreadA(showTime);
threadA.start();
// 初始化TreadB多執行緒物件
ThreadB threadB = new ThreadB(showTime);
threadB.start();
}
}
// 定義一個類ShowTime
class ShowTime {
// 定義一個顯式時間方法showTime
public void showTime() {
// 利用synchronized(this)同步程式碼塊作為同步方法
try {
synchronized (this) {
System.out.println("一個程序開始執行,執行時間為:" + System.currentTimeMillis());
// 設定休眠時間
Thread.sleep(2000);
System.out.println("這個程序執行結束,結束時間為:" + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 定義一個執行緒A
class ThreadA extends Thread {
// 定義ShowTime物件
private ShowTime showTime;
// 定義有餐建構函式
public ThreadA(ShowTime showTime) {
super();
this.showTime = showTime;
}
// 定義run方法
@Override
public void run() {
super.run();
showTime.showTime();
}
}
// 定義一個執行緒B
class ThreadB extends Thread {
// 定義ShowTime物件
private ShowTime showTime;
// 定義有餐建構函式
public ThreadB(ShowTime showTime) {
super();
this.showTime = showTime;
}
// 定義run方法
@Override
public void run() {
super.run();
showTime.showTime();
}
}
控制檯輸出結果為:
一個程序開始執行,執行時間為:1539525103617
這個程序執行結束,結束時間為:1539525105619
一個程序開始執行,執行時間為:1539525105619
這個程序執行結束,結束時間為:1539525107619
結果顯示也是同步的,執行緒獲取的是synchronized(this){}括號裡面的物件例項的物件鎖。
4、運用synchronized(非this物件)
public class SynRun1 {
public static void main(String[] args) {
ShowInfo service = new ShowInfo("Cansluck");
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
class ShowInfo {
String info = new String();
public ShowInfo(String info) {
this.info = info;
}
public void showInfo() {
try {
synchronized (info) {
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入同步塊");
Thread.sleep(3000);
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開同步塊");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private ShowInfo showInfo;
public ThreadA(ShowInfo showInfo) {
super();
this.showInfo = showInfo;
}
@Override
public void run() {
showInfo.showInfo();
}
}
class ThreadB extends Thread {
private ShowInfo showInfo;
public ThreadB(ShowInfo showInfo) {
super();
this.showInfo = showInfo;
}
@Override
public void run() {
showInfo.showInfo();
}
}
控制檯輸出結果:
執行緒名稱為:A在1539525475324進入同步塊
執行緒名稱為:A在1539525478325離開同步塊
執行緒名稱為:B在1539525478325進入同步塊
執行緒名稱為:B在1539525481325離開同步塊
這裡執行緒爭奪的是info的物件鎖,兩個執行緒有競爭同一物件鎖的關係,出現同步
5、靜態synchronized同步方法
public class SynRun {
public static void main(String[] args) {
ThreadAA a = new ThreadAA();
a.setName("A");
a.start();
ThreadBB b = new ThreadBB();
b.setName("B");
b.start();
}
}
class Service {
synchronized public static void printA() {
try {
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
Thread.sleep(3000);
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void printB() {
System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
}
}
class ThreadAA extends Thread {
@Override
public void run() {
Service.printA();
}
}
class ThreadBB extends Thread {
@Override
public void run() {
Service.printB();
}
}
控制檯輸出結果:
執行緒名稱為:B在1539525627704進入printB
執行緒名稱為:B在1539525627704離開printB
執行緒名稱為:A在1539525627704進入printA
執行緒名稱為:A在1539525630705離開printA
兩個執行緒在爭奪同一個類鎖,因此同步
6、運用synchronized (類.class)
public class SynRun {
public static void main(String[] args) {
ThreadAA a = new ThreadAA();
a.setName("A");
a.start();
ThreadBB b = new ThreadBB();
b.setName("B");
b.start();
}
}
class Service {
public static void printA() {
synchronized (Service.class) {
try {
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
Thread.sleep(3000);
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printB() {
synchronized (Service.class) {
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
System.out.println(
"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
}
}
}
class ThreadAA extends Thread {
@Override
public void run() {
Service.printA();
}
}
class ThreadBB extends Thread {
@Override
public void run() {
Service.printB();
}
}
控制檯輸出結果:
執行緒名稱為:A在1539525736333進入printA
執行緒名稱為:A在1539525739334離開printA
執行緒名稱為:B在1539525739334進入printB
執行緒名稱為:B在1539525739334離開printB
兩個執行緒依舊在爭奪同一個類鎖,因此同步
需要特別說明:對於同一個類A,執行緒1爭奪A物件例項的物件鎖,執行緒2爭奪類A的類鎖,這兩者不存在競爭關係。物件鎖和類鎖互互不干預
靜態方法則一定會同步,非靜態方法需在單例模式才生效,但是也不能都用靜態同步方法,總之用得不好可能會給效能帶來極大的影響。另外,有必要說一下的是Spring的bean預設是單例的。
物件鎖:鎖的是類的物件例項。
類鎖 :鎖的是每個類的的Class物件,每個類的的Class物件在一個虛擬機器中只有一個,所以類鎖也只有一個。
來自享學IT教育課後總結。