• 狂神说JUC学习笔记(一)


    狂神说JUC的原版笔记:
    链接:https://pan.baidu.com/s/12zrGI4JyZhmkQh0cqEO4BA
    提取码:d65c

    我的笔记在狂神的笔记上增加了一些知识点或者做了些许修改

    如果狂神原版笔记的连接失效了请在评论区留言,我看到后会更新的

    什么是JUC

    JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。

    线程和进程

    进程是操作系统中的应用程序、是资源分配的基本单位,线程是用来执行具体的任务和功能,是CPU调度和分派的最小单位

    一个进程往往可以包含多个线程,至少包含一个

    例如:

    进程:一个程序,QQ.exe Music.exe 程序的集合;
    一个进程往往可以包含多个线程,至少包含一个!

    Java默认有几个线程?

    2个,main线程、GC线程


    线程:开了一个进程 Typora,写字,自动保存(线程负责的)
    对于Java而言:Thread、Runnable、Callable

    Java 真的可以开启线程吗?

    开不了。Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

    start底层代码:

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    //这是一个C++底层,Java是没有权限操作底层硬件的
    private native void start0();
    

    并发和并行

    并发:多个任务在同一个CPU 核上,按细分的时间片轮流(交替)执行,从逻编上来看那些任务是同时执行。

    • 狂神笔记:
      • 多线程操作同一个资源
      • CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替

    并行: 单位时间内,多个处理器或多核处理器同时处理多个任务, 是真正意义上的“同时进行” 。

    • 狂神笔记:
      • 多个人一起行走
      • CPU 多核 ,多个线程可以同时执行; 线程池

    补充

    并发

    并行

    串行: 有n个任务, 由一个线程按顺序执行。由于任务、方法都在一个线程执行,所以不存在线程不安全情况,也就不存在临界区的问题。


    获取cpu的核数

    public class Test1 {
        public static void main(String[] args) {
            // 获取cpu的核数
    		// CPU 密集型,IO密集型
            System.out.println(Runtime.getRuntime().availableProcessors());
        }
    }
    

    线程有几个状态

    public enum State {
    
        	//运行
            NEW,
    
        	//运行
            RUNNABLE,
    
        	//阻塞
            BLOCKED,
    
        	//等待
            WAITING,
    
        	//超时等待
            TIMED_WAITING,
    
        	//终止
            TERMINATED;
        }
    

    wait/sleep

    1、来自不同的类

    wait => Object

    sleep => Thread

    2、关于锁的释放

    wait 会释放锁;

    sleep睡觉了,不会释放锁;

    3、使用的范围是不同的

    wait 必须在同步代码块中;

    sleep 可以在任何地方睡;

    4、是否需要捕获异常

    wait是不需要捕获异常;

    sleep必须要捕获异常;

    Lock锁(重点)

    传统的 synchronized

    import lombok.Synchronized;
    
    public class Demo01 {
        public static void main(String[] args) {
            final Ticket ticket = new Ticket();
    
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"A").start();
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"B").start();
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"C").start();
        }
    }
    // 资源类 OOP 属性、方法
    class Ticket {
        private int number = 30;
    
        //卖票的方式
        public synchronized void sale() {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
            }
        }
    }
    

    Lock

    公平锁: 十分公平,必须先来后到~;

    非公平锁: 十分不公平,可以插队;(默认为非公平锁)

    补充:

    公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

    • 优点:所有的线程都能得到资源,不会饿死在队列中。
    • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

    非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

    • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。

    • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;

      public class LockDemo {
      public static void main(String[] args) {
      final Ticket2 ticket = new Ticket2();

            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "C").start();
        }
      

      }
      //lock三部曲
      //1、 Lock lock=new ReentrantLock();
      //2、 lock.lock() 加锁
      //3、 finally=> 解锁:lock.unlock();
      class Ticket2 {
      private int number = 30;

        // 创建锁
        Lock lock = new ReentrantLock();
        //卖票的方式
        public synchronized void sale() {
            lock.lock(); // 开启锁
            try {
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
                }
            }finally {
                lock.unlock(); // 关闭锁
            }
      
        }
      

      }

    Synchronized 与Lock 的区别

    1、Synchronized 内置的Java关键字,Lock是一个Java类

    2、Synchronized 无法判断获取锁的状态,Lock可以判断

    3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

    4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

    5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

    6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

    生产者和消费者的关系

    生产者和消费者问题 Synchronized 版

    /**
    * 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
    * 线程交替执行 A B 操作同一个变量 num = 0
    * A num+1
    * B num-1
    */
    
    public class ConsumeAndProduct {
        public static void main(String[] args) {
            Data data = new Data();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "B").start();
        }
    }
    
    // 判断等待,业务,通知
    class Data {
        private int num = 0;
    
        // +1
        public synchronized void increment() throws InterruptedException {
            // 判断等待
            if (num != 0) {
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            this.notifyAll();
        }
    
        // -1
        public synchronized void decrement() throws InterruptedException {
            // 判断等待
            if (num == 0) {
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 -1 执行完毕
            this.notifyAll();
        }
    }
    

    问题存在,A B C D 4 个线程! 虚假唤醒

    解决方式 ,if 改为while即可,防止虚假唤醒

    public class ConsumeAndProduct {
        public static void main(String[] args) {
            Data data = new Data();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "C").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "D").start();
        }
    }
    
    class Data {
        private int num = 0;
    
        // +1
        public synchronized void increment() throws InterruptedException {
            // 判断等待
            while (num != 0) {
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            this.notifyAll();
        }
    
        // -1
        public synchronized void decrement() throws InterruptedException {
            // 判断等待
            while (num == 0) {
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 -1 执行完毕
            this.notifyAll();
        }
    }
    

    结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
    这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后

    JUC版的生产者和消费者问题

    补充:

    Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

    Condition是个接口,基本的方法就是await()和signal()方法;

    • Conditon中的await()对应Object的wait();

    • Condition中的signal()对应Object的notify();

    • Condition中的signalAll()对应Object的notifyAll()。

      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;

    Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
    调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

    public class LockCAP {
        public static void main(String[] args) {
            Data2 data = new Data2();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
    
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "C").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "D").start();
        }
    }
    
    class Data2 {
        private int num = 0;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
    	//condition.await(); // 等待
    	//condition.signalAll(); // 唤醒全部
        // +1
        public  void increment() throws InterruptedException {
            lock.lock();
            try {
                // 判断等待
                while (num != 0) {
                    condition.await();
                }
                num++;
                System.out.println(Thread.currentThread().getName() + "=>" + num);
                // 通知其他线程 +1 执行完毕
                condition.signalAll();
            }finally {
                lock.unlock();
            }
    
        }
    
        // -1
        public  void decrement() throws InterruptedException {
            lock.lock();
            try {
                // 判断等待
                while (num == 0) {
                    condition.await();
                }
                num--;
                System.out.println(Thread.currentThread().getName() + "=>" + num);
                // 通知其他线程 -1 执行完毕
                condition.signalAll();
            }finally {
                lock.unlock();
            }
    
        }
    }
    

    Condition 精准的通知和唤醒线程

    如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Description:
     * A 执行完 调用B
     * B 执行完 调用C
     * C 执行完 调用A
     *
     **/
    
    public class ConditionDemo {
        public static void main(String[] args) {
            Data3 data3 = new Data3();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    data3.printA();
                }
            },"A").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    data3.printB();
                }
            },"B").start();
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    data3.printC();
                }
            },"C").start();
        }
    
    }
    class Data3 {
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
        private int num = 1; // 1A 2B 3C
    
        public void printA() {
            lock.lock();
            try {
                // 业务代码 判断 -> 执行 -> 通知
                while (num != 1) {
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName() + "==> AAAA" );
                num = 2;
                condition2.signal();
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        public void printB() {
            lock.lock();
            try {
                // 业务代码 判断 -> 执行 -> 通知
                while (num != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "==> BBBB" );
                num = 3;
                condition3.signal();
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        public void printC() {
            lock.lock();
            try {
                // 业务代码 判断 -> 执行 -> 通知
                while (num != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName() + "==> CCCC" );
                num = 1;
                condition1.signal();
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    /*
    A==> AAAA
    B==> BBBB
    C==> CCCC
    A==> AAAA
    B==> BBBB
    C==> CCCC
    ...
    */
    

    8锁现象

    如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!

    例子1

    两个同步方法,先执行发短信还是打电话

    import java.util.concurrent.TimeUnit;
    
    public class Test1 {
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            //锁的存在
            new Thread(()->{
                phone.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone.call();
            },"B").start();
        }
    }
    
    class Phone{
        // synchronized 锁的对象是方法的调用者!
        // 两个方法用的是同一个锁,谁先拿到谁执行
        public synchronized void sendSms(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    

    两种情况:

    情况1:两个线程在标准情况下运行,即没有休眠4s

    就是注释代码中的这一段

    //        try {  
    //            TimeUnit.SECONDS.sleep(4);  
    //        } catch (InterruptedException e) {  
    //            e.printStackTrace();  
    //        }  
    

    情况1输出结果为:

    发短信
    打电话
    

    先打印发短信,过了1s再打印打电话

    情况2,加上了上述注释中的代码

    大约5s,发短信和打电话一起被打印

    例子2

    情况1:只有一个对象

    import java.util.concurrent.TimeUnit;
    
    public class Test2 {
        public static void main(String[] args) {
    
            Phone2 phone1 = new Phone2();
    
            //锁的存在
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone1.hello();
            },"B").start();
        }
    }
    
    class Phone2{
        public synchronized void sendSms(){
    
            // synchronized 锁的对象是方法的调用者!
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    
        // 这里没有锁!不是同步方法,不受锁的影响
        public void hello(){
            System.out.println("hello");
        }
    }
    

    输出结果

    hello
    发短信
    

    1s后打印hello,4s后打印发短信,hello没有锁,不受锁影响

    如果去掉延迟4s的话就是发短信先了,因为不用等待4s了,而hello要等待main中 sleep 1s

    情况2:

    两个对象,两个线程分别调用不同对象的发短信和打电话方法

    import java.util.concurrent.TimeUnit;
    
    public class Test2 {
        public static void main(String[] args) {
    
            Phone2 phone1 = new Phone2();
            Phone2 phone2 = new Phone2();
    
            //锁的存在
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone2.call();
            },"B").start();
        }
    }
    
    class Phone2{
        public synchronized void sendSms(){
    
            // synchronized 锁的对象是方法的调用者!
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    
        // 这里没有锁!不是同步方法,不受锁的影响
        public void hello(){
            System.out.println("hello");
        }
    }
    

    输出结果:

    打电话
    发短信
    

    两把锁不一样,按时间来,打电话更快

    例子3

    情况1:增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?

    import java.util.concurrent.TimeUnit;
    
    public class Test3 {
        public static void main(String[] args) {
            Phone3 phone1 = new Phone3();
    
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone1.call();
            },"B").start();
        }
    }
    
    class Phone3{
        public static synchronized void sendSms(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public static synchronized void call(){
            System.out.println("打电话");
        }
    }
    

    输出结构:

    发短信
    打电话
    

    说明:
    synchronized 锁的对象是方法的调用者!
    static 静态方法,类一加载就有了!锁的是Class对象

    再看另一个情况加深理解:

    情况2:两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?

    import java.util.concurrent.TimeUnit;
    
    public class Test3 {
        public static void main(String[] args) {
            Phone3 phone1 = new Phone3();
            Phone3 phone2 = new Phone3();
    
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone2.call();
            },"B").start();
        }
    }
    
    class Phone3{
        public static synchronized void sendSms(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public static synchronized void call(){
            System.out.println("打电话");
        }
    }
    

    输出结果:

    发短信
    打电话
    

    说明:发短信永远在前面,因为static的修饰,锁的是对象Phone3,而对象只有一个,所以按照线程的顺序来获取锁。

    例子4

    情况1:1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?

    import java.util.concurrent.TimeUnit;
    
    public class Test4 {
        public static void main(String[] args) {
            Phone4 phone1 = new Phone4();
            
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone1.call();
            },"B").start();
        }
    }
    
    class Phone4{
        public static synchronized void sendSms(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    

    输出结果:

    打电话
    发短信
    

    说明:加了static锁的是class类模板,普通同步方法锁的是调用者

    情况2:1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?

    import java.util.concurrent.TimeUnit;
    
    public class Test4 {
        public static void main(String[] args) {
            Phone4 phone1 = new Phone4();
            Phone4 phone2 = new Phone4();
    
            new Thread(()->{
                phone1.sendSms();
            }, "A").start();
    
            //捕获
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone2.call();
            },"B").start();
        }
    }
    
    class Phone4{
        public static synchronized void sendSms(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    

    输出结果:

    打电话
    发短信
    

    小结

    new this 具体的一个手机
    static Class 唯一的一个模板

    集合类不安全

    List不安全

    //java.util.ConcurrentModificationException 并发修改异常!
    public class ListTest {
        public static void main(String[] args) {
    
            List<Object> arrayList = new ArrayList<>();
    
            for(int i=1;i<=10;i++){
                new Thread(()->{
                    arrayList.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }
    
        }
    }
    

    会导致 java.util.ConcurrentModificationException 并发修改异常!

    解决方案:

    • List list = new Vector<>();
    • List list = Collections.synchronizedList(new ArrayList<>());
    • List list = new CopyOnWriteArrayList<>();

    推荐使用第三种的CopyOnWriteArrayList!

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

    CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

    核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

    读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

    多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;

    CopyOnWriteArrayList比Vector厉害在哪里?

    Vector底层是使用synchronized关键字来实现的:效率特别低下。

    CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

    Set不安全

    Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

    解决方案还是两种:

    • 使用Collections工具类的synchronized包装的Set类
      • Set<String> set = Collections.synchronizedSet(new HashSet<>());
    • 使用CopyOnWriteArraySet 写入复制的JUC解决方案(推荐!)
      • Set<String> set = new CopyOnWriteArraySet<>();

    示例代码:

    public class SetTest {
        public static void main(String[] args) {
    
            Set<String> set = new CopyOnWriteArraySet<>();
    
            for (int i = 1; i <= 30; i++) {
                new Thread(() -> {
                    set.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(set);
                },String.valueOf(i)).start();
            }
        }
    }
    

    HashSet底层是什么?

    hashSet底层就是一个HashMap;

    Map不安全

    回顾Map基本操作,看下hashmap源码

        /**
         * The default initial capacity - MUST be a power of two.
         */
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    
        /**
         * The maximum capacity, used if a higher value is implicitly specified
         * by either of the constructors with arguments.
         * MUST be a power of two <= 1<<30.
         */
        static final int MAXIMUM_CAPACITY = 1 << 30;
    
        /**
         * The load factor used when none specified in constructor.
         */
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
    

    源码中从上到下三个常量:

    • DEFAULT_INITIAL_CAPACITY:默认初始容量16
    • MAXIMUM_CAPACITY:最大容量1 << 30,即 2^30=1073741824
    • DEFAULT_LOAD_FACTOR: 负载因子:默认值为0.75。 当元素的总个数>当前数组的长度 * 负载因子。

    同样的HashMap基础类也存在并发修改异常!

    解决方案:

    • 使用Collections工具类的synchronizedMap包装的Map类
      • Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
    • 使用ConcurrentHashMap(推荐!)
      • Map<String, String> map = new ConcurrentHashMap<>();
      • ConcurrentHashMap是面试中常问的问题

    示例代码:

    public class MapTest {
        public static void main(String[] args) {
            /**
             * 解决方案
             * 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
             *  Map<String, String> map = new ConcurrentHashMap<>();
             */
            Map<String, String> map = new ConcurrentHashMap<>();
            //加载因子、初始化容量
            for (int i = 1; i < 100; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    }
  • 相关阅读:
    git 学习
    C语言 do while 语句
    C语言 计算班级平均成绩以及不及格的人数
    C语言 加减算法
    C语言 两个日期间的天数计算
    C语言 梯形面积
    C语言 while语句
    C语言 分段函数if else语句
    C语言 乘法运算
    C语言学习,for循环
  • 原文地址:https://www.cnblogs.com/kylinxxx/p/14091983.html
Copyright © 2020-2023  润新知