JUC总结详细笔记

 1 介绍

JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

 

 2 线程与进程

进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。Java默认有2个线程,main线程、GC线程。

线程:通常在一个进程中可以包一个或多个线程,线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。Java 无法直接操作硬件,java通过调用本地方法开启线程

并发:多线程操作同一个资源。

  • CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。

并行:多台机器一起运行。

  • CPU多核,多个线程可以同时执行,可以使用线程池。

并发编程的本质:充分利用CPU的资源,提高效率

线程运行原理

虚拟机栈与栈帧:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当java中使用多线程时,每个线程都会维护它自己的栈帧。每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

线程上下文切换(Thread Context Switch):因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

线程的6个状态:

public enum State {
    	//新生
        NEW,

    	//运行
        RUNNABLE,

    	//阻塞
        BLOCKED,

    	//等待
        WAITING,

    	//超时等待
        TIMED_WAITING,

    	//终止
        TERMINATED;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

wait/sleep 区别:

(1)来自不同的类 wait => Object sleep => Thread

(2)关于锁的释放 wait 会释放锁,sleep 抱着锁睡觉,不会释放

(3)使用的范围是不同的 wait必须在同步代码块中,sleep 可以再任何地方

(4)是否需要捕获异常 wait 不需要捕获异常 sleep 必须要捕获异常

3 Lock锁(重点)

3.1 传统的Synchronized

/**
 * 真正的多线程开发
 * 线程就是一个单独的资源类,没有任何的附属操作!
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //多线程操作
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
        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();
    }
}

//资源类
class Ticket{
    //属性+方法
    private int number=50;

    //卖票的方式
    // synchronized 本质:队列,锁
    public synchronized void sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
            number--;
        }
    }
}

3.2 Lock 接口

public interface Lock {
    /**
     * 获取锁,调用该方法的线程会获取锁,如果锁被暂用则一直等待
     */
    void lock();
    /**
     * 可响应中断。即在获取锁的过程中可以中断当前线程
     */
    void lockInterruptibly() throws InterruptedException;
    /**
     * 尝试非阻塞的获取锁,调用该方法之后会立即返回,如果获取到锁就返回true否则返回false
     */
    boolean tryLock();
    /**
     * 超时的获取锁,下面的三种情况会返回
     * ①当前线程在超时时间内获取到了锁
     * ②当前线程在超时时间内被中断
     * ③超时时间结束,返回false
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    /**
     * 释放锁
     */
    void unlock();
    /**
     * 获取等待通知组件,该组件和当前锁绑定,当前线程只有获取到了锁才能调用组件的wait方法,调用该方法之后会释放锁
     */
    Condition newCondition();
}
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //多线程操作
        //并发:多线程操作同一个资源类,把资源类丢入线程
        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=50;
    Lock lock=new ReentrantLock();
    // 卖票的方式
    // 使用Lock锁
    public void sale(){
        //加锁
        lock.lock();
        try {
            //业务代码
            if(number>=0){
                System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
                number--;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            //解锁
            lock.unlock();
        }
    }
}

公平锁: 十分公平,必须先来后到;多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁,其他线程阻塞,cpu唤醒阻塞线程的开销会很大。

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

非公平锁(默认): 十分不公平,可以插队;多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁,如果抢锁成功就会减少唤醒阻塞线程的次数,所以非公平锁性能高于公平锁性能。还有就是这样的场景:当线程A执行时间为30分钟,线程B执行时间为1分钟,这个时候当然先执行再执行A。如果是公平锁就会先执行A再执行B,这样B就会等待30分钟。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必每次都去唤醒下一个线程,会减少唤起线程的数量。
  • 缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

可以发现,如果线程一旦获取锁失败进入到AQS同步队列后,就一直排队,会直到其他获取到锁的线程唤醒它或者它被其他线程中断,才会出队。特殊情况,如果当T1释放锁时,AQS同步队列外部没有线程来争抢锁,这时候就非公平锁又是公平的。

3.3 Synchronized和Lock的区别

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

2、Synchronized 无法判断获取锁的状态,Lock可以判断tryLock(),设置超时时间tryLock(long timeout, TimeUnit unit)

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

4、Synchronized 是可重入锁,不可以中断的,非公平的;Lock是可重入的,可以判断是否获取锁和设置超时时间,可以中断,可以自己设置公平锁和非公平锁;

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

 4 生产者和消费者问题

4.1 Synchronized版本

