JAVA之JUC并发编程超详细保姆级入门笔记!看完不会算我输!

1. 环境搭配

保证三个1.8:




2. JUC概述

介绍

java.util.concurrent工具包!

线程和进程

  1. Java默认只有2个进程:main和GC

  2. Java语言实际上无法真正开启进程:

    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 */
            }
        }
    }
    
    //这是个本地方法,java无法直接操作硬件,底层是C++
    private native void start0();
    

并发

  1. 并发:多线程操作同一个资源
  2. 并行编程的本质:充分利用CPU的资源



3. 线程和进程

线程状态

6个:创建、运行、阻塞、等待、超时等待、终止

wait和sleep的区别

区别 wait sleep
所属类 Object thread
锁的释放 释放锁 不释放锁
使用范围 必须在同步代码块中 任何地方
异常捕获 不需捕获 必须捕获异常

线程就是一个单独的资源类,没有任何附属的操作!




4. Lock锁

Lock实现类

  1. ReentrantLock:可重入锁(最常用),分为两类!
    • 公平锁:十分公平,可以先来后到
    • 非公平锁:十分不公平,可以插队(默认 3h 3s)
  2. ReadLock:读锁
  3. WriteLock:写锁

synchronized锁和lock锁的区别

  1. synchronized 是内置的Java关键字,lock 是一个Java类
  2. synchronized 无法获取锁的状态,lock 可以判断是否获取到了锁
  3. synchronized 会自动释放锁,lock 必须手动释放,如果不释放,会死锁!
  4. synchronized 线程1(获得锁,然后阻塞),线程2会一直等待锁的释放,lock锁不一定(trylock方法尝试获取锁)
  5. synchronized 可重入锁,不可以中断,非公平;lock 可重入锁,可以判断锁,非公平(可以自己设置是否公平
  6. synchronized 适合锁少量的代码同步问题,lock 适合锁大量的同步代码
package com.kuang;


public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    

        Ticket ticket = new Ticket();

        new Thread(()->{
    
    
            for (int i = 0; i < 20 ; i++) {
    
    
                try {
    
    
                    ticket.inc();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.dec();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();


    }
}

class Ticket {
    
    
    private int number = 0;
    public synchronized void inc() throws InterruptedException {
    
    
        if(number!=0){
    
    
            wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
    
    
        if(number!=1){
    
    
            wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();
    }


}

问题?如果多加两个进程参与加减呢?

package com.kuang;


public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    

        Ticket ticket = new Ticket();

        new Thread(()->{
    
    
            for (int i = 0; i < 20 ; i++) {
    
    
                try {
    
    
                    ticket.inc();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.dec();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.inc();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.dec();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Ticket {
    
    
    private int number = 0;
    public synchronized void inc() throws InterruptedException {
    
    
        while (number!=0){
    
    
            wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
    
    
        while (number!=1){
    
    
            wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();
    }


}

结果:会出现虚假唤醒

解决方案:将wait()放在while中! 或者将wait放在if判断外面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmXvNBMf-1618733889287)(E:\学习笔记\图片\image-20210401230526674.png)]

//方法2:
class Ticket {
    
    
    private int number = 0;
    public synchronized void inc() throws InterruptedException {
    
    
        if(number==0){
    
    
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            notifyAll();
        }

        wait();
    }

    public synchronized void dec() throws InterruptedException {
    
    
        if(number==1){
    
    
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            notifyAll();
        }
       wait();
    }
}

//方法1:

class Ticket {
    
    
    private int number = 0;
    public synchronized void inc() throws InterruptedException {
    
    
        while (number!=0){
    
    
            wait();

        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();

    }

    public synchronized void dec() throws InterruptedException {
    
    
        while (number!=1){
    
    
            wait();

        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        notifyAll();
    }
}

Lock版生产者消费者

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqYCtxqS-1618733889296)(E:\学习笔记\图片\image-20210402100030290.png)]

package com.kuang;


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

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    

        Ticket ticket = new Ticket();

        new Thread(()->{
    
    
            for (int i = 0; i < 20 ; i++) {
    
    
                try {
    
    
                    ticket.inc();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.dec();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.inc();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.dec();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

class Ticket {
    
    
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

//    condition.await()  等待
//    condition.signalAll() 唤醒全部
    public  void inc() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=0){
    
    
                condition.await();

            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }

}
    public  void dec() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=1){
    
    
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

condition:精准通知和唤醒

package com.kuang;


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

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    

        Ticket ticket = new Ticket();

        new Thread(()->{
    
    
            for (int i = 0; i < 20 ; i++) {
    
    
                try {
    
    
                    ticket.incA();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.decB();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.incC();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
    
    
            for (int i = 0; i < 40 ; i++) {
    
    
                try {
    
    
                    ticket.decD();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

class Ticket {
    
    
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    Condition condition4 = lock.newCondition();

//    condition.await()  等待
//    condition.signalAll() 唤醒全部
    public  void incA() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=0){
    
    
                condition1.await();

            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition2.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }

}
    public  void decB() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=1){
    
    
                condition2.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition3.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }


    public  void incC() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=0){
    
    
                condition3.await();

            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition4.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }

    }

    public  void decD() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (number!=1){
    
    
                condition4.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition1.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }

}



5. 八锁现象

介绍

8锁,就是关于锁的八个问题!

  1. 标准情况下,两个线程执行,先打印发短信还是打电话? 1/发短信 2/打电话

    解释:在主线程main中,B线程是延迟一秒创建的,在这一秒中,A已经拿到锁了!synchronized锁的对象是方法的调用者

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) {
          
          
    
            Ticket ticket = new Ticket();
            new Thread(()->{
          
          ticket.message();},"A").start();
    
            try {
          
          
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
    
    
            new Thread(()->{
          
          ticket.call();},"B").start();
    
        }
    }
    
    class Ticket {
          
          
       public synchronized void message(){
          
          
           System.out.println("发消息");
       }
       public synchronized void call(){
          
          
           System.out.println("打电话");
       }
    }
    
  2. 在message中添加5秒的延时,是怎样?1/发短信 2/打电话

    解释:还是因为A线程先拿到这个锁,就算发短信延迟,但是还是没有释放锁啊!

  3. 新增一个普通非同步方法hello(),message中延迟5秒,让B线程调用hello,请问执行结果? 1/hello 2/发短信

    解释:hello方法没有synchronized锁,不是同步方法,不受锁的影响,主线程1s延迟后,创建了B,B执行hello,它不会管此刻仍然在延迟中的线程中的A手中的锁!

  4. 两个ticket,分别在A、B中调用message和call,message中延迟5秒,请问执行结果? 1/打电话 2/发短信

    解释:都不是同一把锁,A中拿到的是第一个ticket的锁,它延迟跟1s后B拿到第二个ticket的锁没任何关系

  5. 将call和message都设置成static,message中有延迟5秒,让A和B分别调用message和call,请问执行结果?1/发短信 2/打电话

    解释:都设置成static,其实都是随着类的加载而产生的,这个时候synchronized锁的是这个类的class实例对象,而并不是锁的调用者对象,因此还是A先拿到锁,但拿的是类Class实例的锁。

  6. 在5的基础上,分别用两个ticket分别在A和B中调用static和message,请问执行结果? 1/发短信 2/打电话

    解释:只要加了static,不管是多少个ticket,锁的都是共有的唯一的那个class实例!因此还是A先拿到这个实例的锁!

  7. 一个延迟4秒的静态同步方法message被A中的ticket调用,一个普通同步方法call被B中的同一个ticket调用,请问执行结果? 1/打电话 2/发短信

    解释:A拿到的是Class实例的锁,B拿到的是调用对象ticket的锁,两个锁不是一个东西,因此B先执行。

  8. 2个不同的ticket,分别在A和B中调用一个延迟4秒的静态同步方法message和一个普通同步方法call,请问执行结果? 1/打电话 2/发短信

    解释:同7!

小结

synchronized只会锁2个东西:

  1. 类的Class实例对象
  2. 调用者实例本身



6. 集合类不安全

6.1 CopyOnWriteArrayList

// 	java.util.ConcurrentModificationException 	并发修改异常
public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        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();
        }
    }
}

介绍

​ 在多线程并发的情况下,ArrayList并不安全,会报 java.util.ConcurrentModificationException并发修改异常,如何解决这个问题呢?

  1. 用Vector

  2. List<String> list = Collections.synchronizedList(new ArrayList<>());
    
  3. List<String> list = new CopyOnWriteArrayList<>();
    

CopyOnWrite

  1. 写入时复制,COW,是计算机设计领域的一种优化策略
  2. 当多个线程调用ArrayList的时候,读是固定的,而写不一定是固定的,后者的写操作可能会覆盖前者,为了避免覆盖,才使用写入时复制。
  3. CopyOnWrite比Vector好在哪?Vector底层的add方法加了synchronized,方法效率低!


6.2 CopyOnWriteArraySet

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

解决方式

  1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
    
  2. Set<String> set = new CopyOnWriteArraySet<>();
    


6.3 ConcurrentHashMap

public class SaleTicketDemo1 {
    
    
   public static void main(String[] args) {
    
    
       Map<String,Object> map = new HashMap();

       for (int i = 0; i <30 ; i++) {
    
    
           new Thread(()->{
    
    
               map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
               System.out.println(map);
           },String.valueOf(i)).start();
       }
   }
}

解决方式

  1. Map<String,Object> map = Collections.synchronizedMap(new HashMap<>());
    
  2. Map<String,Object> map = new ConcurrentHashMap<>();
    



7. Callable

介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxW72AJ1-1618733889297)(E:\学习笔记\图片\image-20210403170816314.png)]

  1. 有返回值
  2. 可以抛出异常
  3. 方法不同,call()

关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pXmy0Vlr-1618733889299)(E:\学习笔记\图片\image-20210403173438181.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxcMxZMu-1618733889300)(E:\学习笔记\图片\image-20210403174312593.png)]

代码测试

package com.kuang;


import java.sql.Array;
import java.sql.Time;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;



public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer>{
    
    

    @Override
    public Integer call() throws Exception {
    
    
        System.out.println("call()");  		// 结果只会打印一次call()
        return 1024;
    }
}

细节:

  1. 有缓存
  2. 结果可能需要等待,会阻塞



8. 三大常用辅助类

8.1 CutDownLatch

介绍

是一个减法计数器

测试

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 总数是6,必须要执行任务时再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <=6 ; i++) {
    
    
            new Thread(()->{
    
    
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown();  // 数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); //等待计数器归零,然后再向下执行
        System.out.println("Close");
    }
}

原理()

  1. countDownLatch.countDown():数量-1
  2. countDownLatch.await():等待计数器归零,然后再向下执行
  3. 每次线程调用countDown()都会使countDownLatch数量-1,当数量为0的时候,唤醒await()方法,继续往下执行


8.2 CyclicBarrier

介绍(加法计数器)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXEA6MHZ-1618733889301)(E:\学习笔记\图片\image-20210403180522872.png)]

测试

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 集齐7颗龙珠召唤神龙

        //召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
    
    
            System.out.println("召唤神龙成功");
        });
        for (int i = 1; i <= 7; i++) {
    
    
            int temp = i;
            new Thread(()->{
    
    
                System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");
                try {
    
    
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

await方法会进行减法计数,计数完了之后会开启一条新的线程执行!



8.3 Semaphore

介绍

测试

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 线程数量:停车位 主要用来限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
    
    
            new Thread(()->{
    
    
                // acquire() 得到
                try {
    
    
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    semaphore.release();// release() 释放
                }

            },String.valueOf(i)).start();
        }
    }
}

原理

semaphore.acquire():获得。假设车位已经满了,会等待直到被释放为止

semaphore.release():释放。会将当前的信号量释放,然后唤醒等待的线程

作用:多个共享线程互斥的使用。并发限流,控制最大的线程数!




9. 读写锁

ReadWriteLock

测试

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        MyCache myCache = new MyCache();

        for (int i = 1; i <= 5 ; i++) {
    
    
            int temp = i;
            new Thread(()->{
    
    
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= 5 ; i++) {
    
    
            int temp = i;
            new Thread(()->{
    
    
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }

    }
}

class MyCache{
    
    
    private volatile Map<String,Object> map = new HashMap<>();

    //读写锁:更加细粒度的控制
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //存,写入的时候只希望同时只有一个线程往里边写
    public void put(String key,Object value){
    
    
        readWriteLock.writeLock().lock();


        try {
    
    
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }

    //取,读
    public void get(String key){
    
    
        readWriteLock.readLock().lock();

        try {
    
    
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }

    }
}

分析

  1. 独占锁(写锁)、共享锁(读锁)



10. 阻塞队列

介绍

  1. Queue不是新的东西,是跟list和set同级的Collection子类
  2. BlockingQueue的使用场景:多线程并发处理、线程池


10.1 四组API

方式 抛出异常 不会抛出异常,有返回值 阻塞等待 超时等待
添加 add offer put offer(,,)
移除 remove poll take poll(,,)
判断队列首部 element peek - -
  1. 抛出异常

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            test1();
        }
    
        public static void test1(){
          
          
            //队列的大小是3
            ArrayBlockingQueue blockingqueue = new ArrayBlockingQueue<>(3);
    
            System.out.println(blockingqueue.add('a'));
            System.out.println(blockingqueue.add('a'));
            System.out.println(blockingqueue.add('a'));
    
            // 抛出异常 Exception in thread "main" java.lang.IllegalStateException: Queue full
            // System.out.println(blockingqueue.add('a'));
            System.out.println("========================");
    
    	System.out.println(blockingqueue.element());	//查看队首元素
            System.out.println(blockingqueue.remove());
            System.out.println(blockingqueue.remove());
            System.out.println(blockingqueue.remove());
            // 抛出异常 Exception in thread "main" java.util.NoSuchElementException
            // System.out.println(blockingqueue.remove());
    
        }
    }
    
  2. 不会抛出异常,有返回值

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            test1();
        }
    
        public static void test1(){
          
          
            //队列的大小是3
            ArrayBlockingQueue blockingqueue = new ArrayBlockingQueue<>(3);
    
            System.out.println(blockingqueue.offer('a'));   // true
            System.out.println(blockingqueue.offer('b'));   // true
            System.out.println(blockingqueue.offer('c'));   // true
            System.out.println(blockingqueue.offer('d'));  // 不抛出异常 false
    
            System.out.println("==============================");
    
            System.out.println(blockingqueue.peek());	//检测队首元素
            System.out.println(blockingqueue.poll());
            System.out.println(blockingqueue.poll());
            System.out.println(blockingqueue.poll());
            System.out.println(blockingqueue.poll()); // 不抛出异常 null
    
        }
    }
    
  3. 阻塞等待

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            test1();
        }
    
        public static void test1() throws InterruptedException {
          
          
            //队列的大小是3
            ArrayBlockingQueue blockingqueue = new ArrayBlockingQueue<>(3);
    
            blockingqueue.put("a");
            blockingqueue.put("b");
            blockingqueue.put("c");
    //        blockingqueue.put("d"); // 队列没有位置 一直阻塞
            System.out.println(blockingqueue.take());
            System.out.println(blockingqueue.take());
            System.out.println(blockingqueue.take());
            System.out.println(blockingqueue.take());  // 没有这个元素,一直阻塞
    
    
        }
    }
    
  4. 等待超时

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            test1();
        }
    
        public static void test1() throws InterruptedException {
          
          
            //队列的大小是3
            ArrayBlockingQueue blockingqueue = new ArrayBlockingQueue<>(3);
    
            blockingqueue.offer("a");
            blockingqueue.offer("b");
            blockingqueue.offer("c");
    //        blockingqueue.offer("d",2,TimeUnit.SECONDS);   // 等待 超过2秒就退出
    
            System.out.println("=================");
    
            blockingqueue.poll();
            blockingqueue.poll();
            blockingqueue.poll();
            blockingqueue.poll(2,TimeUnit.SECONDS); // 等待 超过2秒就退出
    
        }
    }
    


