線程互斥(線程安全)
synchronized 簡介
synchronized
是 Java 內(nèi)建的同步機制,所以也有人稱其為 Intrinsic Locking唤崭,它提供了互斥的語義和可見性棕孙,當一個線程已經(jīng)獲取當前鎖時舔亭,其他試圖獲取的線程只能等待或者阻塞在那里synchronized
是 Java 中最為常用的同步方法之一,實現(xiàn)比較簡單蟀俊,代碼簡潔钦铺,可讀性和維護性較好在JDK早期版本中,性能并不好肢预,只適合鎖競爭不是特別激烈的場合矛洞。目前隨著JVM進步得到很好的優(yōu)化,性能與重入鎖(ReentrantLock)差距縮小
JDK源碼中
synchronized
的使用也有很多烫映,如同步容器Hashtable
, 同步包裝器(Synchronized Wrapper)沼本,我們可以調(diào)用 Collections 工具類提供的包裝方法,來獲取一個同步的包裝容器(如 Collections.synchronizedMap)但是它們都是利用非常粗粒度的同步方式锭沟,在高并發(fā)情況下抽兆,性能比較低下更好的選擇是使用并發(fā)包(JUC)提供的線程安全容器,這些容器基本是使用重入鎖(ReentrantLock)實現(xiàn)族淮,
synchronized
和ReentrantLock
的比較辫红,見重入鎖(ReentrantLock)博文
synchronized 用法
- 鎖定對象:一定要同一個對象
-
synchronized
鎖定非靜態(tài)方法,這個等同于把方法全部語句用synchronized
塊包起來祝辣。一個方法建議一個synchronized
贴妻,不然容易產(chǎn)生死鎖
private synchronized void get(String name) {}
-
synchronized
鎖定業(yè)務類對象。一般使用synchronized(this)
private void get2(String name) {
int len = name.length();
synchronized (this) {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
-
synchronized
鎖定同步塊蝙斜。相比較與鎖定對象揍瑟,鎖定塊更加精確,減少了鎖范圍乍炉,效率更高绢片。
private void get3(String name) {
...
synchronized (name) {
}
...
}
- 鎖類:鎖定字節(jié)碼
- 鎖靜態(tài)方法:等價與鎖定當前 Class 上
public synchronized static void get5(String name) {}
- 鎖 Class 對象上滤馍。直接顯示鎖定字節(jié)碼,如
public static void get4(String name) {
int len = name.length();
synchronized (DataObject.class) {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
線程同步(多線程復雜交互)
方法簡介
synchronized
: 保證線程安全底循,線程互斥wait
: 可以讓線程等待當前對象上的通知(notify
被調(diào)用)巢株,在wait
的過程中,線程會釋放對象鎖熙涤,供其他線程使用阁苞。當接收到對象上的通知后(notify
被調(diào)用),就能重新獲取對象的獨占鎖祠挫,并且繼續(xù)運行notify
:可以喚醒一個等待在當前對象上的線程那槽。如果有多個線程等待,講隨機選擇一個
示例代碼
- 兩個線程:子線程循環(huán)3次等舔,主線程循環(huán)5次骚灸,然后子線程又循環(huán)3次,主線程5次慌植,如此一共循環(huán)2次
-
synchronized
甚牲、wait
、notify
操作的必須是同一個對象
public class ThreadCommunication {
public static void main(String[] args) {
Data data = new Data();
new Thread(new MainThread(data), "thead 0 ").start();
new Thread(new SubThread(data), "thead 1 ").start();
}
/**
* 主線程
*/
static class MainThread implements Runnable {
private Data data;
public MainThread(Data data) {
this.data = data;
}
@Override
public void run() {
try {
for (int i = 0; i < 2; i++) {
data.mainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 子線程
*/
static class SubThread implements Runnable {
private Data data;
public SubThread(Data data) {
this.data = data;
}
@Override
public void run() {
try {
for (int i = 0; i < 2; i++) {
data.subMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 業(yè)務對象
*/
static class Data {
/**
* 默認子線程運行
*/
private volatile boolean isSubRunnable = true;
synchronized void mainMethod() throws InterruptedException {
// 主線程先等待
while (isSubRunnable) {
this.wait();
}
for (int i = 0; i < 5; i++) {
System.out.println("Main線程運行次數(shù) : " + i);
}
isSubRunnable = true;
this.notify();
}
synchronized void subMethod() throws InterruptedException {
// 一開始是true,先子線程運行
while (!isSubRunnable) {
this.wait();
}
for (int i = 0; i < 3; i++) {
System.out.println("Sub線程運行次數(shù) : " + i);
}
isSubRunnable = false;
this.notify();//喚醒
}
}
}
- 線程同步: 簡單實現(xiàn)一個阻塞隊列
public class CustomBlockQueue {
private List<Object> list = new ArrayList<>();
public synchronized Object pop() throws InterruptedException {
// 如果隊列為空蝶柿,等待
while (list.isEmpty()) {
this.wait();
}
if (list.size() > 0) {
// 隊列不為空丈钙,返回第一個對象
return list.remove(0);
} else {
return null;
}
}
public synchronized void put(Object object) {
// 添加到隊列當中
list.add(object);
// 通知一個 pop()方法,可以取數(shù)據(jù)
this.notify();
}
}
synchronized 總結(jié)
- 代碼簡潔交汤,線程安全雏赦,性能靠譜,功能沒有
ReentrantLock
豐富 - 鎖對象芙扎、鎖代碼塊喉誊、鎖class類三種用法
-
synchronized
加鎖的方法之間也是互斥的(隊列),原因在于鎖定的是同一個對象 - 只能保證線程安全纵顾,無法控制復雜邏輯的多線程交互,如需實現(xiàn)多線程交互栋盹,需要配合使用Object對象的
wait
施逾、notify
方法