JUC并发编程学习笔记

目录

一、什么是JUC

2、线程和进程

1.进程

2.线程

3.并发

4.并行

并发编程的本质

3、多线程回顾

1.线程的几种状态

2.sleep和wait的区别

4、Lock锁

1、传统 Synchronized锁

2.公平锁和非公平锁(锁的底层)

3.Synchronized 和 Lock 区别

4.生产者和消费者问题

生产者和消费者问题:synchronized版

问题存在:ABCD4个线程会有虚假唤醒

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

Condition 精准的通知和唤醒线程

5、8锁现象(就是关于锁的八个问题)

6、集合类不安全

ArryList集合

HashSet集合

HashMap集合

7、Callable接口

8、常用的辅助类(必会)

1.CountDownLatch

2.CyclicBarrier

3.Semaphore

9、读写锁ReadWriteLock

10、阻塞队列  BlockingQueue

四组API

SynchronousQueue同步队列

11、线程池 

池化技术

线程池的好处

线程池:3大方法

线程池:7大参数

手动创建一个线程池 

线程池:4种拒绝策略

IO密集型,CPU密集型:(调优)

12、四大函数式接口(必需掌握)

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

Function 函数式接口

Predicate 断定型接口

Consumer 消费型接口

Supplier 供给型接口

13、Stream 流式计算

14、ForkJoin

ForkJoin 

15、异步回调

16、JMM

17、Volatile

1、保证可见性

2、不保证原子性

3、指令重排

18、单例模式

单例模式的几种实现方式

1、懒汉式,线程不安全

2、懒汉式,线程安全

3、饿汉式

4、双检锁/双重校验锁(DCL,即 double-checked locking)

5、登记式/静态内部类

6、枚举

19、深入理解CAS

Unsafe 类 

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

20、原子引用 

21、各种锁的理解

1、公平锁、非公平锁

2、可重入锁

Synchronized 版

Lock 版

3、自旋锁

4、死锁


1、什么是JUC

JUC指的是:Java里的三个包
java.util.concurrent
java.util.concurrent.atomic:原子性
java.util.concurrent.locks:lock锁

2、线程和进程

1.进程

程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。

2.线程

可以指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。

3.并发

同一时刻,多个线程交替执行。(一个CPU交替执行线程)

4.并行

同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)

# 获取cpu的核数(cpu密集型;io密集型)
System.out.println(Runtime.getRuntime().availableProcessors());

并发编程的本质

并发编程的本质是充分利用cpu资源。

Java 真的可以开启线程吗? 答案是:开不了的

Java创建Thread类调用start方法,底层是把线程放到一个组里面,然后调用一个本地方法start0;方法底层是C++;Java无法操作硬件。

3、多线程回顾

1.线程的几种状态

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     * 线程新生状态
     */
    NEW,
    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     * 线程运行中
     */
    RUNNABLE,
    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     * 线程阻塞状态
     */
    BLOCKED,
    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. * 线程等待状态,死等 */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *

    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
* 线程超时等待状态,超过一定时间就不再等 */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. * 线程终止状态,代表线程执行完毕 */ TERMINATED; }

2.sleep和wait的区别

  1. sleep是Thread类的本地方法;wait是Object类的方法。
  2. sleep不释放锁;wait释放锁。
  3. sleep不需要和synchronized关键字一起使用;wait必须和synchronized代码块一起使用。
  4. sleep不需要被唤醒(时间到了自动退出阻塞);wait需要被唤醒。
  5. sleep一般用于当前线程休眠,或者轮循暂停操作;wait则多用于多线程之间的通信。

4、Lock锁

1、传统 Synchronized锁

public class SaleTicketTDemo01 {
    /*
     * 真正的多线程开发,公司中的开发,降低耦合性
     * 线程就是一个单独的资源类,没有任何附属的操作!
     * 1、 属性、方法
     */
    public static void main(String[] args) {
        //并发:多个线程同时操作一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        // @FunctionalInterface 函数式接口,jdk1.8 lambada表达式
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}
//资源类 OOP
class Ticket {
    //属性、方法
    private int number = 50;
    // 卖票的方式
    // synchronized 本质: 队列,锁
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了" +
                    (50-(--number)) + "张票,剩余:" + number + "张票");
        }
    }
}

2.公平锁和非公平锁(锁的底层)

