并发编程之线程安全lock

并发编程之线程安全lock

为什么需要lock接口锁

提供了编程级别的锁接口,锁实现,可以灵活使用、扩展

lock类层次结构

自jdk1.5后,增加的JUC包下的locks包包含的类如下:

image-20220124125015818

image-20220127143919642

image-20210413182850908

AQS

AbstractOwnableSynchronizer:提供独占模式同步的线程所有者;

AbstractQueuedSynchronizer:抽象的队列同步器;

AbstractQueuedLongSynchronizer:将AbstractQueuedSynchronizer的状态int改为long类型的抽象的队列同步器;

ReentrantLock

特性:独占锁;支持公平锁、非公平锁两种模式;可重入锁

ReentrantReadWriteLock

维护一对关联锁,一个用于读操作,一个用于写操作;读锁可以有多个线程同时持有,写锁时排他锁

适用场景:适合读取线程比写入线程多的场景,改进互斥锁的性能,比如:集合的并发线程安全性改造缓存组件

锁降级指的是写锁降级成为读锁。把持住当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写- >读是降级。(读->写,是不能实现的)

lock类方法

lock接口方法

  1. Acquires the lock 获取锁
void lock();
  1. Acquires the lock unless the current thread is interrupted 获取锁,除非当前线程被打断
void lockInterruptibly() throws InterruptedException;
  1. Acquires the lock only if it is free at the time of invocation 仅在调用时锁是空闲的情况下才获取锁(尝试获取锁)
boolean tryLock();
//带时间限制的尝试获取锁,超过指定时间无法获取锁,则继续执行
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  1. 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机制);

image-20231228070118732

同步锁的本质-排队

同步的方式:独享锁 – 单个队列窗口, 共享锁 – 多个队列窗口

抢锁的方式: 插队抢(不公平锁)、先来后到抢锁(公平锁)

没抢到锁的处理方式: 快速尝试多次(CAS自旋锁)、阻塞等待

唤醒阻塞线程的方式(叫号器):全部通知、通知下一个

AQS抽象队列同步器

提供了对资源占用、释放,线程的等待、唤醒等等接口和具体实现。

可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)

image-20231228065907203

acquire、 acquireShared : 定义了资源争用的逻辑,如果没拿到,则等待。

tryAcquire、 tryAcquireShared : 实际执行占用资源的操作,如何判定一个由使用者具体去实现。

release、 releaseShared : 定义释放资源的逻辑,释放之后,通知后续节点进行争抢。

tryRelease、 tryReleaseShared: 实际执行资源释放的操作,具体的AQS使用者去实现。

资源占用流程

image-20231228070017624

线程封闭

线程封闭概念

多线程中访问共享可变数据时,涉及到线程间数据同步的问题。

并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

线程封闭实现

ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。

用法:ThreadLocal var = new ThreadLocal();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法

栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

流程

[外链图片转存中…(img-YNyxQVpp-1747910139323)]

线程封闭

线程封闭概念

多线程中访问共享可变数据时,涉及到线程间数据同步的问题。

并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

线程封闭实现

ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。

用法:ThreadLocal var = new ThreadLocal();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法

栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值