并发编程之线程安全lock
为什么需要lock接口锁
提供了编程级别的锁接口,锁实现,可以灵活使用、扩展
lock类层次结构
自jdk1.5后,增加的JUC包下的locks包包含的类如下:
AQS
AbstractOwnableSynchronizer:提供独占模式同步的线程所有者;
AbstractQueuedSynchronizer:抽象的队列同步器;
AbstractQueuedLongSynchronizer:将AbstractQueuedSynchronizer的状态int改为long类型的抽象的队列同步器;
ReentrantLock
特性:独占锁;支持公平锁、非公平锁两种模式;可重入锁
ReentrantReadWriteLock
维护一对关联锁,一个用于读操作,一个用于写操作;读锁可以有多个线程同时持有,写锁时排他锁
适用场景:适合读取线程比写入线程多的场景,改进互斥锁的性能,比如:集合的并发线程安全性改造、缓存组件。
锁降级指的是写锁降级成为读锁。把持住当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写- >读是降级。(读->写,是不能实现的)
lock类方法
lock接口方法
- Acquires the lock 获取锁
void lock();
- Acquires the lock unless the current thread is interrupted 获取锁,除非当前线程被打断
void lockInterruptibly() throws InterruptedException;
- Acquires the lock only if it is free at the time of invocation 仅在调用时锁是空闲的情况下才获取锁(尝试获取锁)
boolean tryLock();
//带时间限制的尝试获取锁,超过指定时间无法获取锁,则继续执行
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- Releases the lock 释放锁
void unlock();
lock锁使用
lock锁使用
JUC包中的ReentrantLock类中给我们提供了使用lock锁的实例,如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}}
建议:正如上边的实例,在使用lock锁时,正确的姿势:配合try{}finally{}一块使用,保证获取锁后,在不使用锁时将锁能够及时的释放:lock.lock lock.unlock
public static void main(String[] args) {
// test1();
test2();
}
/**
*
*/
private static void test1() {
lock.lock();
System.out.println("当前线程"+Thread.currentThread().getName()+"获的锁的次数为:"+lock.getHoldCount());;
if( lock.tryLock()){
System.out.println("尝试获得锁成功");
}
System.out.println("当前线程"+Thread.currentThread().getName()+"获的锁的次数为:"+lock.getHoldCount());;
try{
System.out.println("获取了锁");
}finally {
lock.unlock();
System.out.println("当前线程"+Thread.currentThread().getName()+"释放一次锁,目前获的锁的次数为:"+lock.getHoldCount());
lock.unlock();
System.out.println("当前线程"+Thread.currentThread().getName()+"释放一次锁,目前获的锁的次数为:"+lock.getHoldCount());
}
}
public static void test2(){
lock.lock();
try {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("当前线程" + Thread.currentThread().getName() + "开始获得锁");
lock.lockInterruptibly();//子线程可以被打断,执行finally
lock.lock();//子线程不可被打断,一直阻塞等待
System.out.println("当前线程" + Thread.currentThread().getName() + "结束获得锁");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("当前线程" + Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
}
});
thread.start();
System.out.println("当前线程" + Thread.currentThread().getName() + " 开始打断interrupt子线程"+thread.getName());
thread.interrupt();
} finally {
// lock.unlock();
}
}
}
自定义锁
自定义实现Lock
public class MyLock implements Lock {
//当前锁的拥有者
private AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列
private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
@Override
public boolean tryLock() {
return owner.compareAndSet(null,Thread.currentThread());
}
@Override
public void lock() {
while (!tryLock()){
waiters.offer(Thread.currentThread());
LockSupport.park();
waiters.remove(Thread.currentThread());
}
}
public void unlock() {
if(owner.compareAndSet(Thread.currentThread(),null)){
waiters.forEach(thead -> {
LockSupport.unpark(thead);
});
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
使用自定义实现的lock锁
public class MyLockDemo {
MyLock lock = new MyLock();
public int i =0 ;
public static void main(String[] args) {
for (int i=0;i<200;i++){
test2(i+1);
}
}
public static void test2(int n){
MyLockDemo demo = new MyLockDemo();
List<Thread> list = new ArrayList<>();
for (int i=0;i<200;i++){
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
demo.add();
}
});
thread.start();
list.add(thread);
}
for (Thread thread:list){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("第 "+n+" 次:"+demo.i);
}
public void add(){
lock.lock();
try{
i++;
}finally {
lock.unlock();
}
}
}
ReentrantReadWritLock使用
Demo5_ReadWrite01的实例,是一个读和写都用一把锁,也就是共同争抢一把锁,这样效率太低了,代码如下:
/**
* 既有读,又有写时, 读也需要加锁。
* 一个写(独占),多个读(共享),多个读不能相互影响,读写之间互斥。
*/
public class Demo5_ReadWrite01 {
long i = 0;
Lock lock = new ReentrantLock();
//为什么读也要加锁???
public void read() {
lock.lock();
try {
long a = i;
System.out.println(Thread.currentThread().getName()+" 读到了值"+a);
}finally {
lock.unlock();
}
}
public void write() {
lock.lock();
try {
i++;
System.out.println(Thread.currentThread().getName()+" 修改值:"+i);
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final Demo5_ReadWrite01 demo = new Demo5_ReadWrite01();
List<Thread> list = new ArrayList<Thread>();
for (int i=0; i<=10; i++){
int n = i;
String threadName = n == 0 ? "writeThread" : "readThread"+n;
Thread th = new Thread(()->{
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 1000) {
if (n==0) {
demo.write();
}
else{
demo.read();
}
}
}, threadName);
list.add(th);
th.start();
}
for(Thread th : list) {
th.join();
}
}
}
由于读和写都通争抢一把锁,效率太低了,因此使用读写锁ReentrantReadWriteLock改进,读一把锁,写一把锁,代码Demo6_ReadWrite02,如下:
public class Demo6_ReadWrite02 {
volatile long i = 0;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//为什么读也要加锁???
public void read() {
// 获得读锁,读锁共享
rwLock.readLock().lock();
try {
long a = i;
System.out.println(Thread.currentThread().getName()+" 读到了值"+a);
}finally {
// 释放读锁
rwLock.readLock().unlock();
}
}
public void write() {
// 获取写锁,写锁排他
rwLock.writeLock().lock();
try {
i++;
System.out.println(Thread.currentThread().getName()+" 修改值:"+i);
}finally {
// 释放写锁
rwLock.writeLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final Demo5_ReadWrite01 demo = new Demo5_ReadWrite01();
List<Thread> list = new ArrayList<Thread>();
for (int i=0;i<=10; i++){
int n = i;
String threadName = n == 0 ? "writeThread" : "readThread"+n;
Thread th = new Thread(()->{
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 1000) {
if (n==0) {
demo.write();
}
else{
demo.read();
}
}
}, threadName);
list.add(th);
th.start();
}
for(Thread th : list) {
th.join();
}
}
}
ReentrantReadWritLock锁降级
ReentrantReadWritLock:读写锁包含:读锁ReadLock、写锁WriteLock
/**
* 如果缓存中存在的,则从缓存中获取;
* 如果缓存中不存在的,则从数据库中获取
*/
public class CacheDemo {
public static void main(String[] args) {
System.out.println(new CacheDemo().getData("admin"));
System.out.println(new CacheDemo().getData("admin"));
}
static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static volatile boolean isCache ;
private String getData(String key) {
String date = "";
//获取读锁
rwLock.readLock().lock();
try {
//缓存中有
if(isCache){
date = Redis.map.get(key);
}else{
//缓存中没有,从数据库获取
//..........................
//从数据库获取
//释放读锁
rwLock.readLock().unlock();
//获取写锁
rwLock.writeLock().lock();
try {
//判断缓存中是否有
if(isCache){
date = Redis.map.get(key);
}else {
date = DBService.getData();
Redis.map.put(key,date);
isCache = true;
}
rwLock.readLock().lock();//锁降级、由写锁变为读锁
}finally {
rwLock.writeLock().unlock();
}
}
return date;
}finally {
//释放读锁
rwLock.readLock().unlock();
}
}
}
class DBService{
static String getData(){
System.out.println("开始查询数据库。。。");
return "{name:张三,age:18}";
}
}
class Redis{
public static Map<String,String> map = new HashMap<>();
}
Condition
用于替代wait/notify。
Object中的wait(),notify(),notifyAll()方法是和synchronized配合使用的,可以唤醒一个或者全部(单个等待集);
Condition是需要与Lock配合使用的,提供多个等待集合,更精确的控制(底层是park/unpark机制);
同步锁的本质-排队
同步的方式:独享锁 – 单个队列窗口, 共享锁 – 多个队列窗口
抢锁的方式: 插队抢(不公平锁)、先来后到抢锁(公平锁)
没抢到锁的处理方式: 快速尝试多次(CAS自旋锁)、阻塞等待
唤醒阻塞线程的方式(叫号器):全部通知、通知下一个
AQS抽象队列同步器
提供了对资源占用、释放,线程的等待、唤醒等等接口和具体实现。
可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)
acquire、 acquireShared : 定义了资源争用的逻辑,如果没拿到,则等待。
tryAcquire、 tryAcquireShared : 实际执行占用资源的操作,如何判定一个由使用者具体去实现。
release、 releaseShared : 定义释放资源的逻辑,释放之后,通知后续节点进行争抢。
tryRelease、 tryReleaseShared: 实际执行资源释放的操作,具体的AQS使用者去实现。
资源占用流程
线程封闭
线程封闭概念
多线程中访问共享可变数据时,涉及到线程间数据同步的问题。
并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭
线程封闭实现
ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。
用法:ThreadLocal var = new ThreadLocal();
会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法
栈封闭
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
流程
[外链图片转存中…(img-YNyxQVpp-1747910139323)]
线程封闭
线程封闭概念
多线程中访问共享可变数据时,涉及到线程间数据同步的问题。
并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭
线程封闭实现
ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。
用法:ThreadLocal var = new ThreadLocal();
会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法
栈封闭
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。