10.2 同步队列

SynchronousQueue

  1. 没有容量,进去一个元素后,必须取出来之后才能再往里面放入一个元素
  2. put放入,take取出

测试

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        test1();
    }

    public static void test1() throws InterruptedException {
    
    
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();   // 同步队列

        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName()+" put 1");
            try {
    
    
                synchronousQueue.put("1");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" put 2");
            try {
    
    
                synchronousQueue.put("2");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" put 3");
            try {
    
    
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"T2").start();
    }
}




11. 线程池

11.1 池化技术及应用

池化技术

  1. 程序的运行,本质:占用系统的资源!需要优化资源的使用==>池化技术,例如线程池、连接池、内存池、对象池,创建和销毁十分浪费资源

  2. 池化技术:事先准备好一些资源,有人要用就来这里拿,用完之后还过来

  3. 线程池的好处:

    • 降低资源的消耗
    • 提高相应的速度
    • 方便管理线程

    即线程复用、可以控制最大并发数和管理线程

线程池

线程池分类

线程池类型 用途说明 适用场景
newFixedThreadPool 创建固定线程数的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数永远不会变化 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景
newSingleThreadExecutor 创建只有一个线程的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数只有一个 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景
newCachedThreadPool 创建可供缓存的线程池,该线程池中的线程空闲时间超过60s会自动销毁,使用的是SynchronousQueue特殊无界队列 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景
newScheduledThreadPool 创建可供调度使用的线程池(可延时启动,定时启动),使用的是DelayWorkQueue无界延时队列 适用于需要多个后台线程执行周期任务的场景
newWorkStealingPool jdk1.8提供的线程池,底层使用的是ForkJoinPool实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu核数的线程来并行执行任务 适用于大耗时,可并行执行的场景


