💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 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()`释放许可证,表示池中多了一个可用资源,可以来访问该资源