Android笔记
——锁
【若对该知识点有更多想了解的,欢迎私信博主~~】
锁:Lock
一:用途
-
互斥访问
锁可以确保在任意时刻只有一个线程可以获得锁,从而保证了对共享资源的互斥访问。当一个线程获得锁后,其他线程必须等待锁被释放才能继续执行。
-
数据一致性
锁可以防止多个线程同时对共享资源进行写操作,从而保证数据的一致性。只有获得锁的线程才能对资源进行写操作,其他线程必须等待。
-
可见性
锁可以确保在释放锁之前,对共享资源的修改对其他线程可见。这防止了线程在不同的 CPU 缓存中看到不一致的数据。
二:分类
-
内置锁(synchronized)
用于在多个线程之间同步对共享资源的访问
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } }
内置锁是最常见的方式,这个案例是指当有多个线程访问increment()时,只会有一个线程访问该实例方法,其他会等待
此处介绍3种使用方式:
1. **实例方法同步 :**确保只有一个线程访问实例方法
public synchronized void synchronizedMethod() { // 同步代码块 }
2. **静态方法同步:**确保只有一个线程访问静态方法
public static synchronized void synchronizedStaticMethod() { // 同步代码块 }
3. **代码块同步:**确保只有一个线程访问该对象,其他线程继续执行下面的代码
public void someMethod() { synchronized (lockObject) { // 同步代码块 } }
-
重入锁(Reentrant Lock)
拥有显示的锁定和解锁机制
特性和优点:
1. 可重入性
同一个线程可以多次获得同一个锁
2. 公平性
可按照请求锁的先后顺序分配锁,避免线程饥饿
3. 可中断
可响应中断信号
4. 超时获取
允许指定时间内获取锁,否则视为放弃
5. 可实现多个条件变量
可创建多个Condition条件对象,用于不同条件下等待和唤醒线程(例:生产者-消费者问题)
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private static ReentrantLock lock = new ReentrantLock(); // 创建一个重入锁 private static int count = 0; public static void increment() { lock.lock(); // 获取锁 try { for (int i = 0; i < 10000; i++) { count++; } } finally { lock.unlock(); // 释放锁 } } }
该示例中,在操作count前后分别显示的加锁和解锁
-
读写锁(Read-Write Lock)
允许多个线程同时读取共享资源,但有写操作时,所有读写操作都会被阻塞
特性和优点:
1. 读锁
读锁可以并发读取资源,只要没有线程持有写锁
2. 写锁
当有一个线程持有写锁,其他线程将无法同时持有写锁和读锁
3. 公平性
可按照请求锁的先后顺序分配锁,避免线程饥饿
4. 可中断
可响应中断信号
5. 锁升级和降级
可先获取读锁再升级为写锁,也可先获取写锁再降级为读锁
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private int value = 0; private ReadWriteLock rwLock = new ReentrantReadWriteLock(); public int getValue() { rwLock.readLock().lock(); try { return value; } finally { rwLock.readLock().unlock(); } } public void setValue(int newValue) { rwLock.writeLock().lock(); try { value = newValue; } finally { rwLock.writeLock().unlock(); } } }
该示例中,getValue()会加读锁,setValue()会加写锁,在操作数值前后分别加锁解锁
-
条件锁(Condition Lock)
通过Condition条件对象实现,通常与重入锁一起使用
特性和优点:
1. 等待和通知机制
不满足时等待await(),满足时唤醒signal()、signalAll()
2. 与重入锁结合
可与重入锁一起使用
3. 公平性
可按照请求锁的先后顺序分配锁,避免线程饥饿
4. 可中断
可响应中断信号
5. 可实现多个条件变量
可创建多个Condition条件对象,用于不同条件下等待和唤醒线程(例:生产者-消费者问题)
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionLockExample { private int count = 0; private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() { lock.lock(); try { count++; condition.signalAll(); } finally { lock.unlock(); } } public void await() throws InterruptedException { lock.lock(); try { while (count < 10) { condition.await(); } } finally { lock.unlock(); } } }
该示例中,increment()只要有线程操作count就会唤醒其他线程,await()只要count小于10就会一直等待被唤醒,大于或等于10后才会继续执行后续的操作
-
信号量(Semaphore)
可控制共享资源的访问数量,避免过度竞争或资源耗尽,可用于解决生产者-消费者问题或连接池问题
特性和优点:
1. 许可数量
获取资源时必须先获取数量许可,数量为1时则变成互斥锁
2. 获取和释放许可
通过acquire()获取许可,通过release()释放许可
3. 公平性
可按照请求锁的先后顺序分配锁,避免线程饥饿
4. 可中断
可响应中断信号
import java.util.concurrent.Semaphore; public class SemaphoreExample { private Semaphore semaphore = new Semaphore(3); // 允许同时访问的线程数量为3 public void accessResource() throws InterruptedException { semaphore.acquire(); try { // 访问共享资源 } finally { semaphore.release(); } } }
该示例中,semaphore的许可数量为3,accessResource()在操作资源时会先后获取和释放许可
-
倒计时门闩(CountDownLatch)
等待一个或多个任务完成之后才会继续执行其他任务
基本用法:
1. 初始化计数
需要一个初始计数值,需要等待的线程数量
2. 等待操作
await()等待,直到计数减少到0或中断
3. 减小计数
用countDown()来减小计数
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { private CountDownLatch latch = new CountDownLatch(3); // 设置计数器为3 public void doTask() { // 执行任务 latch.countDown(); // 任务完成,计数减少 } public void awaitCompletion() throws InterruptedException { latch.await(); // 等待计数减少到0 } }
该示例中,latch的计数为3,doTask()会不断减少计数值,等到0后,awaitCompletion()里就会继续执行
-
循环屏障(CyclicBarrier)
允许一组线程等待彼此达到一个共同的屏障点,然后再一起继续执行
基本用法:
1. 初始化计数
需要一个初始计数值,需要等待的线程数量
2. 等待操作
利用await()达到屏障点,当足够线程都达到屏障点,则会释放,继续执行
特性和优点:
1. 可重用
可多次利用,每次达到节点后会自动重置,继续等待下一轮
2. 回调操作
所有线程达到循环屏障后,可执行一个可选的回调操作
import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { private CyclicBarrier barrier = new CyclicBarrier(3); // 等待三个线程 public void performTask() throws Exception { // 执行任务 barrier.await(); // 等待所有线程到达屏障 // 继续执行 } }
该示例中,barrier需要等待的线程数为3,当performTask()被执行三次,才会继续执行下面的程序
-
交换器(Exchanger)
用于两个线程到达同步点后互相交换数据并继续执行
基本用法:
1. 数据交换
调用exchange()交换数据,调用处即同步点
特性和优点:
1. 可重用
可多次利用,每次达到节点后会自动重置,继续等待下一轮
2. 可中断
可响应中断信号
import java.util.concurrent.Exchanger; public class ExchangerExample { private Exchanger<String> exchanger = new Exchanger<>(); public void exchangeData(String data) throws InterruptedException { String receivedData = exchanger.exchange(data); // 使用 receivedData 进行处理 } }
该示例中,当有两个线程调用exchangeData()这个方法时,会交换数据,然后继续执行
-
并发集合(Concurrent Collections)
提供的一组并发的数据结构
内容:
1. ConcurrentHashMap:并发读取和部分并发写入的哈希表
2. ConcurrentLinkedQueue:高并发入队和出队的队列
3. ConcurrentSkipListMap、ConcurrentSkipListSet:有序并发访问的键值表、集合
4. CopyOnWriteArrayList、CopyOnWriteArraySet:通过复制整个集合确保线程安全,读不需要锁
5. BlockingQueue:接口,支持阻塞操作的队列
import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapExample { private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); public void addToMap(String key, int value) { map.put(key, value); } }
该示例中,map里的键值是可并发访问的
-
StampedLock
除了支持读和取,还支持乐观读
基本用法:
1. 三种模式
读模式、写模式(同读写锁)、乐观读模式,并发读取时不会阻塞写操作,而是验证是否正确
2. 获取戳记
即stamp,lock.validate(stamp)来验证乐观读锁是否有效
特性和优点:
1. 适应性自旋
会根据系统的负载和竞争情况进行适应性调整
2. 可中断
可响应中断信号
import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private int value = 0; private StampedLock lock = new StampedLock(); public void updateValue(int newValue) { long stamp = lock.writeLock(); // 获取写锁 try { value = newValue; } finally { lock.unlockWrite(stamp); // 释放写锁 } } public int getValue() { long stamp = lock.tryOptimisticRead(); // 获取乐观读锁 int currentValue = value; if (!lock.validate(stamp)) { // 验证乐观读锁是否有效 stamp = lock.readLock(); // 获取悲观读锁 try { currentValue = value; } finally { lock.unlockRead(stamp); // 释放悲观读锁 } } return currentValue; } }
该示例中,updateValue()用于写操作,getValue()用于乐观读操作,会先获取乐观读锁的状态,然后赋值,验证乐观读锁状态是否正常,如果不正常,则获取悲观读锁,然后阻塞其他线程的读写操作,然后重新赋正确的值,确保数据的唯一性