java中的锁

锁是什么

answer: 锁是常用的并发控制机构

什么是进程? 什么是线程?

answer: 进程是具有特定功能的程序关于一次特定数据集合的一次运动活动

线程是进程中执行的最小单元

synchronized

  • 背景: 为了保证多线程访问过程中,对同一对象的读写时数据的一致性,增加了锁结构

  • 下面为一次多线程同时对一个对象做减法,导致对象同一个值被多次读取(Thread.sleep 只是为了模仿网络环境的阻塞),就会产生错误值

    public class SynchronizedDemo {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "C").start();
        }
    }
    
    // 为了降低耦合性,多线程开发中资源类只有属性和方法
    class Ticket {
        private int ticketNum = 10;
    
        public void get() {
            if (ticketNum > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 买到了第" + ticketNum-- + "张," + "目前还有" + ticketNum + "张");
            }
        }
    }
    
  • 对多线程操作的对象进行锁的增加,可以保证对象在同一时间锁内的方法或语句只会有一条线程进行操作,其他线程必须等待该线程操作结束才能再次竞争锁

    public class SynchronizedDemo {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "C").start();
        }
    }
    
    // 为了降低耦合性,多线程开发中资源类只有属性和方法
    class Ticket {
        private int ticketNum = 10;
    
        public synchronized void get() {
            if (ticketNum > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 买到了第" + ticketNum-- + "张," + "目前还有" + ticketNum + "张");
            }
        }
    }
    
    • 可以看到增加 synchronized 后,不会产生一次数据多次读取,而且各个线程按照顺序进行读取(可能会产生所有数据都是一个线程读取,这是因为更深层次的原因,这里不讨论)

wait and notify

  • 背景: 对于多线程访问同一对象,如果某条线程的运行条件不能被满足,那么这条线程应该被暂停运行并释放锁,等其他线程运行后这条线程运行条件被满足,那么就应该重新运行

    public class WaitAndNotify {
        public static void main(String[] args) {
            Queue<Integer> queue = new LinkedList<>();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    synchronized (WaitAndNotify.class) {
                        System.out.println("add线程获得锁");
                        queue.add(i + 1);
                        System.out.println("add线程释放锁");
                        WaitAndNotify.class.notifyAll();
                        System.out.println("add线程唤醒get线程");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                }
            }).start();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    synchronized (WaitAndNotify.class) {
                        System.out.println("get线程获得锁");
                        while (queue.isEmpty()) {
                            try {
                                System.out.println("get线程睡眠");
                                WaitAndNotify.class.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(queue.remove());
                        System.out.println("get线程释放锁");
                    }
                }
    
            }).start();
        }
    }
    