11.2 七大参数和自定义线程池

三大方法

//  Executors 工具类 3大方法

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程的线程池
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);// 固定线程数量的线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();// 可伸缩的线程池
        try {
    
    
            for (int i = 1; i <100 ; i++) {
    
    
                //  使用了线程池之后,使用线程池来创建
                threadPool.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName()+" OK");
                });
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //  线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

七大参数

  1. 源码分析

    public static ExecutorService newSingleThreadExecutor() {
          
          
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
          
          
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool() {
          
          
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    // 	本质:ThreadPoolExcutor()
        
        public ThreadPoolExecutor(int corePoolSize,	//	核心线程池大小
                                  int maximumPoolSize,	//	最大线程池大小
                                  long keepAliveTime,	//	超时时间,没人调用就会释放
                                  TimeUnit unit,	//	超时单位
                                  BlockingQueue<Runnable> workQueue,	//	阻塞队列
                                  ThreadFactory threadFactory,	//	线程工厂,创建线程的,一般不动
                                  RejectedExecutionHandler handler) {
          
          	//	拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    
    

  2. 四种拒绝策略

    • AbortPolicy:超过最大承载数了还有线程进来,不处理并报错
    • CallerRunsPolicy:哪来的去哪里,是main方法执行
    • DiscardPolicy:超过最大承载数了就会丢掉任务,不会抛出异常
    • DiscardOlderstPolicy:队列满了,尝试去和最早的竞争也不会抛出异常

手动创建线程池

public class SaleTicketDemo1 {
    
    
    public static void main(String[] args) {
    
    
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        try {
    
    
            //  最大承载 = queue + max
            //  超出最大承载,抛出RejectedExecutionException
            for (int i = 1; i <= 9 ; i++) {
    
    
                threadPool.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            threadPool.shutdown();
        }
    }

}

问题:最大线程到底该如何定义?

  1. CPU密集型:几核就是几,可以保证CPU效率最高!

    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            2,
            Runtime.getRuntime().availableProcessors(),		// 最大定义为「核数」
            3,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardOldestPolicy());
    
  2. IO密集型:判断你系统中十分消耗IO的线程数量,设置数值大于这个数就行了,一般可以设置2倍!




12. 四大函数式接口

介绍

只有一个方法的接口,简化了编程模型,在新版本的框架底层大量应用!

@FunctionalInterface
public interface Runnable {
    
    
    public abstract void run();
}
// foreach(消费者类型的函数式接口)

测试

  1. Function函数型接口,有一个输入参数,有一个输出参数

    只要是函数式接口,都能用lambda表达式简化!

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) {
          
          
            //工具类:输出输入的值
    //        Function function = new Function<String,String>() {
          
          
    //            @Override
    //            public String apply(String str) {
          
          
    //                return str;
    //            }
    //        };
            Function<String,String> function = (str)->{
          
          return str;};
            System.out.println(function.apply("asd"));		//	asd
        }
    }
    
  2. Predicate断定型接口,有一个输入参数,返回值只能是布尔值

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) {
          
          
            Predicate<String> predicate = str -> {
          
          return str.isEmpty();};
            System.out.println(predicate.test(""));		//	true
        }
    }
    
  3. Consumer消费型接口,只有输入参数没有返回值

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) {
          
          
           Consumer<String> consumer = (str)->{
          
           System.out.println(str);};
           consumer.accept("aaa");  //  aaa
        }
    }
    
  4. Supplier供给型接口,只有返回值没有输入参数

    public class SaleTicketDemo1 {
          
          
        public static void main(String[] args) {
          
          
           Supplier supplier = () -> {
          
          return 1024;};
           supplier.get();  //  aaa
        }
    }
    



