美文网首页
Java并发同步锁

Java并发同步锁

作者: NengLee | 来源:发表于2020-12-29 19:53 被阅读0次

    synchronized

    如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized就是实现线程同步的关键字,可以说在并发控制中是必不可少的部分

    特性

    1. 原子性:如同事务要么全部执行,要么回滚最初,保证数据的准确性
    2. 可见性:多个线程需要访问一个资源的时候,具有该资源的状态、属性等都具有访问权限
    3. 有序性:在持锁的代码逻辑,具有先后顺序执行
    4. 可重入性:一个线程拥有了锁然而还可以重复申请锁

    锁的是什么?

    synchronized 可以修复方法、成员函数、静态方法、同时还有代码块,但是归根结底是锁什么?

    1. Class类
    2. new实体对象
    private int num = 0;
    
    @Test
    public void testMain() throws InterruptedException {
    
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
    
        thread1.start();
        thread2.start();
    
        thread1.join();
        thread2.join();
    
        System.out.println(num);
    }
    
    
    class MyRunnable implements Runnable {
        
        //锁的对象
        private synchronized void add() {
            num++;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        }
    }
    
    //  System.out.println(num);  1875
    

    如果想要执行结果是 20000;怎么改? 方法很多抓锁的本质锁谁,优化谁

    锁对象
    • 既然锁对象,那么就在多线程Thread执行同一个对象,搞定!
    MyRunnable syMyRunnable= new MyRunnable();
    
    Thread thread1 = new Thread(syMyRunnable);
    Thread thread2 = new Thread(syMyRunnable);
    
    //  System.out.println(num);  20000
    
    • 锁对象,也可以在代码块里锁一个具体的对象,
    private int num = 0;
    private Object object = new Object();
    
    @Test
    public void testMain() throws InterruptedException {
        
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
        
        //....
    }
    
    
    private void add() {
        //锁代码块中的 唯一对象Object
        synchronized (object) {
            num++;
        }
    
    }
    
    //  System.out.println(num);  20000
    
    锁类

    如果想通过锁类,那么就保证Class为static静态即可

    private static int num = 0; //静态变量
    
    @Test
    public void testMain() throws InterruptedException {
    
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
    
        thread1.start();
        thread2.start();
    
        thread1.join();
        thread2.join();
    
        System.out.println(num);
    }
    
    /**
    *静态类
    */
    static class MyRunnable implements Runnable {
        //静态函数
        private synchronized static void add() {
            num++;
        }
    
    }
    
    //  System.out.println(num);  20000
    

    可以总结:

    • 静态方法锁class类
    • 非静态方法锁对象
    • 当锁类的情况下,会把这个Class类下的所有变量、方法以及引用对象都所锁住,造成资源浪费
    • 一般可锁不被引用的对象Object
    • synchronized是悲观锁
    • 优点:使用简单、不用手动去管理
    • 弊端:太重、锁的属性对象太多、容易造成死锁

    CAS

    是英文单词Compare And Swap的缩写,翻译过来就是比较并替换

    CAS机制当中使用了3个基本操作数:内存地址原值V,旧的预期值A,要修改的新值B

    原理操作是:比较 A 与 V 是否相等, 如果比较相等,将 B 写入 V。(交换) 返回操作是否成功。

    当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。

    lock

    public interface Lock {
    
        /**
         *  获取锁  
         */
        void lock();
    
        /**
         *  如果当前线程未被中断,则获取锁,可以响应中断
         */
        void lockInterruptibly() throws InterruptedException;
    
        /**
         * 仅在调用时锁为空闲状态才获取该锁,可以响应中断
         */
        boolean tryLock();
    
        /**
         * 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
         */
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
        /**
         *  释放锁  
         */
        void unlock();
    
        /**
         *  返回绑定到此 Lock 实例的新 Condition   
         *  return:Condition接口实例 具有唤醒阻塞的线程
         */
        Condition newCondition();
    }
    

    获取锁

    • lock()
    • tryLock()
    • tryLock(long time, TimeUnit unit)
    • lockInterruptibly()
    lock()

    lock()就是用来获取锁,如果当前锁已被其他线程获取,则进入等待,采用lock()必须主动去释放锁,并且在发生异常时,不会自动释放,因此在使用时会try{},并且将释放操作放到finally

    try{
        //处理任务
    }catch(Exception ex){
    
    }finally{
        //释放锁
        lock.unlock();   
    }
    
    tryLock() && tryLock(long time, TimeUnit unit)
    • tryLock() 方法具有返回值,用来尝试获取锁,如果获取成功返回True,如果获取失败说明此时锁已经被其他线程所占用,则返回false,也就是说这个方法无论如何都会立即返回,而并不是在那里自旋。
    • tryLock(long time, TimeUnit unit) 也是具有返回值,区别是拿不到锁会等待一定的时间,在时间内还拿不到锁,就返回false,同时响应中断,如果一开始拿到锁或者是在等待时间范围类拿到锁就直接返回True
    if(lock.tryLock()) {
         try{
             //处理任务
         }catch(Exception ex){
    
         }finally{
             //释放锁
             lock.unlock();  
         } 
    }else {
        //如果不能获取锁,则直接做其他事情
    }
    
    lockInterruptibly()

    该方法比较特殊,就是当lockInterruptibly()去获取锁的时候,如果线程正在等待取锁,则这个线程能够响应中断,立即中断线程的等待状态,例如:当俩个线程同时通过lock.lockinterruptibly想获取某个锁时,此时Thread-A获取到了锁,而Thread-B只能等待,那么对线程B调用Thread-B.Lockinterruptibly就可以使得Thread-B中断等待过程。

    public void method() throws InterruptedException {
        lock.lockInterruptibly();
        try {  
         //.....
        }
        finally {
            lock.unlock();
        }  
    }
    

    释放锁

    • unlock()

    lock.unlock(); 合适的try{}地方进行释放

    绑定用于占用

    • newCondition()
    // 只有一个锁
    Lock lock = new ReentrantLock(); 
    
    //condition 进行通信管理状态
    Condition condition = lock.newCondition();
    
    

    Condition

    对于synchronized锁,线程之间的通信方式是waitnotify。对于Lock接口线程间就是通过Condition方式通信的。它比wait/notify更强大的是,它支持等待过程中不响应中断、多个等待队列和在指定时间苏醒。

    //reentrantlock对应一个AQS阻塞队列 ,其内部有个 abstract static class Sync extends AbstractQueuedSynchronizer {}
    Lock lock = new ReentrantLock();  
    
    Condition condition = lock.newCondition();
    
    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            // 释放cpu的执行资格和执行权,同时释放锁
            condition.await();
        } finally {
            //抛异常时进行释放锁
            lock.unlock();
        }
    }
    
    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            //唤醒
            condition.signal();
        } finally {
            //抛异常时进行释放锁
            lock.unlock();
        }
    }
    

    Atomic

    JDK为了防止一些基本数据以及数组对象,分别在java.util.concurrent包专门处理线程并发安全的工具类,并且包含多个原子操作,

    • 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
    • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
    • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
    • 复合变量类:AtomicMarkableReference,AtomicStampedReference

    其内部实现不是简单使用synchronized,而是更加高效的 CAS + volatie,避免大开销,提升执行效率,其中CAS比较通过直接内存指针操作获取unsafe.getAndAddInt(**this**, valueOffset, 1), Unsafe.class是大量native 操作本地方法的类,应用层一般都是通过反射机制操作,而JDK是可以直接使用,其操作不当容易操作各种问题,官方不建议普通开发者进行使用。

    volatile

    Java语言提供了一种消弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行的时候都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起排序。

    • 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更为轻量级的同步机制

    当一个变量定义了volatile之后具有俩中特性:

    1. 此变量在多线程中具有可见性(顶部的特性中有说明)
    2. 禁止指向重新序优化,也就不会和其他内存操作一起排序。俗称内存屏障

    常见作用多线程中:“一写多读”,只有唯一的线程(Thread-0)对事件的增删改,其他线程只读取用

    总结

    1. 乐观锁:CAS,基准为有对象属性可能会被锁定
    2. 悲观锁:synchronized,基准为当前代码一定都会被锁定
    3. 无锁:轻量级无锁资源,单线程独享
    4. 偏向锁:总是由同一个线程多次获得,例如main-Thread,那么此时的锁为偏向锁
    5. 轻量级锁:由偏向锁升级而来,当有第二个线程也申请该锁的资源,那么一前一后交替执行同步代码
    6. 重量级锁:当同一个时间,多个线程同时竞争资源锁,此时锁就会升级重量级锁,导致开销大执行较重

    相关文章

      网友评论

          本文标题:Java并发同步锁

          本文链接:https://www.haomeiwen.com/subject/otrqoktx.html