zoukankan      html  css  js  c++  java
  • JUC:Lock接口、ReentrantLock类、Lock与Synchronized的区别、解决虚假唤醒、Condition接口、解决集合类线程不安全、Callable、FutrueTask

    Lock 接口 (重点)

    常用传教Lock的方法:

    Lock lock = new ReentrantLock()

    1、ReentrantLock 类

    实现了 Lock接口

    构造方法:

    // ReentrantLock类有两个构造器
    public ReentrantLock() {
        sync = new NonfairSync(); // 获得非公平锁
    }
    
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync(); // 如果为true则获得公平锁
    }

    默认构造器创建一个非公平锁,传入true则会获得公平锁

    非公平锁:其他线程可以插队,可实现花费时间少的线程可以优先使用

    公平锁:当线程被cpu调用了,那么就必须得去执行,不能被改变

    // 固定写法 try catch Finally
    public class LockTest {
        public static void main(String[] args) {
            SaleTickets saleTickets = new SaleTickets();
            new Thread(()-> {for (int i = 0; i < 50; i++) saleTickets.sale();},"A").start();
            new Thread(()-> {for (int i = 0; i < 50; i++) saleTickets.sale();},"B").start();
            new Thread(()-> {for (int i = 0; i < 50; i++) saleTickets.sale();},"C").start();
        }
    }
    
    class SaleTickets{
        private int tickets = 50;
        // 创建非公平锁对象
        Lock lock = new ReentrantLock();
        public void sale(){
            // 设置锁
            lock.lock();
            try{
                if(tickets > 0){
                    System.out.println(Thread.currentThread().getName() + ": 卖出了第"+ tickets-- + "号票");
                }
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    }

    2、Lock与Synchronized的区别 面试

    1. synchronized 是java内置的关键字。Lock是一个类
    2. synchronized 无法判断锁的状态。而Lock锁可以判断锁的状态。
    3. synchronized 是自动释放锁。Lock得调用unlock方法释放锁 (如果不释放锁,则会产生死锁状态)。
    4. synchronized 如果是有两个线程,有一个线程在执行过程中被阻塞,那么另一个线程就会一直等待。Lock锁则不一定会等待下去 (可通过调用tryLock方法去避免这个问题)。
    5. synchronized 可重入锁,不可中断的非公平锁。Lock也是可重入的锁,并可以设置锁的公平与非公平锁
    6. synchronized 适合锁少量的代码同步问题。Lock适合锁大量的同步代码。

    3、防止线程虚假唤醒

    synchronized 来实现防止线程虚假唤醒

    public class SynchTest {
        public static void main(String[] args) {
            Number number = new Number();
            // 创建了3个线程
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
            
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
    
        }
    }
    
    class Number{
        private int num = 0;
    
        public synchronized void increment() throws InterruptedException {
            // 如果num不是0,就进入等待状态
            while (num != 0){
                this.wait();
            }
            num ++ ;
            System.out.println(Thread.currentThread().getName() + " ==> " + num);
            // 通知等待中的线程
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            // 如果num是0,就进入等待状态
            while (num == 0){
                this.wait();
            }
            num -- ;
            System.out.println(Thread.currentThread().getName() + " ==> " + num);
            this.notifyAll();
        }
    }

    解决虚假唤醒分析 面试

    如上代码,如果在Number类同步代码块中使用if判断num的值进而进行等待操作,在2个线程之间通信是不会出现虚假唤醒的情况。而如果是大于2个线程,还是使用if判断就会出现问题。因为,当调用this.notifyAll();时,会唤醒所有等待的线程,唤醒之后,如果时if的话,就不会再去判断num是否满足条件,会在之前执行等待的代码开始继续往下执行。而使用while,线程醒了进入BLOCK状态,被cpu调用之后,还会去判断num是否符合要求,直到不符合,才会继续执行while以外的代码。这样就保证了线程的安全。while循环的作用就是保证了符合要求的才可以进行之后的操作。

    4、Condition 接口 JDK 1.5

    JUC 线程之间的通信

    synchronized 与 Lock 的对应关系

    在这里插入图片描述

    java.utils.concurrent.locks interface Condition 接口中

    • void await() throws InterruptedException
    • void signal()
    • void signalAll()

    与传统的wait方法和notify方法没有什么不同,只是这是Lock接口专门使用的

    在这里插入图片描述

    public class LockConditionTest {
        public static void main(String[] args) {
            Numbers number = new Numbers();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        number.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
        }
    }
    
    class Numbers{
        private int num = 0;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
    
        public void increment() throws InterruptedException {
            lock.lock();
            try{
                // 如果num不是0,就进入等待状态
                while (num != 0){
                    condition.await();
                }
                num ++ ;
                System.out.println(Thread.currentThread().getName() + " ==> " + num);
                // 通知等待中的线程
                condition.signalAll();
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                lock.unlock();
            }
        }
    
        public void decrement() throws InterruptedException {
            lock.lock();
            try{
                // 如果num不是0,就进入等待状态
                while (num == 0){
                    condition.await();
                }
                num -- ;
                System.out.println(Thread.currentThread().getName() + " ==> " + num);
                // 通知等待中的线程
                condition.signalAll();
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                lock.unlock();
            }
        }
    }

    这样写,与普通方式没什么区别。

    5、Condition实现精准通知唤醒

    • 可以使线程之间有序的执行,精准的通知和唤醒指定的线程
    public class LockConditionTest {
        public static void main(String[] args) {
            Numbers number = new Numbers();
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    number.printA();
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    number.printB();
                }
            },"B").start();
    
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    number.printC();
                }
            },"C").start();
        }
    }
    
    class Numbers{
        private int num = 1;  // num=1 线程A执行, num=2 线程B执行, num=3 线程C执行
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
    
        public void printA(){
            lock.lock();
            // 判断等待 执行 通知
            try{
                // 如果num不等于1,就等待,不执行
                while (num != 1){
                    condition1.await();
                }
                num = 2;
                System.out.println(Thread.currentThread().getName());
                condition2.signal(); // 这句话与notify有点区别,该意思是,给condition2发送通知,让condition2去执行
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                lock.unlock();
            }
        }
    
        public void printB(){
            lock.lock();
            try {
                while ( num != 2){
                    condition2.await();
                }
                num = 3;
                System.out.println(Thread.currentThread().getName());
                condition3.signal();
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                lock.unlock();
            }
        }
    
        public void printC(){
            lock.lock();
            try {
                while ( num != 3){
                    condition3.await();
                }
                num = 1;
                System.out.println(Thread.currentThread().getName());
                condition1.signal();
            }catch (Exception e){
                System.out.println("异常");
            }finally {
                lock.unlock();
            }
        }
    }
    
    // 线程有顺序的执行并输出:
    // A
    // B
    // C
    // ...

    6、关于锁的问题 面试

    // 1. 哪个语句先输出
    public class LockProblem8 {
        public static void main(String[] args) {
            Info info = new Info();
    
            new Thread(()->info.msg()).start();
    
            try {
                TimeUnit.SECONDS.sleep(5); // 使已启动的线程睡眠5秒,常用的方法
            } catch (InterruptedException e) {
                System.out.println("异常");
            }
    
            new Thread(()->info.call()).start();
        }
    }
    
    class Info{
        public synchronized void msg(){
            System.out.println("发短信...");
        }
        public synchronized void call(){
            System.out.println("打电话...");
        }
    }
    // 这里的锁为同一个对象锁,两个线程的锁都是info这个对象
    // 先输出 发短信... 再输出 打电话...
    // 2 哪个语句先输出
    public class LockProblem8 {
        public static void main(String[] args) {
            Info info1 = new Info();
            Info info2 = new Info();
    
            new Thread(()->info1.msg()).start();
    
            new Thread(()->info2.msg()).start();
        }
    }
    
    class Info{
    
        public synchronized void msg(){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                System.out.println("异常");
            }
            System.out.println("发短信...");
        }
    
        public synchronized void call(){
            System.out.println("打电话...");
        }
    }
    // 因为两个线程都是非同一把锁,锁对象都不一样。因为不是同样的所对象,所以互不影响。又因为msg()方法有睡眠
    // 先输出 打电话... 在输出 发短信...
    // 3 哪个语句先输出
    public class LockProblem8 {
        public static void main(String[] args) {
            Info info1 = new Info();
            Info info2 = new Info();
    
            new Thread(()->info1.msg()).start();
    
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                System.out.println("异常");
            }
    
            new Thread(()->info2.call()).start();
        }
    }
    
    class Info{
        public static synchronized void msg(){
            System.out.println("发短信...");
        }
    
        public static synchronized void call(){
            System.out.println("打电话...");
        }
    }
    // 因为同步代码被static修饰,所以锁对象都是Info的Class对象,在类加载器加载的时候就生成了的
    // 所以先输出 发短信... 在输出 打电话...
    // 4 哪个语句先输出
    public class LockProblem8 {
        public static void main(String[] args) {
            Info info1 = new Info();
            Info info2 = new Info();
    
            new Thread(()->info1.msg()).start();
    
            new Thread(()->info2.call()).start();
        }
    }
    
    class Info{
        public static synchronized void msg(){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                System.out.println("异常");
            }
            System.out.println("发短信...");
        }
    
        public void call(){
            System.out.println("打电话...");
        }
    }
    // 由于第二个线程调用的是普通方法,没有锁的竞争
    // 所以先输出 打电话... 在输出 发短信...

    解决集合类线程不安全

    使用大部分集合 会报异常 ConcurrentModificationException(并发修改异常) 线程不同步原因。

    解决集合同步

    关于List集合

    1. 使用Vector集合,继承了List集合,始于jdk1.0

      Vector之所以线程安全,是因为在每个方法上添加了关键字synchronized

      public synchronized boolean add(E e) {
          modCount++;
          ensureCapacityHelper(elementCount + 1);
          elementData[elementCount++] = e;
          return true;
      }
    2. Collections.synchronizedList(new ArrayList<>());

      通过Collections集合的工具类,包装集合,达到线程安全。只不过它不是加在方法的声明处,而是方法的内部

    3. new CopyOnWriteArrayList<>();

      JUC可解决并发线程安全问题。顾名思义:写入时复制。多个线程,每个线程在写入时,将写入的数据进行复制,然后再插入到集合中,保证其他线程写入的数据不被覆盖。

      源码:

       private transient volatile Object[] array;

      遍历Vector/SynchronizedList是需要自己手动加锁的。

      CopyOnWriteArrayList使用迭代器遍历时不需要显示加锁,看看add()、clear()、remove()get()方法的实现可能就有点眉目了。

      public boolean add(E e) {   
          // 加锁
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
      
              // 得到原数组的长度和元素
              Object[] elements = getArray();
              int len = elements.length;
      
              // 复制出一个新数组
              Object[] newElements = Arrays.copyOf(elements, len + 1);
      
              // 添加时,将新元素添加到新数组中
              newElements[len] = e;
      
              // 将volatile Object[] array 的指向替换成新数组
              setArray(newElements);
              return true;
          } finally {
              lock.unlock();
          }
      }

      通过代码我们可以知道:在添加的时候就上锁,并复制一个新数组,增加操作在新数组上完成,将array指向到新数组中,最后解锁。

      【总结】

      • 在修改时,复制出一个新数组,修改的操作在新数组中完成,最后将新数组交由array变量指向
      • 写加锁,读不加锁

    关于Set集合

    1. Collections.synchronizedSet(Set<E> set) 方法解决并发
    2. CopyOnWriteArraySet 类解决并发

    原理与List集合一样


    关于Map集合

    1. Collections.synchronizedMap(Map<K,V> map)

    2. ConcurrentHashMap<K,V>()

      要知道ConcurrentHashMap原理

    解决并发几个方法的区别

    • Vector被CopyOnWriteArrayList替代,是因为Vector在每个方法都是使用的Synchronized关键字,而CopyOnWriteArrayList是使用的Lock锁,因此后者效率高很多
    • Vector和Collections都是使用的Synchronized关键字,前者在方法上,后者在方法中使用。并且两个都在原集合进行操作。

    JUC解决并发代码:

    利用循环创建多个线程,每个线程都需要往list集合中添加数据,模拟高并发

    public class Test01{
        public static void main(String[] args) {
            // JUC 解决
            List<String> list = new CopyOnWriteArrayList<>();
    
            for (int i = 1; i <= 10; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,4));
                    System.out.println(Thread.currentThread().getName() + list);
                },String.valueOf(i)).start();
            }
        }
    }

    Callable 进阶 FutureTask

    java.util.concurrent interface Callable<V>

    函数式接口,只有一个call方法。该接口与Runnble类似。只不过该接口有返回值,可抛出异常。

    Thread类没有关于Callable的构造方法。因此Callable只能通过Runnable实现类java.utils.concurrent.FutureTask<V>的构造方法,与Thread类进行连接

    在这里插入图片描述

    public class CallableUpTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<String> futureTask = new FutureTask<>(new CallableUp());
            new Thread(futureTask).start(); // 创建Callable线程
            // 两个线程去执行Callable中的call方法,只打印一次call,是因为有缓存
            new Thread(futureTask).start(); 
            
            // 获取返回值,可能会产生阻塞,是因为在执行call方法时,可能时间很长,一般最后去获取返回值
            System.out.println(futureTask.get()); 
        }
    }
    
    class CallableUp implements Callable<String>{
        @Override
        public String call() throws Exception {
            System.out.println("call");
            return "12345";
        }
    }
    // 输出
    // call
    // 12345

    细节:

    • Callable有缓存
    • 获取返回值可能会发生阻塞

    感谢:bilibli主播 —— 遇见狂神说
    博主的开源教学视频十分良心!超级赞!全栈Java的学习指导!
    链接:https://space.bilibili.com/95256449/video
    本博客是本人观看 遇见狂神说 的开源课程而自己所做的笔记,与 遇见狂神说 开源教学视频搭配更佳!!

  • 相关阅读:
    DBA_Oracle Erp重启Database/Application/Concurrent/Apache(案例)
    DBA_Oracle Erp R12安装虚拟机镜像IP修正(案例)
    RMAN_学习实验2_RMAN Duplicate复制数据库过程(案例)
    RMAN_学习实验1_RMAN备份标准过程(案例)
    PLSQL_基础系列12_替换函数用法REPLACE / TRANSLATE / REGEXP_REPLACE
    PLSQL_基础系列11_递归和层次查询CONNECT BY(案例)
    DBA_Oracle Sort排序处理空间耗用(概念)
    DBA_Oracle性能优化的基本方法概述(方法论)
    DBA_Oracle海量数据处理分析(方法论)
    PLSQL_基础系列10_子查询WITH AS(案例)
  • 原文地址:https://www.cnblogs.com/turbo30/p/13688207.html
Copyright © 2011-2022 走看看