13. Stream流式计算

介绍

//  现有五个用户,要求一行代码实现以下筛选:
//  1.ID必须是偶数
//  2.年龄必须大于23岁
//  3.用户名转为大写字母
//  4.用户名字母倒着排序
//  5.只能输入一个用户

    public class SaleTicketDemo1 {
    
    
        public static void main(String[] args) {
    
    
            User u1 = new User(1,"a",21);
            User u2 = new User(2,"b",22);
            User u3 = new User(3,"c",23);
            User u4 = new User(4,"d",24);
            User u5 = new User(6,"e",25);

            //  集合就是存储
            List<User> list = Arrays.asList(u1,u2,u3,u4,u5);

            //  计算交给String流
            list.stream()
                    .filter(u->{
    
    return u.getId()%2==0;})
                    .filter(u->{
    
    return u.getAge()>23;})
                    .map((u)->{
    
    return u.getName().toUpperCase();})
                    .sorted((uu1,uu2)->{
    
    return uu2.compareTo(uu1);})
                    .limit(1)
                    .forEach(System.out::println);
        }
    }
@AllArgsConstructor
@Data
@NoArgsConstructor
class User {
    
    
        private int id;
        private String name;
        private int age;
}



14. ForkJoin

介绍

在JDK1.7出现,将大任务拆成小任务,并行执行任务,在大数据量的背景下能提高效率。

