## 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专题五:持久化