## Java專題十三(2):線程安全與同步
[TOC]
多個線程訪問共享資源和可變資源時,由于線程執行的隨機性,可能導致程序出現錯誤的結果
> 假設我們要實現一個視頻網站在線人數統計功能,在每個客戶端登錄網站時,統計在線人數,通常用一個變量count代表人數,用戶上線后,count++
```java
class Online{
int count;
public Online(){
this.count = 0;
}
public void login(){
count++;
}
}
```
假設目前在線人數count是10,甲登錄網站,網站后臺讀取到count值為10(count++分為三步:讀取-修改-寫入),還沒來得及修改,這時乙也在登錄,后臺讀取到count值也為10,最終甲、乙登錄完成后,count變為了11,正確結果本來應該是12的
### 原子變量
java.util.concurrent.atomic包中有很多原子變量,用于對數據進行原子操作
如對于上面的問題,可以用AtomicInteger原子變量的incrementAndGet()來實現正確操作
### synchronized關鍵字
- 方法同步
```java
public synchronized void login(){
count++;
}
```
- 代碼塊同步
靜態方法內代碼:`synchronized(Online.class)`
非靜態方法內代碼:`synchronized(this)`
```java
public void login(){
synchronized(this){
count++;
}
}
```
### 鎖機制
位于`java.util.concurrent.locks`包中
~~~
public interface Lock {
void lock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
~~~
| 鎖 | 說明 |
| --- | --- |
| ReentrantLock | 可重入互斥鎖 |
| ReentrantReadWriteLock | 可重入讀寫鎖 |
- ReentrantLock
~~~
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}}
~~~
- ReentrantReadWriteLock
```java
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}}
```
#### 一些鎖的概念
可重入鎖:同一線程在方法獲取鎖的時候,在進入前面方法內部調用其它方法會自動獲取鎖
公平鎖:按照線程鎖申請后順序來獲取鎖
非公平鎖:不一定按照線程鎖申請先后順序來獲取鎖
樂觀鎖:樂觀地認為其他人讀數據時都不會修改數據,不會上鎖
悲觀鎖:悲觀地認為其他人讀數據時都會修改數據,會上鎖,別人只能等待它釋放鎖
共享鎖:同一時刻可以被多個線程擁有
獨占鎖:同一時刻只能被一個線程擁有
### 計數信號量
`java.util.concurrent.Semaphore`
通常一個信號量維持著一個許可證的集合,`acquire`方法會申請許可證`permit`,讓線程阻塞直到許可證是空的,而`release`方法會釋放一個許可證
> 假設現在有一個類使用信號量去實現資源池,生產者消費者模式線程同步
~~~
public class Pool<E> {
private final E[] items;
private final Semaphore availableItems;
private final Semaphore availableSpaces;
private int putPosition = 0, takePosition = 0;
Pool(int capacity) {
availableItems = new Semaphore(0);
availableSpaces = new Semaphore(capacity);
items = (E[]) new Object[capacity];
}
boolean isEmpty(){
return availableItems.availablePermits() == 0;
}
boolean isFull(){
return availableSpaces.availablePermits() == 0;
}
public void put(E x) throws InterruptedException {
availableSpaces.acquire();
doInsert(x);
availableItems.release();
}
public E take() throws InterruptedException {
availableItems.acquire();
E item = doExtract();
availableSpaces.release();
return item;
}
private synchronized void doInsert(E x) {
int i = putPosition;
items[i] = x;
putPosition = (++i == items.length) ? 0 : i;
}
private synchronized E doExtract() {
int i = takePosition;
E x = items[i];
items[i] = null;
takePosition = (++i == items.length) ? 0 : i;
return x;
}
}
~~~
首先定義2個信號量`Semaphore`:
- `availableItems`代表可用資源數,數值初始化為`0`
- `availableSpaces`可用空間數,數值初始化為`capacity`
生產消費操作實現方法:
- 從池中取出資源(`take`):
- 判斷是否有可用資源,調用`availableItems.acquire()`查詢`availableItems`許可證,該方法會阻塞直到池中有可用資源
- 存入資源(`doExtract方法`)
- 釋放`availableSpaces.release()`釋放許可證,表示池中多了一個可用的空間,可以用來存放新的資源
- 放入資源至池中(`put`):
- 判斷是否有可用空間,調用`availableSpaces.acquire()`查詢`availableSpaces`許可證,該方法會阻塞直到池中有可用空間
- 取出資源(`doInsert方法`)
- 釋放`availableItems.release()`釋放許可證,表示池中多了一個可用資源,可以來訪問該資源
- JavaCook
- Java專題零:類的繼承
- Java專題一:數據類型
- Java專題二:相等與比較
- Java專題三:集合
- Java專題四:異常
- Java專題五:遍歷與迭代
- Java專題六:運算符
- Java專題七:正則表達式
- Java專題八:泛型
- Java專題九:反射
- Java專題九(1):反射
- Java專題九(2):動態代理
- Java專題十:日期與時間
- Java專題十一:IO與NIO
- Java專題十一(1):IO
- Java專題十一(2):NIO
- Java專題十二:網絡
- Java專題十三:并發編程
- Java專題十三(1):線程與線程池
- Java專題十三(2):線程安全與同步
- Java專題十三(3):內存模型、volatile、ThreadLocal
- Java專題十四:JDBC
- Java專題十五:日志
- Java專題十六:定時任務
- Java專題十七:JavaMail
- Java專題十八:注解
- Java專題十九:淺拷貝與深拷貝
- Java專題二十:設計模式
- Java專題二十一:序列化與反序列化
- 附加專題一:MySQL
- MySQL專題零:簡介
- MySQL專題一:安裝與連接
- MySQL專題二:DDL與DML語法
- MySQL專題三:工作原理
- MySQL專題四:InnoDB存儲引擎
- MySQL專題五:sql優化
- MySQL專題六:數據類型
- 附加專題二:Mybatis
- Mybatis專題零:簡介
- Mybatis專題一:配置文件
- Mybatis專題二:映射文件
- Mybatis專題三:動態SQL
- Mybatis專題四:源碼解析
- 附加專題三:Web編程
- Web專題零:HTTP協議
- Web專題一:Servlet
- Web專題二:Cookie與Session
- 附加專題四:Redis
- Redis專題一:數據類型
- Redis專題二:事務
- Redis專題三:key的過期
- Redis專題四:消息隊列
- Redis專題五:持久化