public class A {
    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 number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        //if改为while即可,防止虚假唤醒
        while(number!=0){
            //等待操作
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程 我+1完毕了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        //if改为while即可,防止虚假唤醒
        while(number==0){
            //等待操作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程  我-1完毕了
        this.notifyAll();
    }
}

4.2 JUC版本,await、signal 替换 wait、notify

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.increment();
        }
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.decrement();
        }},"B").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.increment();
        }
        },"C").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.decrement();
        }
        },"D").start();
    }
}
class Data2{
    //数字资源类
    private int number = 0;
    //lock锁
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //+1
    public void increment()  {
        lock.lock();
        try{
            //业务
            while(number!=0){
                //等待操作
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //-1
    public void decrement()  {
        lock.lock();
        try{
            //业务
            while (number==0){
                //等待操作
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition的优势:精准的通知和唤醒的线程,可以指定通知的下一个进行顺序执行

/**
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
 */

public class C {

    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 number = 1; //1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=1){
                //等待
                condition1.await();
            }
            //操作
            System.out.println(Thread.currentThread().getName()+",AAAAA");
            //唤醒指定的线程
            number=2;
            condition2.signal(); // 唤醒2
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+",BBBBB");
            //唤醒3
            number=3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+",CCCCC");
            //唤醒1
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

5 八锁现象

如何判断锁的是谁,锁到底锁的是谁?

锁会锁住:对象、Class,这类问题要判断是否是同一把锁

/**
 * 8锁,就是关于锁的8个问题
 * 1、标准情况下,两个线程先打印,先发短信还是先打电话? 发短信
 * 2、sendSms延迟4秒,两个线程先打印,先发短信还是先打电话? 发短信
 * 原因:synchronized锁的对象是方法的调用,对于两个方法都是对象锁,而且又是同一个对象调用
 */
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("打电话");
    }
}
/**
 * 3、 增加了一个普通方法后,先执行发短信还是Hello? Hello普通方法
 * 原因:hello是一个普通方法,不受synchronized锁的影响
 * 4、 两个对象,两个同步方法,先发短信还是打电话? 打电话
 * 原因:发短信和打电话锁的是对象,但是调用的对象是两个,所以互不影响
 */
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{
    // 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("打电话");
    }
    // 这里没有锁,不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}
/**
 * 5、两个静态的同步方法,只有一个对象,先发短信还是打电话?  发短信
 * 6、两个静态的同步方法,两个对象,先发短信还是打电话?      发短信
 * 原因:对于static静态方法来说,锁的是类Class,不管多少个对象,对于静态的锁都只有一把锁
 */
public class Test3 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        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();
    }
}
// Phone3唯一的一个 Class 对象
class Phone3{
    // synchronized 锁的对象是方法的调用者
    // static 静态方法
    // 类一加载就有了,锁的是Class
    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("打电话");
    }
}
/**
 * 7、1个静态的同步方法,1个普通的同步方法,一个对象,先发短信还是打电话?   打电话
 * 8、1个静态的同步方法,1个普通的同步方法,两个对象,先发短信还是打电话?   打电话
 * 原因:static静态方法锁的是类Class,普通同步方法锁的是对象,不是同一把锁
 */
public class Test4 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        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();
    }
}
// Phone3唯一的一个 Class 对象
class Phone4{
    // 静态的同步方法 锁的是 Class 类模板
    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("打电话");
    }
}

6、集合类不安全

6.1 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();
        }

    }
}

解决方案:使用Vector;

Collections.synchronizedList(new ArrayList<>());

使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();

6.2 Set不安全

解决方案: 使用Collections工具类的synchronized包装的Set类;

使用CopyOnWriteArraySet 写入复制的JUC解决方案。

6.3 Map不安全

解决方案: 使用Hashtable;

使用Collections.synchronizedMap(new HashMap<>());

使用ConcurrentHashMap进行并发处理。

7 Callable

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 1; i < 10; i++) {
//            new Thread(new Runnable()).start();
//            new Thread(new FutureTask<>( Callable)).start();
            MyThread thread= new MyThread();
            //适配类:FutureTask
            FutureTask<String> futureTask = new FutureTask<>(thread);
            //放入Thread使用
            new Thread(futureTask,String.valueOf(i)).start();
            //获取返回值
            String s = futureTask.get();
            System.out.println("返回值:"+ s);
        }
    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Call:"+Thread.currentThread().getName());
        return "String"+Thread.currentThread().getName();
    }
}

8 常用的辅助类(必会)

8.1 CountDownLatch

 其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器

//这是一个计数器,减法
public class CountDownLatchDemo {

    public static void main(String[] args) throws 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 door");
    }
}

主要方法:

  • countDown 减一操作;
  • await 等待计数器归零。

await等待计数器为0,就唤醒,再继续向下运行。

CountDownLatch主要的使用场景就是一个线程等待多个线程执行完毕后再执行,任务分解最后汇总。

8.2 CyclickBarrier

其实就是一个加法计数器

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //主线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙~");
        });
        for (int i = 1; i <= 7; i++) {
            //子线程
            int finalI = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
                try {
                    cyclicBarrier.await(); //加法计数 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

  

8.3 Semaphore

Semaphore:信号量

抢车位:3个车位 6辆车

public class SemaphoreDemo {
    public static void main(String[] args) {
        //停车位为3个
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire(); //得到
                    //抢到车位
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位{"+ finalI +"}");
                    TimeUnit.SECONDS.sleep(2); //停车2s
                    System.out.println(Thread.currentThread().getName()+" 离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用

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

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

9、读写锁 ReadWriteLock

先对于不加锁的情况:如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;采用五个线程去写入,使用十个线程去读取。

如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。

可以采用更细粒度的锁:ReadWriteLock 读写锁来保证

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            }).start();
        }
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) {
            int finalI = i;
            new Thread(()->{
                String o = mycache.get(String.valueOf(finalI));
            }).start();
        }
    }
}

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

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();

    public void put(String key,String value){
        //加锁
        readWriteLock.writeLock().lock();
        try {
            //写入
            //业务流程
            System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); //解锁
        }
    }

    public String get(String key){
        //加锁
        String o="";
        readWriteLock.readLock().lock();
        try {
            //得到
            System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
            o = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
        return o;
    }
}

