Java专题十三:线程安全与同步
Posted 黄先生的日志
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java专题十三:线程安全与同步相关的知识,希望对你有一定的参考价值。
Java专题十三(2):线程安全与同步
多个线程访问共享资源和可变资源时,由于线程执行的随机性,可能导致程序出现错误的结果
假设我们要实现一个视频网站在线人数统计功能,在每个客户端登录网站时,统计在线人数,通常用一个变量count代表人数,用户上线后,count++
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关键字
- 方法同步
public synchronized void login(){
count++;
}
- 代码块同步
静态方法内代码:synchronized(Online.class)
非静态方法内代码:synchronized(this)
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
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()
释放许可证,表示池中多了一个可用资源,可以来访问该资源
- 判断是否有可用空间,调用
以上是关于Java专题十三:线程安全与同步的主要内容,如果未能解决你的问题,请参考以下文章
阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第3节 线程同步机制_4_解决线程安全问题_同步代码块