ReentrantLock

  • Java.util 提供了一个新的锁,提高了加锁和释放锁的性能

    public class ReentrantLockDemo2 {
        public static void main(String[] args) {
            Ticket2 ticket = new Ticket2();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) ticket.get();
            }, "C").start();
        }
    }
    
    class Ticket2 {
        private int ticketNum = 10;
    
        private final Lock lock = new ReentrantLock();
    
        public void get() {
            lock.lock();
            try {
                if (ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + " 买到了第" + ticketNum-- + "张,剩余" + ticketNum + "张");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

await and signal

  • java.util.concurrent 包同时提供了新的等待和唤醒方式

    public class ReentrantLockDemo {
        public static void main(String[] args) {
            Ticket3 ticket3 = new Ticket3();
            new Thread(()->{
                for (int i =0; i < 5; i++){
                    ticket3.add();
                }
            }, "A").start();
            new Thread(()->{
                for (int i =0; i < 5; i++){
                    ticket3.dec();
                }
            }, "B").start();
        }
    
    }
    
    class Ticket3 {
        // 注意点,两个锁是交叉使用的
        private int ticketNum = 0;
        private final Lock lock = new ReentrantLock();
        private final Condition notFull = lock.newCondition();
        private final Condition notEmpty = lock.newCondition();
    
        public void add(){
            lock.lock();
            //System.out.println(Thread.currentThread().getName() + "get");
            try {
                while (ticketNum > 0){
                    //System.out.println(Thread.currentThread().getName() + "wait");
                    notFull.await();
                }
                System.out.println(Thread.currentThread().getName() + "===>" + ++ticketNum);
                notEmpty.signalAll();
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                //System.out.println(Thread.currentThread().getName() + "unlock");
                lock.unlock();
            }
        }
    
        public void dec(){
            lock.lock();
            //System.out.println(Thread.currentThread().getName() + "get");
            try {
                while (ticketNum <= 0){
                    //System.out.println(Thread.currentThread().getName() + "wait");
                    notEmpty.await();
                }
                System.out.println(Thread.currentThread().getName() + "===>" + --ticketNum);
                notFull.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            } finally {
                //System.out.println(Thread.currentThread().getName() + "unlock");
                lock.unlock();
            }
        }
    }
    
  • 新的等待和唤醒方式的作用是精准唤醒

    public class ReentrantLockDemo3 {
        public static void main(String[] args) {
            Ticket5 ticket5 = new Ticket5();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++){
                    ticket5.printA();
                }
            }).start();
            new Thread(()->{
                for (int i = 0; i < 5; i++){
                    ticket5.printB();
                }
            }).start();
            new Thread(()->{
                for (int i = 0; i < 5; i++){
                    ticket5.printC();
                }
            }).start();
        }
    }
    
    class Ticket5{
        // 精准通知相关线程
        private final Lock lock = new ReentrantLock();
        private final Condition conditionA = lock.newCondition();
        private final Condition conditionB = lock.newCondition();
        private final Condition conditionC = lock.newCondition();
    
        private int flag = 1;
    
        public void printA(){
            lock.lock();
            try {
                if (flag != 1){
                    conditionA.await();
                }
                System.out.println(Thread.currentThread().getName() + "AAA");
                flag = 2;
                conditionB.signalAll();
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printB(){
            lock.lock();
            try {
                if (flag != 2){
                    conditionB.await();
                }
                System.out.println(Thread.currentThread().getName() + "BBB");
                flag = 3;
                conditionC.signalAll();
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printC(){
            lock.lock();
            try {
                if (flag != 3){
                    conditionC.await();
                }
                System.out.println(Thread.currentThread().getName() + "CCC");
                flag = 1;
                conditionA.signalAll();
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

ReentrantReadWriteLock

  • 背景: 由于多数应用在多线程方面都是写入较少甚至没有读取,而读取是必须的,而前面的两种锁结构都是在读取的时候每条线程单独进行读取

  • ReentrantReadWriteLock 锁可以保证在写入的时候,所有的读锁都会阻塞,在读取的时候可以多个锁同时访问对象,这种锁也叫悲观锁

    public class ReentrantLockDemo4 {
        public static void main(String[] args) {
            Ticket6 ticket = new Ticket6();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    ticket.add();
                }
            }).start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    ticket.get();
                }
            }).start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    ticket.get();
                }
            }).start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    ticket.get();
                }
            }).start();
        }
    }
    
    class Ticket6 {
        // 精准通知相关线程
        private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
        private final Lock rlock = rwlock.readLock();
        private final Lock wlock = rwlock.writeLock();
    
        private int flag = 0;
    
        public void add() {
            wlock.lock(); // 加写锁
            try {
                flag += 3;
                System.out.println(Thread.currentThread().getName() + flag);
            } finally {
                wlock.unlock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void get() {
            rlock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + --flag);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rlock.unlock();
            }
        }
    }
    

StampedLock

  • 悲观锁的问题在于当我们少量写入大量读取的时候,因为读锁必须等待写锁写完才能读取,所以会造成多数人等少数人的问题

  • 乐观锁应运而生,乐观锁的读锁在读取的时候会进行一个标记,在读取结束的时候会进行一次标记检查,检查锁的版本是否有变化,如果有变化,那么说明有写锁进行了数据修改,那么这个时候读锁会再进行加锁,这次加的是悲观锁,防止数据修改。并重新读取数据。如果锁的版本没有变化,那么就说明没有写锁进行数据修改,就不会加锁,直接输出。这其中就省略了一次加读锁的过程,节约了性能

  • // 代码pass

  • 后续会补充关于更高阶的内容——————//todo

posted @ 2021-06-22 22:34  Rainful  阅读(42)  评论(0编辑  收藏  举报