特点:工作窃取

这个里面维护的都是双端队列,因此两个线程都可以获取工作任务进行操作从而提高效率。

使用

  1. forkjoinPool:通过它来执行

  2. 计算任务forkjoinPool.execute(forkjointask tast)

  3. forjointask:即计算类要继承forkjointask

测试

package com.kuang;

import java.util.concurrent.RecursiveTask;

public class qwe extends RecursiveTask<Long> {
    
    
    private Long start;
    private Long end;

    private Long temp = 10000L;
    public qwe(Long start,Long end){
    
    
        this.start=start;
        this.end=end;
    }
    // 计算方法
    @Override
    protected Long compute() {
    
    
        if((end-start) > temp) {
    
    
            // 分支合并计算
            Long sum = 0L;
            for (Long i = start; i < end; i++) {
    
    
                sum +=i;
            }
            return sum;
        }else {
    
         // forkjoin
            long middle = (start+end)/2;
            qwe task1 = new qwe(start, middle);
            task1.fork();   // 拆分任务,把任务压入线程队列
            qwe task2 = new qwe(middle+1, end);
            task2.fork();   // 拆分任务,把任务压入线程队列
            return  task1.join()+task2.join();
        }
    }
}
package com.kuang;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        test3();
    }
    public static void test1(){
    
    
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i < 10_0000_0000; i++) {
    
    
            sum +=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间:"+(end - start));
    }



    public static void test2() throws ExecutionException, InterruptedException {
    
    
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new qwe(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务 有结果
        Long sum = submit.get();

        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间 "+(end - start));
    }




    public static void test3(){
    
    
        long start = System.currentTimeMillis();
        // Stream并行流
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间"+(end - start));
    }
}