JUC并发编程学习笔记_第1张图片

JUC并发编程学习笔记_第2张图片

  • 公平锁:十分公平,线程执行顺序按照先来后到顺序
  • 非公平锁:十分不公平:可以插队 (默认锁)

将上面的卖票例子用lock锁 替换synchronized:

public class SaleTicketTDemo02 {
    public static void main(String[] args) {
        //并发:多个线程同时操作一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        // @FunctionalInterface 函数式接口,jdk1.8 lambada表达式
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}
//Lock 3步骤
// 1. new ReentrantLock();
// 2. lock.lock()  加锁
// 3. lock.unlock() 解锁
class Ticket2 {
    //属性、方法
    private int number = 50;
    Lock lock = new ReentrantLock();
    // 卖票方式
    public void sale() {
        lock.lock();// 加锁
        try {
            // 业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了" +
                        (50 - (--number)) + "张票,剩余:" + number + "张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 解锁
        }
    }
}

3.Synchronized 和 Lock 区别

  • 1、Synchronized 内置的Java关键字, Lock 是一个Java类
  • 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • 3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  • 4、Synchronized 线程 1(获得锁,如果线程1阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
  • 5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
  • 6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

4.生产者和消费者问题

生产者和消费者问题:synchronized版

/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
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();
        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 number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        /*
        假设 number此时等于1,即已经被生产了产品
        如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权
        假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放锁),然后C试图去执行
        生产方法,但依然判断number!=0成立,则B.wait()开始等待(wait()会释放锁)
        碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();
        这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次
        同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();
        然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1
        if(number != 0){
            // 等待
            this.wait();
        }*/
        while (number != 0) {
     // 注意这里不可以用if 否则会出现虚假唤醒问题,解决方法将if换成while
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我+1完毕了
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

问题存在:ABCD4个线程会有虚假唤醒

首先到JDK官方文档 java.lang包下 找到Object ,然后找到wait()方法:

JUC并发编程学习笔记_第3张图片

因此上述代码中必须使用while判断,而不能使用if

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

JUC并发编程学习笔记_第4张图片

官方文档中通过Lock 找到 Condition

JUC并发编程学习笔记_第5张图片

点入Condition 查看 

JUC并发编程学习笔记_第6张图片

JUC并发编程学习笔记_第7张图片

代码实现: 

public class Demo04 {
 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 i = 0;
 Lock lock = new ReentrantLock();
 Condition condition = lock.newCondition();
 // +1
 public  void increment() throws InterruptedException {
     lock.lock();
     try {
         while (i != 0) {
             condition.await();
         }
         i++;
         System.out.println(Thread.currentThread().getName() + "=>" + i);
         // 通知其他线程我+1完成
         condition.signalAll();
     } catch (InterruptedException e) {
         e.printStackTrace();
     } finally {
         lock.unlock();
     }
 }
 // -1
 public void decrement() throws InterruptedException {
     lock.lock();
     try {
         while (i==0){
             condition.await();
         }
         i--;
         System.out.println(Thread.currentThread().getName() + "=>" + i);
         // 通知其他线程,我-1完毕
         condition.signalAll();
     } catch (InterruptedException e) {
         e.printStackTrace();
     } finally {
         lock.unlock();
     }
 }
}

Condition 精准的通知和唤醒线程

问题:ABCD线程 抢占执行的顺序是随机的,如果想让ABCD线程有序执行,该如何改进代码?

public class Demo05 {
    public static void main(String[] args) {
        Data01 data01 = new Data01();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data01.A();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data01.B();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data01.C();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
    }
}
// 判断等待,业务,通知
//A执行完调用B,B执行完调用C,C执行完调用A
class Data01 {
    private int num = 1;// 1A 2B 3C
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    public void A() throws InterruptedException {
        lock.lock();
        try {
            // 业务代码,判断=>执行=>通知!
            while (num!=1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAAA");
            num = 2;
            // 唤醒指定的线程,B
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void B() throws InterruptedException {
        lock.lock();
        try {
            while (num!=2){
                condition2.await();
            }
            num = 3;
            System.out.println(Thread.currentThread().getName()+"=>BBBBB");
            // 唤醒指定的线程,C
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void C() throws InterruptedException {
        lock.lock();
        try {
            while (num!=3){
                condition3.await();
            }
            num = 1;
            System.out.println(Thread.currentThread().getName()+"=>CCCCC");
            // 唤醒指定的线程,A
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

JUC并发编程学习笔记_第8张图片

5、8锁现象(就是关于锁的八个问题)

前面提出一个问题:如何判断锁的是谁!知道什么是锁,锁到底锁的是谁!

深刻理解我们的锁

synchronized 锁的对象是方法的调用者

/**
 * 8锁,就是关于锁的8个问题
 * 1、标准情况下,两个线程先打印 发短信还是 先打印 打电话? 1/发短信  2/打电话    发短信 1s 打电话
 * 2、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信  2/打电话      5s 发短信 打电话
 * 3、增加了一个普通方法后!先执行发短信还是Hello?// 普通方法                  hello 发短信
 * 4、两个对象,两个同步方法, 发短信还是 打电话? // 打电话                    打电话 4s 发短信
 * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?               5s 发短信 打电话
 * 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?                 5s 发短信 打电话
 * 7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?      打电话 4s 发短信
 * 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?      打电话 4s 发短信
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();


        new Thread(()->{
            phone1.sendSms();
        },"A").start();

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

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}

class Phone{
    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("打电话");
    }

    public void hello(){
        System.out.println("hello");
    }
}

6、集合类不安全

ArryList集合

多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)

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

解决方案:

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

概念:CopyOnWrite写入时复制,计算机程序设计语言的一种优化策略。(保证效率和性能问题)

HashSet集合

多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)

// java.util.ConcurrentModificationException:并发修改异常
public class Test11 {
    public static void main(String[] args) {
        // Set strings = Collections.synchronizedSet(new HashSet<>());
        HashSet strings = new HashSet<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                strings.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(strings);
            }).start();
        }
    }
}

解决方案:

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

hashset集合的底层是hashmap的key

HashMap集合

多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)

// java.util.ConcurrentModificationException:并发修改异常
public class Test11 {
    public static void main(String[] args) {
        // 默认相当于
        Map map = new HashMap<>(16, 0.75F);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            }).start();
        }
    }
}

解决方案:

  1. 使用Map concurrentHashMap = new ConcurrentHashMap<>();

7、Callable接口

Callable接口类似于Runnable接口,线程第三种创建方式。

  1. 可以抛出异常。
  2. 可以有返回值。
  3. 方法不同与Runnable接口。Call方法
/**
 * 1、探究原理
 * 2、觉自己会用
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // new Thread(new Runnable()).start();// 启动Runnable
        // new Thread(new FutureTask()).start();
        // new Thread(new FutureTask( Callable )).start();
        new Thread().start(); // 怎么启动Callable?
        // new 一个MyThread实例
        MyThread thread = new MyThread();
        // MyThread实例放入FutureTask
        FutureTask futureTask = new FutureTask(thread); // 适配类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); // call()方法结果会被缓存,提高效率,因此只打印1个call
        // 这个get 方法可能会产生阻塞!把他放到最后
        Integer o = (Integer) futureTask.get(); 
        // 或者使用异步通信来处理!
        System.out.println(o);// 1024
    }
}
class MyThread implements Callable {
    @Override
    public Integer call() {
        System.out.println("call()"); // A,B两个线程会打印几个call?(1个)
        // 耗时的操作
        return 1024;
    }
}
//class MyThread implements Runnable {
//
//    @Override
//    public void run() {
//        System.out.println("run()"); // 会打印几个run
//    }
//}
  • Callable 是 java.util 包下 concurrent 下的接口,有返回值,可以抛出被检查的异常
  • Runable 是 java.lang 包下的接口,没有返回值,不可以抛出被检查的异常
  • 二者调用的方法不同,run()/ call()

同样的 Lock 和 Synchronized 二者的区别,前者是java.util 下的接口 后者是 java.lang 下的关键字。

JUC并发编程学习笔记_第9张图片

8、常用的辅助类(必会)

1.CountDownLatch

JUC并发编程学习笔记_第10张图片

减法计数器: 实现调用几次线程后 再触发某一个任务

// 计数器
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");
    }
}

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); // 等待计数器归零,然后再向下执行

每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行

2.CyclicBarrier

JUC并发编程学习笔记_第11张图片

// 相当于加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 集齐七颗龙珠召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {// 如果计数器为7,线程只有6个,则会等待,不进行召唤神龙
            System.out.println("召唤神龙");
        });
        for (int i = 0; i < 7; i++) {
            final 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();
        }
    }
}

JUC并发编程学习笔记_第12张图片

3.Semaphore

Semaphore:信号量

JUC并发编程学习笔记_第13张图片

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位!限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                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();// 释放
                }
            }).start();
        }
    }
}

JUC并发编程学习笔记_第14张图片

原理:
semaphore.acquire();获得,假设已经满了则等待,等待其他线程释放。
semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程。

9、读写锁ReadWriteLock

ReadWriteLock接口有一个实现类ReentrantReadWriteLock类。

读可以被多个线程同时读,写的时候只能有一个线程去写

JUC并发编程学习笔记_第15张图片

/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读  可以共存!
 * 读-写  不能共存!
 * 写-写  不能共存!
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        //MyCache myCache = new MyCache();
        MyCacheLock myCacheLock = new MyCacheLock();
        // 写入
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
/**
 * 自定义缓存
 * 加锁的
 */
class MyCacheLock{
    private volatile Map map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new             
                                    ReentrantReadWriteLock();
    // private Lock lock = new ReentrantLock();
    // 存,写入的时候,只希望同时只有一个线程写
    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();
        }
    }
}
/**
 * 自定义缓存
 * 不加锁的
 */
class MyCache{
    private volatile Map map = new HashMap<>();
    // 存,写
    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName()
                                                       +"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()
                                                       +"写入OK");
    }
    // 取,读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()
                                                       +"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()
                                                       +"读取OK");
    }
}

JUC并发编程学习笔记_第16张图片

JUC并发编程学习笔记_第17张图片

10、阻塞队列  BlockingQueue

JUC并发编程学习笔记_第18张图片

JUC并发编程学习笔记_第19张图片

JUC并发编程学习笔记_第20张图片

四组API

方式

抛出异常

有返回值,不抛出异常 阻塞,一直等待 阻塞,超时等待
添加 add() offer() put() offer( , , )
移除 remove() pull() take() pull( , , )
检测队首元素 element() peek() - -
public class Test {
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    // 抛出异常:java.lang.IllegalStateException: Queue full
    public static void test1(){
        // 队列的大小为3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // add()方法返回boolean值
        boolean flag1 = blockingQueue.add("a");
        boolean flag2 = blockingQueue.add("b");
        boolean flag3 = blockingQueue.add("c");
        boolean flag4 = blockingQueue.add("d");// add添加元素超过队列的长度会抛出异常java.lang.IllegalStateException: Queue full
        System.out.println(blockingQueue.element());// 获得队首元素
        System.out.println("=========");
        // remove()返回本次移除的元素
        Object e1 = blockingQueue.remove();
        Object e2 = blockingQueue.remove();
        Object e3 = blockingQueue.remove();
        Object e4 = blockingQueue.remove();// 队列中没有元素仍继续移除元素会抛出异常java.util.NoSuchElementException
    }
    // 有返回值,不抛出异常
    public static void test2(){
        // 队列的大小为3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // offer返回boolean值
        boolean flag1 = blockingQueue.offer("a");
        boolean flag2 = blockingQueue.offer("b");
        boolean flag3 = blockingQueue.offer("c");
        //boolean flag4 = blockingQueue.offer("d");// offer添加元素超过队列的长度会返回false
        System.out.println(blockingQueue.peek());// 获得队首元素
        System.out.println("=========");
        // poll()返回本次移除的元素
        Object poll1 = blockingQueue.poll();
        Object poll2 = blockingQueue.poll();
        Object poll3 = blockingQueue.poll();
        Object poll4 = blockingQueue.poll();// 队列中没有元素仍继续移除元素会打印出null
    }
    // 阻塞,一直等待
    public static void test3() throws InterruptedException {
        // 队列的大小为3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // put没有返回值
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //blockingQueue.put("d");// put添加元素超过队列的长度会一直等待
        System.out.println("=========");
        // take()返回本次移除的元素
        Object take1 = blockingQueue.take();
        Object take2 = blockingQueue.take();
        Object take3 = blockingQueue.take();
        Object take4 = blockingQueue.take();// 队列中没有元素仍继续移除元素会一直等待
    }
    // 阻塞,超时等待
    public static void test4() throws InterruptedException {
        // 队列的大小为3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // offer返回boolean值
        boolean flag1 = blockingQueue.offer("a");
        boolean flag2 = blockingQueue.offer("b");
        boolean flag3 = blockingQueue.offer("c");
        // offer添加元素超过队列的长度会返回false;并且等待指定时间后推出,向下执行
        boolean flag4 = blockingQueue.offer("d", 2, TimeUnit.SECONDS);
        System.out.println("=========");
        // poll()返回本次移除的元素
        Object poll1 = blockingQueue.poll();
        Object poll2 = blockingQueue.poll();
        Object poll3 = blockingQueue.poll();
        // 队列中没有元素仍继续移除元素会打印出null,等待指定之间后退出。
        Object poll4 = blockingQueue.poll(2,TimeUnit.SECONDS);
    }
}

SynchronousQueue同步队列

进去一个元素,必须等待取出这个元素后,才能放下一个元素。put()、take()

/**
 * 同步队列:
 * 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
 * put了一个元素,必须从里面先take取出来,否则不能在put进去值!
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = 
                                new SynchronousQueue<>(); // 同步队列
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()
                                                           +" put 1");
                // put进入一个元素
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName()
                                                           +" put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName()
                                                           +" put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                // 睡眠3s取出一个元素
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()
                                           +"=>"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()
                                           +"=>"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()
                                           +"=>"+blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

JUC并发编程学习笔记_第21张图片

11、线程池 

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

池化技术

程序的运行,本质:占用系统的资源! (优化资源的使用 => 池化技术)

线程池、连接池、内存池、对象池///… 创建、销毁。十分浪费资源

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

线程池的好处

  • 1、降低系统资源的消耗
  • 2、提高响应的速度
  • 3、方便管理

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

线程池:3大方法

JUC并发编程学习笔记_第22张图片

public class Demo01 {
    public static void main(String[] args) {
        // Executors 工具类、3大方法
        // Executors.newSingleThreadExecutor();// 创建单个线程的线程池
        // Executors.newFixedThreadPool(5);// 创建一个固定大小的线程池
        // Executors.newCachedThreadPool();// 创建一个可伸缩的线程池
        // 单个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();                           
        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();
        }
    }
}

线程池:7大参数

源码分析:

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

JUC并发编程学习笔记_第23张图片

手动创建一个线程池 

因为实际开发中工具类Executors 不安全,所以需要手动创建线程池,自定义7个参数。

// Executors 工具类、3大方法
// Executors.newSingleThreadExecutor();// 创建单个线程的线程池
// Executors.newFixedThreadPool(5);// 创建一个固定大小的线程池
// Executors.newCachedThreadPool();// 创建一个可伸缩的线程池
/**
 * 四种拒绝策略:
 *
 * new ThreadPoolExecutor.AbortPolicy()
 * 银行满了,还有人进来,不处理这个人的,抛出异常
 *
 * new ThreadPoolExecutor.CallerRunsPolicy()
 * 哪来的去哪里!比如你爸爸 让你去通知妈妈洗衣服,妈妈拒绝,让你回去通知爸爸洗
 *
 * new ThreadPoolExecutor.DiscardPolicy()
 * 队列满了,丢掉任务,不会抛出异常!
 *
 * new ThreadPoolExecutor.DiscardOldestPolicy()
 * 队列满了,尝试丢掉任务中最早的任务并重新执行当前任务,也不会抛出异常!
 */
public class Demo01 {
    public static void main(String[] args) {

        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);
        //ExecutorService threadPool = Executors.newCachedThreadPool();


        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// int corePoolSize, 核心线程池大小(候客区窗口2个)
                5,// int maximumPoolSize, 最大核心线程池大小(总共5个窗口)
                3,// long keepAliveTime, 超时3秒没有人调用就会释,放关闭窗口
                TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒
                new LinkedBlockingQueue<>(3),// 阻塞队列(候客区最多3人)
                Executors.defaultThreadFactory(),// 默认线程工厂
                // 4种拒绝策略之一:
                // 队列满了,尝试丢掉任务中最早的任务并重新执行当前任务,也不会抛出异常!
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        try {
            for (int i = 1; i <= 9; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

线程池:4种拒绝策略

JUC并发编程学习笔记_第24张图片

new ThreadPoolExecutor.AbortPolicy(); // 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy();// 哪来的去哪(主线程来的,就回去让主线程执行)
new ThreadPoolExecutor.DiscardPolicy();// 丢掉任务,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy();// 尝试丢掉任务中最早的任务并重新执行当前任务,竞争失败了也丢掉任务,不抛出异常

IO密集型,CPU密集型:(调优)

CPU密集型:最大线程数,CPU几核的就是几,可以保持CPU效率最高。

IO密集型:判断程序中十分耗IO的线程数量,大于这个数,一般是这个数的两倍。

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

        // 自定义线程池!工作 ThreadPoolExecutor
        // 最大线程到底该如何定义
        // 1、CPU 密集型,几核,就是几,可以保持CPU的效率最高! 
        // 2、IO 密集型 > 判断你程序中十分耗IO的线程, 
        // 比如程序 15个大型任务 io十分占用资源!
        // IO密集型参数(最大线程数)就设置为大于15即可,一般选择两倍

        //获取CPU线程数
        System.out.println(Runtime.getRuntime().availableProcessors());
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// int corePoolSize, 核心线程池大小(候客区窗口2个)
                //获取CPU线程数 4核8线程 6核12线程 具体从任务管理器中性能->逻辑处理器查看
                Runtime.getRuntime().availableProcessors(),// int maximumPoolSize, 最大核心线程池大小(总共5个窗口)
                3,// long keepAliveTime, 超时3秒没有人调用就会释,放关闭窗口
                TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒
                new LinkedBlockingQueue<>(3),// 阻塞队列(候客区最多3人)
                Executors.defaultThreadFactory(),// 默认线程工厂
                // 4种拒绝策略之一:
                // 队列满了,尝试丢掉任务中最早的任务并重新执行当前任务,也不会抛出异常!
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        try {
            for (int i = 1; i <= 9; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

12、四大函数式接口(必需掌握)

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

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

@FunctionalInterface 
public interface Runnable {
    public abstract void run(); 
}

// lambda表达式、链式编程、函数式接口、Stream流式计算 
// 超级多FunctionalInterface 
// 简化编程模型,在新版本的框架底层大量应用! 
// foreach(消费者类的函数式接口)

Function 函数式接口

有一个输入参数,有一个输出(返回值)。

JUC并发编程学习笔记_第25张图片

/**
 * Function 函数型接口, 有一个输入参数,有一个输出参数
 * 只要是 函数型接口 可以 用 lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {
       /*Function function = new 
                                        Function() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/
        // lambda 表达式简化:
        Function function = str->{
    return str;};
        System.out.println(function.apply("asd"));
    }
}

Predicate 断定型接口

有一个输入参数,返回值只能是 布尔值

JUC并发编程学习笔记_第26张图片

/*
 * 断定型接口:有一个输入参数,返回值只能是 布尔值!
 */
public class Demo02 {
    public static void main(String[] args) {
        // 判断字符串是否为空
        /*Predicate predicate = new Predicate(){
            @Override
            public boolean test(String str) {
                return str.isEmpty();//true或false
            }
        };*/
        Predicate predicate = 
                            (str)->{
    return str.isEmpty(); };
        System.out.println(predicate.test(""));//true
    }
}

Consumer 消费型接口

有一个输入参数,没有返回值。

JUC并发编程学习笔记_第27张图片

/**
 * Consumer 消费型接口: 只有输入,没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
        /*Consumer consumer = new Consumer() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };*/
        Consumer consumer = 
                                (str)->{
    System.out.println(str);};
        consumer.accept("sdadasd");
    }
}

Supplier 供给型接口

没有输入参数,有一个输出(返回值)。

JUC并发编程学习笔记_第28张图片

/**
 * Supplier 供给型接口 没有参数,只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
        /*Supplier supplier = new Supplier() {
            @Override
            public Integer get() {
                System.out.println("get()");
                return 1024;
            }
        };*/
        Supplier supplier = ()->{
     return 1024; };
        System.out.println(supplier.get());
    }
}

13、Stream 流式计算

什么是Stream流式计算

大数据:存储 + 计算

集合、MySQL 本质就是存储东西的;

计算都应该交给流来操作!

在这里插入图片描述


/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有5个用户!筛选:
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 */
public class Test {
    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 list = Arrays.asList(u1, u2, u3, u4, u5);
        // 计算交给Stream流
        // lambda表达式、链式编程、函数式接口、Stream流式计算
        list.stream()
                 // ID 必须是偶数
                .filter(u->{return u.getId()%2==0;})
                 // 年龄必须大于23岁
                .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);
    }
}

14、ForkJoin

什么是 ForkJoin

ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!

大数据:Map Reduce (把大任务拆分为小任务)

JUC并发编程学习笔记_第29张图片

ForkJoin 特点:工作窃取,这个里面维护的都是双端队列

JUC并发编程学习笔记_第30张图片

ForkJoin 

在这里插入图片描述

在这里插入图片描述


/**
 * 求和计算的任务!
 * 3000   6000(ForkJoin)  9000(Stream并行流)
 * // 如何使用 forkjoin
 * // 1、forkjoinPool 通过它来执行
 * // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 * // 3. 计算类要继承 RecursiveTask(递归任务,有返回值的)
 */
public class ForkJoinDemo extends RecursiveTask {
    private Long start;  // 1
    private Long end;    // 1990900000
    // 临界值
    private Long temp = 10000L;
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    // 计算方法
    @Override
    protected Long compute() {
        if ((end-start)

 测试代码:

/**
 * 同一个任务,别人效率高你几十倍!
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // test1(); // 12224
        // test2(); // 10038
        // test3(); // 153
    }
    // 普通程序员
    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));
    }
    // 会使用ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask task = new ForkJoinDemo(
                                                0L, 10_0000_0000L);
        // 提交任务
        ForkJoinTask 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、异步回调

Future 设计的初衷: 对将来的某个事件的结果进行建模

/**
 * 异步调用: CompletableFuture
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步回调
//        CompletableFuture completableFuture = 
//                                    CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(
//                Thread.currentThread().getName()+"runAsync=>Void");
//        });
//
//        System.out.println("1111");
//
//        completableFuture.get(); // 获取阻塞执行结果
        // 有返回值的 supplyAsync 异步回调
        // ajax,成功和失败的回调
        // 返回的是错误信息;
        CompletableFuture completableFuture = 
                                CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()
                                           +"supplyAsync=>Integer");
            int i = 10/0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); 
            // 错误信息:
            // java.util.concurrent.CompletionException: 
            // java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        }).get());
        /**
         * succee Code 200
         * error Code 404 500
         */
    }
}

16、JMM

请你谈谈你对 Volatile 的理解

Volatile 是 Java 虚拟机提供轻量级的同步机制,类似于synchronized 但是没有其强大。

1、保证可见性

2、不保证原子性

3、防止指令重排

什么是JMM

JMM : Java内存模型,不存在的东西,概念!约定!

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

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

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

3、加锁和解锁是同一把锁。

线程 工作内存 、主内存

8 种操作:

在这里插入图片描述

在这里插入图片描述

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

  • 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操作之前,必须把此变量同步回主内存

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

在这里插入图片描述

17、Volatile

1、保证可见性

public class JMMDemo {
    // 不加 volatile 程序就会死循环!
    // 加 volatile 可以保证可见性
    private volatile static int num = 0;
    public static void main(String[] args) {
     // main
        new Thread(()->{
     // 线程 1 对主内存的变化不知道的
            while (num==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

2、不保证原子性

原子性 : 不可分割

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

// volatile 不保证原子性
public class VDemo02 {
    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static AtomicInteger num = new AtomicInteger();
    public static void add(){
        // num++; // 不是一个原子性操作
        num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
    }
    public static void main(String[] args) {
        //理论上num结果应该为 2 万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000 ; j++) {
                    add();
                }
            }).start();
        }
        // 判断只要剩下的线程不大于2个,就说明20个创建的线程已经执行结束
        while (Thread.activeCount()>2){
     // Java 默认有 main gc 2个线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() 
                                                       + " " + num);
    }
}

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

在这里插入图片描述

使用原子类,解决原子性问题。

在这里插入图片描述

// volatile 不保证原子性
 // 原子类的 Integer
 private volatile static AtomicInteger num = new AtomicInteger();
 public static void add(){
    // num++; // 不是一个原子性操作
    num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
 }

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

3、指令重排

什么是指令重排?:我们写的程序,计算机并不是按照你写的那样去执行的。

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

处理器在执行指令重排的时候,会考虑:数据之间的依赖性

volatile避免指令重排:
内存屏障(CPU的指令)。

  1. 保证特定操作的执行顺序。
  2. 可以保证某些变量的内存可见性。

在这里插入图片描述

volatile 是可以保证可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

volatile 内存屏障在单例模式中使用的最多!

18、单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的几种实现方式

单例模式的实现有多种方式,如下所示:

1、懒汉式,线程不安全

是否 Lazy 初始化:

是否多线程安全:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

2、懒汉式,线程安全

是否 Lazy 初始化:

是否多线程安全:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

3、饿汉式

是否 Lazy 初始化:

是否多线程安全:

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

5、登记式/静态内部类

是否 Lazy 初始化:

是否多线程安全:

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

6、枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

19、深入理解CAS

public class CASDemo {
    // CAS compareAndSet : 比较并交换! 
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020); 
        // 期望、更新 
        // public final boolean compareAndSet
        //                                    (int expect, int update) 
        // 如果我期望的值达到了,那么就更新,否则,
        // 就不更新, CAS 是CPU的并发原语! 
        System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
        System.out.println(atomicInteger.get()); 
        atomicInteger.getAndIncrement() // 看底层如何实现 ++ 
        System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
        System.out.println(atomicInteger.get()); 
    } 
}

执行结果如图:JUC并发编程学习笔记_第31张图片

Unsafe 类 

JUC并发编程学习笔记_第32张图片

在这里插入图片描述

JUC并发编程学习笔记_第33张图片

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

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

在这里插入图片描述

public class CASDemo {
    // CAS compareAndSet : 比较并交换! 
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020); 
        /*
         * 类似于我们平时写的SQL:乐观锁
         *
         * 如果某个线程在执行操作某个对象的时候,其他线程若操作了该对象,
         * 即使对象内容未发生变化,也需要告诉我。
         *
         * 期望、更新:
         * public final boolean compareAndSet(int 
         *                                    expect, int update) 
         * 如果我期望的值达到了,那么就更新,否则,就不更新, 
         *                                    CAS 是CPU的并发原语! 
         */
        // ============== 捣乱的线程 ================== 
        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()); 
    } 
}

JUC并发编程学习笔记_第34张图片

20、原子引用 

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

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

JUC并发编程学习笔记_第35张图片

注意:

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

21、各种锁的理解

1、公平锁、非公平锁

公平锁: 非常公平, 不能够插队,必须先来后到!

非公平锁:非常不公平,可以插队 (默认都是非公平)

public ReentrantLock() {
    sync = new NonfairSync(); 
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync(); 
}

2、可重入锁

可重入锁(递归锁)

JUC并发编程学习笔记_第36张图片

Synchronized 版

// 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(); // 这里也有锁(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.lock(); lock.unlock(); 
        // lock 锁必须配对,否则就会死在里面
        // 两个lock() 就需要两次解锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "sms");
            call(); // 这里也有锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3、自旋锁

JUC并发编程学习笔记_第37张图片

我们来自定义一个锁测试:

/**
 * 自旋锁
 */
public class SpinlockDemo {
    // int   0
    // Thread  null
    // 原子引用
    AtomicReference 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);// 解锁
    }
}

 测试:

public class TestSpinLock {
    public static void main(String[] args) throws 
                                            InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();
        // 底层使用的自旋锁CAS
        SpinlockDemo lock = new SpinlockDemo();// 定义锁
        new Thread(()-> {
            lock.myLock();// 加锁
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();// 解锁
            }
        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()-> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

JUC并发编程学习笔记_第38张图片

这里T1拿到锁,线程休息1秒,然后T2拿到锁,这个时候T1sleep5秒,所以处于休眠状态,于是T2就开始自旋。5秒过后,T1解锁,于是T2开始sleep5秒,5秒过后T2解锁

4、死锁

死锁是什么?

死锁:线程A持有锁A,想要获得锁B;线程B持有锁B,想要获得锁A。

JUC并发编程学习笔记_第39张图片

 解决方法:查看堆栈信息

  1. 使用jps -l命令查看进程号。(该命令在JDK的bin目录下)
  2. 使用jstack+进程号,找到死锁问题。

你可能感兴趣的:(java,juc,多线程)