zoukankan      html  css  js  c++  java
  • 锁的总结

    1.大纲

      Lock接口

      锁的分类

      乐观锁和悲观锁

      可重入锁与非可重入锁

      公平锁与非公平锁

      共享锁与排它锁

      自旋锁与阻塞锁

      可中断锁

      锁优化

    一:Lock接口

    1.锁

      是一种工具,用于控制对共享资源的访问

      Lock和synchronized,是常见的锁,都可以达到线程安全的目的

      Lock最常见的实现类是ReenTrantLock

      

    2.为啥用Lock

      synchronized不够用

        效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在尝试获得锁的线程

        不够灵活:加锁与释放锁单一,每个锁仅有单一的条件

        无法知道是否成功获取锁

    3.Lock的主要方法

      lock()

      tryLock()

      tryLock(long time, TimeUnit unit)

      lockInterruptibly()

    4.lock

      获取最普通的获取锁,如果被其他线程获取,则进行等待

      不会像synchronized一样在异常的时候自动释放锁

      lock方法不能被中断,一旦陷入死锁,lock就会永久等待

    package com.jun.juc.lock.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Lock必须手动释放锁
     */
    public class MustUnLock {
        private static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            lock.lock();
            try{
                //
                System.out.println(Thread.currentThread().getName()+"-run");
            }finally {
                lock.unlock();
            }
        }
    }
    

      

    5.tryLock

      用来尝试获取锁,如果被其他线程占用,则获取成功,返回true,否则返回false,代表获取锁失败

      功能比lock强大了,可以根据是否获取到锁,决定后续程序的行为

      立刻返回

    6.tryLock(long time, TimeUnit unit)

      超时就放弃

      可以避免死锁

    package com.jun.juc.lock.lock;
    
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * tryLock避免死锁
     */
    public class TryLockDeadLock implements Runnable {
        int flag = 1;
    
        static Lock lock1 = new ReentrantLock();
        static Lock lock2 = new ReentrantLock();
    
        public static void main(String[] args) {
            TryLockDeadLock tryLockDeadLock1 = new TryLockDeadLock();
            TryLockDeadLock tryLockDeadLock2 = new TryLockDeadLock();
            tryLockDeadLock1.flag = 1;
            tryLockDeadLock2.flag = 0;
            new Thread(tryLockDeadLock1).start();
            new Thread(tryLockDeadLock2).start();
        }
    
        @Override
        public void run() {
            if (flag == 1) {
                try {
                    for (int i = 0; i < 100; i++) {
                        if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                            try {
                                System.out.println("1获取了lock1");
                                Thread.sleep(new Random().nextInt(1000));
                                // 获取第二把锁
                                if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                                    try {
                                        System.out.println("1获取了lock2");
                                        System.out.println("1成功获取两把锁");
                                        break;
                                    } finally {
                                        lock2.unlock();
                                        Thread.sleep(new Random().nextInt(1000));
                                    }
                                } else {
                                    System.out.println("1获取lock2失败,在重试");
                                }
                            } finally {
                                // 因为上面获取到了锁,需要释放
                                lock1.unlock();
                                Thread.sleep(new Random().nextInt(1000));
                            }
                        } else {
                            System.out.println("1获取lock1失败,在重试");
                        }
                    }
                } catch (Exception e) {
                    // 防止800ms内被中断
                    e.printStackTrace();
                }
            }
    
            if (flag == 0) {
                try {
                    for (int i = 0; i < 100; i++) {
                        if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                            try {
                                System.out.println("2获取了lock2");
                                Thread.sleep(new Random().nextInt(1000));
                                // 获取第二把锁
                                if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                                    try {
                                        System.out.println("2获取了lock1");
                                        System.out.println("2成功获取两把锁");
                                        break;
                                    } finally {
                                        lock1.unlock();
                                        Thread.sleep(new Random().nextInt(1000));
                                    }
                                } else {
                                    System.out.println("2获取lock1失败,在重试");
                                }
                            } finally {
                                // 因为上面获取到了锁,需要释放
                                lock2.unlock();
                                Thread.sleep(new Random().nextInt(1000));
                            }
                        } else {
                            System.out.println("2获取lock2失败,在重试");
                        }
                    }
                } catch (Exception e) {
                    // 防止800ms内被中断
                    e.printStackTrace();
                }
            }
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:57057', transport: 'socket'
    1获取了lock1
    2获取了lock2
    1获取lock2失败,在重试
    2获取了lock1
    2成功获取两把锁
    1获取了lock1
    1获取了lock2
    1成功获取两把锁
    Disconnected from the target VM, address: '127.0.0.1:57057', transport: 'socket'
    

      

    7.lockInterruptipy

      把超时时间设置为无限,在过程中,线程可以被中断

    package com.jun.juc.lock.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockInterruptibly implements Runnable {
        private Lock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            LockInterruptibly lockInterruptibly = new LockInterruptibly();
            Thread thread = new Thread(lockInterruptibly);
            Thread thread1 = new Thread(lockInterruptibly);
            thread.start();
            thread1.start();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"尝试获取锁");
            try{
                lock.lockInterruptibly();
                try {
                    System.out.println(Thread.currentThread().getName()+"拿到了锁");
                    Thread.sleep(5000);
                }catch (Exception e){
                    System.out.println("睡眠时间被打断");
                }finally {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getName()+"释放了锁");
                }
            }catch (Exception e){
                System.out.println("等待锁时被打断");
                e.printStackTrace();
            }
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:62438', transport: 'socket'
    Thread-0尝试获取锁
    Thread-1尝试获取锁
    Thread-0拿到了锁
    睡眠时间被打断
    Thread-0释放了锁
    Thread-1拿到了锁
    Disconnected from the target VM, address: '127.0.0.1:62438', transport: 'socket'
    Thread-1释放了锁
    
    Process finished with exit code 0
    

      

    8.可见性

      happens-before

      lock拥有可见性保障的

      

      

      

    二:锁的分类

    1.分类

      

    三:乐观锁与悲观锁

     1.悲观锁的劣势

      也叫互斥同步锁

      劣势:

        阻塞和唤醒带来的性能劣势

        永久阻塞,如果持有锁的线程被永久阻塞,那么等待该线程释放的线程,永远都得不到执行

        优先级反转

      

    2.悲观锁

      

    3.乐观锁

      

       

       典型的案例就是原子类,并发容器等

    4.乐观锁例子

    package com.jun.juc.lock.lock;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class PessimismOptimismLock {
        public static void main(String[] args) {
            // 内部是乐观锁,也是安全的
            AtomicInteger atomicInteger = new AtomicInteger();
            int i = atomicInteger.incrementAndGet();
            System.out.println("===:"+i);
        }
    }
    

      

    5.悲观锁与乐观锁的使用场景

      悲观锁:适合并发写入多的情况,适用于临界区持锁时间较长的情况,悲观锁可以避免大量的无用的自旋消耗,典型情况:

        临界区有IO操作

        临界区代码复杂或者循环量大

        临界区竞争激烈

      乐观锁:适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅度提高

    四:重入锁与非可重入锁

    1.可重入锁与非可重入锁,ReentrantLock

    package com.jun.juc.lock.reentrantlock;
    
    import java.util.PrimitiveIterator;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class CinemaBookSeat {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static void bookSeat(){
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"开始预订座位");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"完成预订");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(()->bookSeat()).start();
            new Thread(()->bookSeat()).start();
            new Thread(()->bookSeat()).start();
            new Thread(()->bookSeat()).start();
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:50292', transport: 'socket'
    Thread-0开始预订座位
    Thread-0完成预订
    Thread-1开始预订座位
    Thread-1完成预订
    Thread-2开始预订座位
    Thread-2完成预订
    Thread-3开始预订座位
    Disconnected from the target VM, address: '127.0.0.1:50292', transport: 'socket'
    Thread-3完成预订
    

      

    2.另一个示例

    package com.jun.juc.lock.reentrantlock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockDemo {
        static class Outpurer{
            Lock lock = new ReentrantLock();
            public void output(String name){
                int length = name.length();
                lock.lock();
                try{
                    for (int i=0;i<length;i++){
                        System.out.print(name.charAt(i));
                    }
                    System.out.println("打印完成");
                }finally {
                    lock.unlock();
                }
            }
        }
    
        private void init(){
            final Outpurer outpurer = new Outpurer();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        try{
                            Thread.sleep(500);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                        outpurer.output("abcdefg");
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        try{
                            Thread.sleep(500);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                        outpurer.output("123456");
                    }
                }
            }).start();
        }
    
        public static void main(String[] args) {
            LockDemo lockDemo = new LockDemo();
            lockDemo.init();
        }
    }
    

      

    3.演示可重入

      一个线程可以多次拿到这把锁,无需释放锁,就可以直接获取到。

      也叫递归锁。

      好处:

        避免死锁:如果有两个方法,都被一把锁锁住,运行到第一个方法拿到这把锁,运行到第二个方法,如果不是可重入锁,就需要等锁释放才可以获取,就会造成死锁。

        提升封装性:避免一次次加锁解锁

    package com.jun.juc.lock.reentrantlock;
    
    import javax.swing.*;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 可重入锁实验
     */
    public class GetHoldCount {
        private static ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            System.out.println(lock.getHoldCount());
            lock.lock();
    
            System.out.println(lock.getHoldCount());
            lock.lock();
    
            System.out.println(lock.getHoldCount());
            lock.lock();
    
            System.out.println(lock.getHoldCount());
            lock.unlock();
    
            System.out.println(lock.getHoldCount());
            lock.unlock();
    
            System.out.println(lock.getHoldCount());
            lock.unlock();
    
            System.out.println(lock.getHoldCount());
        }
    }
    

      效果:

    Disconnected from the target VM, address: '127.0.0.1:51619', transport: 'socket'
    0
    1
    2
    3
    2
    1
    0
    
    Process finished with exit code 0
    

      结论:

      很明显,继续加锁,前面没有解锁。是可重入锁。

    4.另一个示例

    package com.jun.juc.lock.reentrantlock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 递归锁
     */
    public class RecursionDemo {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static void accessResource() {
            lock.lock();
            try {
                System.out.println("已经对资源进行了处理");
                if (lock.getHoldCount() < 5) {
                    System.out.println("before:"+lock.getHoldCount());
                    accessResource();
                    System.out.println("after:"+lock.getHoldCount());
                }
            }finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            accessResource();
        }
    }
    

      效果:

    Disconnected from the target VM, address: '127.0.0.1:52207', transport: 'socket'
    已经对资源进行了处理
    before:1
    已经对资源进行了处理
    before:2
    已经对资源进行了处理
    before:3
    已经对资源进行了处理
    before:4
    已经对资源进行了处理
    after:4
    after:3
    after:2
    after:1
    
    Process finished with exit code 0
    

      

    5.ReentrantLock的其他方法

      isHeldByCurrentThread:看出锁是否被当前的线程持有

      getQueueLength:返回当前正在等待这把锁的队列有多长,一般这按两个方法是开发和调试时候使用

    五:公平锁与非公平锁

    1.公平锁与非公平锁

      公平是指按照线程请求的顺序,来分配锁

      非公平指的是,不完全按照请求的顺序,在一定的情况下,可以插队

    2.为什么需要非公平锁

      为了提高效率

      避免唤醒带来的空档期

      ReentrantLock,默认为非公平锁,true为公平锁,false为非公平锁

    3.公平锁

    package com.jun.juc.lock.reentrantlock;
    
    import java.util.Random;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 演示公平锁与非公平锁
     */
    public class FairLock {
        public static void main(String[] args) {
            PrintQueue printQueue = new PrintQueue();
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++) {
                threads[i] = new Thread(new Job(printQueue));
            }
            for (int i=0;i<10;i++){
                //依次执行
                threads[i].start();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    
    class Job implements Runnable {
        PrintQueue printQueue;
    
        public Job(PrintQueue printQueue) {
            this.printQueue = printQueue;
        }
    
        // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "开始打印");
            printQueue.printJob();
            System.out.println(Thread.currentThread().getName() + "开始完毕");
        }
    }
    
    class PrintQueue {
        private Lock queueLock = new ReentrantLock(true);
    
        public void printJob() {
            queueLock.lock();
            try {
                int duration = new Random().nextInt(10) +1;
                System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
                Thread.sleep(duration * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                queueLock.unlock();
            }
            //再次打印
            queueLock.lock();
            try {
                int duration = new Random().nextInt(10) + 1;
                System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
                Thread.sleep(duration * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                queueLock.unlock();
            }
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:62693', transport: 'socket'
    Thread-0开始打印
    Thread-0正在打印,需要时间:2
    Thread-1开始打印
    Thread-2开始打印
    Thread-3开始打印
    Thread-4开始打印
    Thread-5开始打印
    Thread-6开始打印
    Thread-7开始打印
    Thread-8开始打印
    Thread-9开始打印
    Thread-1正在打印,需要时间:2
    Thread-2正在打印,需要时间:2
    Thread-3正在打印,需要时间:5
    Thread-4正在打印,需要时间:3
    Thread-5正在打印,需要时间:9
    Thread-6正在打印,需要时间:10
    Thread-7正在打印,需要时间:3
    Thread-8正在打印,需要时间:4
    Thread-9正在打印,需要时间:5
    Thread-0正在打印,需要时间:8
    Thread-0开始完毕
    Thread-1正在打印,需要时间:3
    Thread-2正在打印,需要时间:6
    Thread-1开始完毕
    Thread-2开始完毕
    Thread-3正在打印,需要时间:6
    Thread-3开始完毕
    Thread-4正在打印,需要时间:9
    Thread-4开始完毕
    Thread-5正在打印,需要时间:5
    Thread-5开始完毕
    Thread-6正在打印,需要时间:7
    Thread-6开始完毕
    Thread-7正在打印,需要时间:7
    Thread-7开始完毕
    Thread-8正在打印,需要时间:1
    Thread-8开始完毕
    Thread-9正在打印,需要时间:2
    Thread-9开始完毕
    Disconnected from the target VM, address: '127.0.0.1:62693', transport: 'socket'
    

      结论:

      都是顺序的。重要的是,线程0执行完第一次锁的时候,想马上拿锁,是拿不到的,需要让线程9执行完,才能让线程0继续拿。

    4.非公平锁

      将true修改为false

    package com.jun.juc.lock.reentrantlock;
    
    import java.util.Random;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 演示公平锁与非公平锁
     */
    public class FairLock {
        public static void main(String[] args) {
            PrintQueue printQueue = new PrintQueue();
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++) {
                threads[i] = new Thread(new Job(printQueue));
            }
            for (int i=0;i<10;i++){
                //依次执行
                threads[i].start();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    
    class Job implements Runnable {
        PrintQueue printQueue;
    
        public Job(PrintQueue printQueue) {
            this.printQueue = printQueue;
        }
    
        // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "开始打印");
            printQueue.printJob();
            System.out.println(Thread.currentThread().getName() + "开始完毕");
        }
    }
    
    class PrintQueue {
        private Lock queueLock = new ReentrantLock(false);
    
        public void printJob() {
            queueLock.lock();
            try {
                int duration = new Random().nextInt(5) +1;
                System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
                Thread.sleep(duration * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                queueLock.unlock();
            }
            //再次打印
            queueLock.lock();
            try {
                int duration = new Random().nextInt(5) + 1;
                System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
                Thread.sleep(duration * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                queueLock.unlock();
            }
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:62894', transport: 'socket'
    Thread-0开始打印
    Thread-0正在打印,需要时间:1
    Thread-1开始打印
    Thread-2开始打印
    Thread-3开始打印
    Thread-4开始打印
    Thread-5开始打印
    Thread-6开始打印
    Thread-7开始打印
    Thread-8开始打印
    Thread-9开始打印
    Thread-0正在打印,需要时间:2
    Thread-0开始完毕
    Thread-1正在打印,需要时间:3
    Thread-1正在打印,需要时间:1
    Thread-1开始完毕
    Thread-2正在打印,需要时间:4
    Thread-2正在打印,需要时间:1
    Thread-2开始完毕
    Thread-3正在打印,需要时间:4
    Thread-3正在打印,需要时间:3
    Thread-3开始完毕
    Thread-4正在打印,需要时间:2
    Thread-4正在打印,需要时间:5
    Thread-4开始完毕
    Thread-5正在打印,需要时间:1
    Thread-5正在打印,需要时间:4
    Thread-5开始完毕
    Thread-6正在打印,需要时间:2
    Thread-6正在打印,需要时间:5
    Thread-6开始完毕
    Thread-7正在打印,需要时间:2
    Thread-7正在打印,需要时间:5
    Thread-7开始完毕
    Thread-8正在打印,需要时间:1
    Thread-8正在打印,需要时间:3
    Thread-8开始完毕
    Thread-9正在打印,需要时间:3
    Thread-9正在打印,需要时间:4
    Disconnected from the target VM, address: '127.0.0.1:62894', transport: 'socket'
    Thread-9开始完毕
    
    Process finished with exit code 0
    

      结论:

      在第二个线程还没唤醒前,当前马上又获取到锁,继续执行

    5.特例

      tryLock方法,不遵守设定的公平的规则

      当执行的时候,一旦有了线程释放锁,则可以直接获取到

    6.公平锁与非公平锁的优缺点

      

    六:共享锁与排它锁

    1.共享锁与排它锁

      以ReentractReadWriteLock为例

      读锁的作用:

        多个读操作,不会有线程安全问题。在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁,读是无阻塞的,提高了效率

      读写的规则

        多个线程只申请读锁,都可以申请到

        如果有一个线程占用了读锁,如果要申请写锁,则申请写锁的线程会一直等待释放读锁

        如果,一个线程占用了写锁,其他线程申请写锁或者读锁,都是只能等待写锁的释放

    2.读写锁示例

    package com.jun.juc.lock.readwritelock;
    
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * 读写锁
     */
    public class CinemalLock {
        private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
        private static void read(){
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁");
                readLock.unlock();
            }
        }
    
        private static void write(){
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(()->read(),"t1").start();
            new Thread(()->read(),"t2").start();
            new Thread(()->write(),"t3").start();
            new Thread(()->write(), "t4").start();
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:51054', transport: 'socket'
    t1得到了读锁,在读取
    t2得到了读锁,在读取
    t1释放读锁
    t2释放读锁
    t3得到了写锁,在写
    t3释放写锁
    t4得到了写锁,在写
    Disconnected from the target VM, address: '127.0.0.1:51054', transport: 'socket'
    t4释放写锁
    
    Process finished with exit code 0
    

      

    3 读锁插队策略

      通过加true或者false判断是公平锁还是非公锁

      

       

       

       结论:

      

    4.演示非公平锁的插队

    package com.jun.juc.lock.readwritelock;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class NonfairDemo {
        private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
        private static void read(){
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁");
                readLock.unlock();
            }
        }
    
        private static void write(){
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(()->write(),"t0").start();
            new Thread(()->read(),"t1").start();
            new Thread(()->read(),"t2").start();
            new Thread(()->write(),"t3").start();
            new Thread(()->read(), "t4").start();
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:58198', transport: 'socket'
    t0得到了写锁,在写
    t0释放写锁
    t1得到了读锁,在读取
    t2得到了读锁,在读取
    t1释放读锁
    t2释放读锁
    t3得到了写锁,在写
    t3释放写锁
    t4得到了读锁,在读取
    Disconnected from the target VM, address: '127.0.0.1:58198', transport: 'socket'
    t4释放读锁
    
    Process finished with exit code 0
    

      

    5.演示读锁的插队

      什么意思呢?

      在队列的头结点为写锁,则需要排队。要是读锁,则直接开始了读锁。

      怎么演示读锁是可以插队的呢,可以写一个子线程,不断的进行读插队,当开始读锁的时候,可以发现,每个读锁之间是有空闲期的,就可以出现了插队的出现了

    演示代码:

    package com.jun.juc.lock.readwritelock;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class NonfairBargeDemo {
        private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
        private static void read(){
            System.out.println(Thread.currentThread().getName()+ "尝试获取读锁");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁");
                readLock.unlock();
            }
        }
    
        private static void write(){
            System.out.println(Thread.currentThread().getName()+ "尝试获取写锁");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(()->write(),"t0").start();
            new Thread(()->read(),"t1").start();
            new Thread(()->read(),"t2").start();
    //        new Thread(()->write(),"t3").start();
            new Thread(()->read(), "t4").start();
            new Thread(()->read(), "t5").start();
            new Thread(()->read(), "t6").start();
            new Thread(()->read(), "t7").start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Thread[] threads = new Thread[2000];
                    for (int i=0; i<2000; i++){
                        threads[i] = new Thread(()->read(),"子线程创建的线程" + i);
                    }
                    for (int i=0; i< 2000; i++){
                        threads[i].start();
                    }
                }
            }).start();
        }
    }
    

      说明:

      如果还没开始进行t2,t3的获取锁,子线程就执行完成了抢锁,就演示不出效果了

      

    6.锁的升级与降级

      支持降级

      什么意思呢?

      在获取写锁,且不释放的情况下,可以接着获取读锁。

      不支持升级,只要是避免死锁。

      假设有两个线程,都在读,然后同时想进行升级到写锁,这个时候,就需要对方释放锁,然后,都不释放的时候,就陷入了死锁。所以,不支持升级。

      保证每一次只有一个锁在升级,才可以实现。

      

    七:自旋锁与阻塞锁

    1.说明

      

      

    2.源码说明

      

    3.实现一个自旋锁

    package com.jun.juc.lock.spinlock;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * 自旋锁
     */
    public class SpinLock {
        private AtomicReference<Thread> sign = new AtomicReference<Thread>();
    
        public void lock(){
            Thread current = Thread.currentThread();
            while (!sign.compareAndSet(null, current)){
                System.out.println(Thread.currentThread().getName() + "获取失败,再次尝试");
            }
        }
    
        public void unlock(){
            Thread current = Thread.currentThread();
            sign.compareAndSet(current, null);
        }
    
        public static void main(String[] args) {
            SpinLock spinLock = new SpinLock();
    
            Runnable runnable = new Runnable(){
                public void run(){
                    System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");
                    spinLock.lock();
                    System.out.println(Thread.currentThread().getName() +"获取到了自旋锁");
                    try{
                        Thread.sleep(300);
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        System.out.println(Thread.currentThread().getName() +"释放到了自旋锁");
                        spinLock.unlock();
                    }
                }
            };
    
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            thread1.start();
            thread2.start();
    
        }
    }
    

      

    4.适用场景

      自旋锁一般用于多核的服务器,在并发度不是很高的情况下,比阻塞锁的效率高

      自旋锁适用于临界区比较短小的情况

    八:可中断锁与不可中断锁

    1.说明

      synchronized是不可中断锁,而lock是可中断锁,因为trylock(time)与lockInterruptibly都可以相应中断

    九:锁优化

    1.jvm

      自旋锁和自适应

      锁消除

      锁粗化

    2.程序优化

      缩小同步代码块

      尽量不要锁住方法

       减少锁的次数

      避免人为制造热点

      锁中尽量不要包含锁

      选择合适锁类型或者合适的工具类

      

      

      

      

      

  • 相关阅读:
    三层架构之解耦
    自动升级 组件
    C语言常量与指针
    ASP.NET MVC Model元数据
    Web层后端权限模块
    java中文排序问题(转)
    JDWP
    bat执行java程序的脚本解析
    jdom dom4j解析xml不对dtd doctype进行验证(转)
    Dom4j SAXReader Constructors
  • 原文地址:https://www.cnblogs.com/juncaoit/p/13022440.html
Copyright © 2011-2022 走看看