15. 异步回调

介绍

public class Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //  没有返回值的runAsync异步回调
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
        });
        System.out.println("1111111111111");
        completableFuture.get();    //获取执行结果
    }
}
public class Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->{
    
    
            return 1024;
        });
        System.out.println(completableFuture1.whenComplete((t, u) -> {
    
    
            System.out.println("t=" + t);
            System.out.println("u=" + u);
        }).exceptionally((e) -> {
    
    
            System.out.println(e.getMessage());
            return 233;     // 可以获取到错误的返回结果
        }).get());
    }
}



16. JMM

Volatile

  1. Volatile是Java虚拟机提供轻量级的同步机制
  2. 保证可见性
  3. 不保证原子性
  4. 禁止指令重排

JMM

  1. JMM:Java内存模型,不存在的东西,是一种概念和约定!

  2. 关于JMM的一些同步的约定:

    • 线程解锁前:必须把共享内存立刻刷回主存
    • 线程加锁前:必须读取主存中的最新值到工作内存中
    • 加锁和解锁是同一把锁
  3. 8种操作:

    内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

    • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令

    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用

    • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

    JMM对这八种指令的使用,制定了如下规则:

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

    • 不允许一个线程将没有assign的数据从工作内存同步回主内存

    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题:程序不知道主内存中的值已经被修改过了!



