線程基礎、線程之間的共享和協作
(目前會將一些概念簡單描述,一些重點的點會詳細描述)
線程常用方法和線程的狀態
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教育課後總結。