10 阻塞队列

BlockingQueue

blockingQueue 是Collection的一个子类;多线程并发处理、线程池会使用到阻塞队列。

整个阻塞队列的家族:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列

使用阻塞队列

添加、移除

SynchronousQueue同步队列

同步队列没有容量,也可以视为容量为1的队列;

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;Synchronized 和其他的BlockingQueue 不一样,它不存储元素;put了一个元素,就必须从里面先take出来,否则不能再put进去值。并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

11、线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略

查看详情 线程池详解

12、四大函数式接口

函数式接口:只有一个方法的接口

查看详情 java8新特性

13 ForkJoin

分支合并框架,ForkJoin 在JDK1.7,并行执行任务,提高效率;在大数据量速率会更快,大数据中:MapReduce 核心思想->把大任务拆分为小任务。

14 异步回调

14.1 没有返回值的runAsync异步回调

14.2 有返回值的异步回调supplyAsync

15 JMM

JMM 即 JAVA内存模型(Java Memory Model),它从java层面定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响=》解决:synchronized
  • 可见性 - 保证指令不会受 cpu 缓存的影响=》解决:volatile和synchronized
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响=》解决:volatile

在编程中,线程之间的通信机制有两种,共享内存和消息传递。

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

(1)线程解锁前,必须把共享变量立刻刷回主存;

(2)线程加锁前,必须读取主存中的最新值到工作内存中;

(3)加锁和解锁是同一把锁;

线程中分为工作内存、主内存,主要有8种操作:

  • Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
  • Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
  • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
  • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
  • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

JMM对这8种操作给了相应的规定:

  • 不允许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 Volatile(重要)

Volatile 是 Java 虚拟机提供轻量级的同步机制,主要有3个特性。

16.1 保证可见性

当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。

16.2 禁止指令重排

执行程序时,计算机并不是按照写的那样去执行的源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。对 volatile 变量的写指令后会加入写屏障,对读指令前会加入读屏障

内存屏障:CPU指令。作用:

1、保证特定的操作的执行顺序;

2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

 

场景 单例模式  双检锁/双重校验锁(DCL)

16.3 不保证原子性

原子性:不可分割;

线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/**
 * 不保证原子性
 * number <=2w
 * 
 */
public class VDemo02 {

    private static volatile int number = 0;
    public static void add(){
        number++; 
        //++ 不是一个原子性操作,是两个~3个操作:1->get取到number的值,2->进行加1操作,3->把值赋给number
    }

    public static void main(String[] args) {
        //理论上number  === 20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}

 不加lock和synchronized ,通过原子类,JUC下的原子包下的class。

这些类的底层都是通过Unsafe类直接和操作系统挂钩,是在内存中修改值。

public class VDemo02 {
    private static volatile AtomicInteger number = new AtomicInteger();
    public static void add(){
        //number++;
        number.incrementAndGet();  //底层是CAS保证的原子性
    }
    public static void main(String[] args) {
        //理论上number  === 20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }
}

17 深入理解CAS(重要)

CAS比较并交换,有3个操作数,内存值V,预期值A,要修改的新值B。当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

实现原理:Unsafe类

cpmpareAndSet()方法实现 

总结:

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作;如果不是就一直循环,使用的是自旋锁。

缺点:

  • 循环会耗时;
  • 一次性只能保证一个共享变量的原子性;
  • 它会存在ABA问题

 CAS:ABA问题(狸猫换太子)

中间过程被修改,但是结果和预期相同

å¨è¿éæå¥å¾çæè¿°

线程1:期望值是1,要变成2;

线程2:两个操作:

  • 期望值是1,变成3
  • 期望是3,变成1

对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1。

解决方案:使用了乐观锁,增加版本号,原子操作。

原子引用

Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

 所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升

public class CASDemo {
    //AtomicStampedReference注意,如果泛型是一个包装类,注意对象的引用问题
    //正常在业务操作,这里面比较的都是一个个对象
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1,1);
    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            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();
    }
}

 使用大于等于128的时候版本号不会上升

正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。

CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。 

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,吃亏点再重试呗。
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
  • CAS 体现的是无锁并发、无阻塞并发,因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一 但如果竞争激烈(写操作多),可以想到重试必然频繁发生,反而效率会受影响

18 各种锁的理解

18.1 公平锁、非公平锁

文章前面Lock锁章节有介绍

18.2 可重入锁

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

 Synchronized锁

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

lock锁

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone2{
    Lock lock=new ReentrantLock();
    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}
  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

18.3 自旋锁 spinlock

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

自我设计自旋锁:

public class SpinlockDemo {
    //int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"===> mylock");
        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
        }
    }
    //解锁
    public void myunlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

18.4 死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

  • 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值