16.1. Volatile

保证可见性

public class Test {
    
    
    // 加了volatile后,保证了可见性,循环检测到了num被修改,停止了
    private static volatile int num = 0;
    public static void main(String[] args) {
    
       // main线程
        new Thread(()->{
    
    
            while (num==0){
    
    

            }
        }).start();
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);

    }
}

不保证原子性

原子性:不可分割!线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功要么同时失败!

public class Test {
    
    
    private volatile static int num = 0;
    public static void add(){
    
    
        num++;
    }
    public static void main(String[] args) {
    
       // main线程
        //理论上num结果为2万
        for (int i = 1; i <= 20 ; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 0; j < 1000 ; j++) {
    
    
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
    
     // main  gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+num);   // 不是20000
    }
}

如果不加lock和synchronized,怎么保证原子性?

使用原子类来解决原子性问题!

原子类

public class Test {
    
    
    private volatile static AtomicInteger num =new  AtomicInteger();
    public static void add(){
    
    
        num.getAndIncrement();  //  AtomicInteger + 1  CAS
    }
    public static void main(String[] args) {
    
       // main线程
        //理论上num结果为2万
        for (int i = 1; i <= 20 ; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 0; j < 1000 ; j++) {
    
    
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
    
     // main  gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+num);   // 20000
    }
}

这些类的底层都是和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

指令重排

  1. 你写的程序,计算机并不是按照你写的顺序去执行的!

  2. 源代码——>编译器优化的重排——>指令并行也可能会重排——>内存系统也会重排——>执行

  3. 处理器在进行指令重排的时候会考虑数据之间的依赖性问题!

int x = 1;	// 1
int y = 2;	//2
x = x + 5;	//3
y = x * x;	//4
我们所期望的是 「1234」,但是可能执行的时候会编程「2134」、「1324」
可不可能是「4123」  不会!
  1. 可能造成的结果:a、b、x、y四个值默认都是0:

    线程A 线程B
    x=a y=b
    b=1 a=2

    正常结果:x=0;y=0;但是可能由于指令重排,会出现以下指令顺序结果:

    线程A 线程B
    b=1 a=2
    x=a y=b

    指令重排导致的诡异结果:x=2;y=1

  2. 加了volatile可以避免指令重排:

    内存屏障,是一个CPU指令,作用:

    • 保证特定的操作的执行顺序

    • 保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)




17. 单例模式

饿汉式

package com.kuang;

public class Hungry {
    
    
    private Hungry(){
    
    

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
    
    
        return HUNGRY;
    }
}

懒汉式

package com.kuang;

public class LazyMan {
    
    
    private LazyMan(){
    
    
        System.out.println(Thread.currentThread().getName()+" OK");
    }

    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
    
    
        if(lazyMan==null){
    
    
            synchronized (LazyMan.class){
    
    
                if(lazyMan ==null){
    
    
                    lazyMan = new LazyMan();    // 不是原子性操作
                    // 1. 分配内存空间
                    // 2.执行构造方法,初始化对象
                    // 3.这个对象指向这个空间  在多线程下,这个过程发生指令重排 例如 A 132当在3的时候,B会直接return lazyMan
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
    
    
        for (int i = 1; i < 10 ; i++) {
    
    
            new Thread(()->{
    
    
                LazyMan.getInstance();
            }).start();
        }
    }
}

内部类

package com.kuang;

import com.sun.org.apache.bcel.internal.classfile.InnerClass;

public class Holder {
    
    
    private  Holder(){
    
    

    }

    public static Holder getInstance(){
    
    
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
    
    
        private static final Holder HOLDER = new Holder();
    }
}

枚举方式

package com.kuang;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum  EnumSingle {
    
    
    INSTANCE;
    public EnumSingle getInstance(){
    
    
        return INSTANCE;
    }
}

class Test{
    
    
    public static void main(String[] args) throws Exception {
    
    
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
        //Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:416)
    }
}



18. 深入理解CAS

CAS

  1. CAS:compareAndSet,比较并交换!
  2. CAS是CPU的并发原语
package com.kuang;

import java.util.concurrent.atomic.AtomicInteger;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //     public final boolean compareAndSet(int expect, int update) {
    
    
        // 期望、更新
        // 如果我期望的值达到了,那么就更新,否则就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe

在这里插入图片描述

总结

  1. CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环!

  2. 缺点:

    • 底层的自旋锁,循环耗时

    • 一次性只能保证一个共享变量的原子性

    • 会存在ABA问题




19. 原子引用

ABA问题

package com.kuang;

import java.util.concurrent.atomic.AtomicInteger;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        // 对于我们平时写的sql:乐观锁!
        // 期望、更新
        // 如果我期望的值达到了,那么就更新,否则就不更新
        // ========================捣乱的线程============================
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());

        // ========================期望的线程============================


        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }
}

原子引用

带版本号的原子操作!

解决ABA问题,引入原子引用,对应的思想:乐观锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bolCj59J-1618733889304)(E:\学习笔记\图片\image-20210406170621358.png)]

public class Test {
    
    
    public static void main(String[] args) {
    
    
//        AtomicInteger atomicInteger = new AtomicInteger(2020);
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);

        new Thread(()->{
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1=>"+stamp);

            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2=>"+atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3=>"+atomicStampedReference.getStamp());

        },"a").start();

        // 乐观锁的原理相同
        new Thread(()->{
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1=>"+stamp);
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();
    }
}



20. 可重入锁

介绍

  1. 可重入锁也叫递归锁

  2. Synchorized:拿一把锁里面的也拿到

    public class Test {
          
          
        public static void main(String[] args) {
          
          
            Phone phone = new Phone();
            new Thread(()->{
          
          
                phone.message();
            },"A").start();
    
            new Thread(()->{
          
          
                phone.message();
            },"B").start();
        }
    }
    
    class Phone{
          
          
        public synchronized void message(){
          
          
            System.out.println(Thread.currentThread().getName()+"发短信");
            call();
        }
        public synchronized void call(){
          
          
            System.out.println(Thread.currentThread().getName()+"打电话");
        }
    }
    

  3. lock:实际是两把锁!

    package com.kuang;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test {
          
          
        public static void main(String[] args) {
          
          
            Phone phone = new Phone();
            new Thread(()->{
          
          
                phone.message();
            },"A").start();
    
            new Thread(()->{
          
          
                phone.message();
            },"B").start();
        }
    }
    
    class Phone{
          
          
        ReentrantLock lock = new ReentrantLock();
        public  void message(){
          
          
            lock.lock();
            try {
          
          
                System.out.println(Thread.currentThread().getName()+"发短信");
                call();
            } catch (Exception e) {
          
          
                e.printStackTrace();
            } finally {
          
          
            lock.unlock();
            }
        }
        public  void call(){
          
          
            lock.lock();
            try {
          
          
                System.out.println(Thread.currentThread().getName()+"打电话");
            } catch (Exception e) {
          
          
                e.printStackTrace();
            } finally {
          
          
                lock.unlock();
            }
        }
    }
    



21. 自旋锁

介绍

public class TestOne {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Test lock = new Test();

        new Thread(()->{
    
    
            lock.mylock();
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.myUnlock();
            }
        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
    
    
            lock.mylock();
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.myUnlock();
            }
        },"T2").start();
    }
}
public class Test {
    
    
       AtomicReference<Thread> atomicReference = new AtomicReference<>();
       // 加锁
       public void mylock(){
    
    
           Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"===> mylock");
            // 自旋锁
            while (!atomicReference.compareAndSet(null,thread)){
    
    
            }
        }
        // 解锁
        public void myUnlock(){
    
    
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"===> myUnlock");
            atomicReference.compareAndSet(thread,null);

            }
        }



22. 死锁排查

死锁

测试

public class Ran {
    
    
    public static void main(String[] args) {
    
    
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}
class MyThread implements Runnable{
    
    
    private String lockA;
    private String lockB;

    public MyThread(String lockA,String lockB){
    
    
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @SneakyThrows
    @Override
    public void run() {
    
    
        synchronized (lockA){
    
    
            System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"想获取"+lockB);
            TimeUnit.SECONDS.sleep(2);
            synchronized (lockB){
    
    
                System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"想获取"+lockA);

            }
        }
    }
}

解决

  1. 使用jps-l定位进程号!

  2. 使用jstack 进程号找到死锁问题

  3. 日常问题排查:

    • 日志
    • 堆栈信息

猜你喜欢

转载自blog.csdn.net/lyyrhf/article/details/115833396
今日推荐