Bootstrap

JUC并发编程,看这一篇就够了

学的B站狂神的JUC教程,以及自己另外看书记的笔记,对视频中的内容做了更为详细的笔记

目录

一、什么是JUC

二、LOCK锁

  三、生产者和消费者问题

三.八锁现象

四、多线程下的集合类不安全

List

Set

Map

五、Callable

六、常用辅助类

七、阻塞队列BlockingQueue

八、同步队列

九、线程池(重点)

9.1简介

9.2 三大方法: 

9.3 七大参数及自定义线程池

十、四大函数式接口

10.1 :函数型接口

10.2 断定型接口

10.3 消费型接口

10.4 供给型接口

十一、Stream流式计算

十二、ForkJoin

十三、JMM

十四、Volatile

十五、指令重排

十六、深入理解CAS

16.1 什么是CAS

16.2 CAS可能存在的一些问题以及解决

16.2.1 ABA问题

16.2.2 循环时间长开销大

16.2.3 只能保证一个共享变量的原子操作

16.3 JDK中对CAS的支持 — Unsafe类

16.4 JDK中的相关原子操作类简介--底层CAS机制

16.4.1 AtomicInteger

16.4.2 AtomicIntegerArray

16.4.3 更新引用类型

16.4.4 原子更新字段类

十七、悲观锁和乐观锁


一、什么是JUC

1.1 JUC简介

我们常说的JUC就是Java.util.concurrent包,他是一个处理线程的工具包,从jdk1.5之后开始出现

1.2进程与线程

进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是线程的容器;

线程:是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位;

1.3.1线程的状态

1.NEW(新生)

2.RUNNABLE(运行)

3.BLOCKED(阻塞)

4.WAITING(不见不散,不来就一直等)

5.TIMED_WAITING(过时不候)

6.TERMINATED(终结)

1.3.2 wait/sleep的区别

①sleep是Thread类的静态方法,wait是Object类的方法,任何对象实例都可以调用这两个方法;

②sleep不会释放锁,也不需要占用锁;wait会释放锁,但是调用它的前提是当前线程占有了锁的位置(即代码要在synchronized同步代码块中)

③它们都可以interrupted方法中断;

④sleep必须要处理异常;

1.4 并发与并行

并发(concurrent):  是指在同一实体上的多个事件,在一台处理器上“同时处理多个任务”,同一时刻其实只有一个事件发生【一对多】

并行(parallel):是在不同实体上的多个事件,多台处理器上同时处理多个任务,同一时刻各自互不干涉【多对多】

二、LOCK锁

1、reentrantlock可以代替synchronized,使用reentrantlock可以完成和synchronized同样的功能,但是必须得手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中释放锁。

2、使用reentrantlock可以进行尝试锁定(tryLock),这样无法锁定或者在指定时间内无法锁定的话,线程可以决定是否继续等待。

3、reentrantlock可以指定为公平锁。ReentrantLock lock=new ReentrantLock(true);表示new一个公平锁。默认是非公平锁。

(以卖票为例:

传统的synchronized锁:

public class SaleTicketDemo01 {

    public static void main(String[] args) {

        //创建多个线程,调用资源类中的方法

        Ticket ticket = new Ticket();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"AA").start();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"BB").start();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"CC").start();

    }

}

class Ticket{

    //票数

    private int number = 30;

    //操作方法:卖票

    public synchronized void sale(){

        //判断是否有票

        if (number>0){

            number--;

            System.out.println(Thread.currentThread().getName()+"卖出1张票"+",还剩"+number+"张票;");

        }

    }

}

使用了ReetrantLock(可重入锁)之后:

/**

 * 使用可重入锁ReentrantLock

 */

public class SaleTicketDemo02 {

    public static void main(String[] args) {

        //创建多个线程,调用资源类中的方法

        Ticket02 ticket = new Ticket02();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"AA").start();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"BB").start();

        new Thread(()->{

            for (int i = 0; i < 40; i++) {

                ticket.sale();

            }

        },"CC").start();

    }

}

class Ticket02{

    //票数

    private int number = 30;

    //1.new ReentrantLock对象

    Lock lock = new ReentrantLock();

    //操作方法:卖票

    public void sale(){

        lock.lock();//2.加锁

        //判断是否有票

        try {

            if (number>0){

                number--;

                System.out.println(Thread.currentThread().getName()+"卖出1张票"+",还剩"+number+"张票;");

            }

        } catch (Exception e) {

            throw new RuntimeException(e);

        } finally {

            //3.释放锁

            lock.unlock();

        }

    }

}

             公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,

      就占有锁,否则就会加入到等待队列中,以后会按照FIFO(队列先进先出)的规则从队列中取到锁;

             非公平锁:如果一个新来的线程,直接就去抢锁,而不去判断等待队列是否有线程在等待,这就是非公平锁。synchronized都是非公平锁。

    新来的线程检查队列与否是区分公平锁和非公平锁的关键!

【Synchronized和Lock的区别:】

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

2、Synchronized无法判断获取锁的状态,Lock 可以判断线程是否获取到了锁;

3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,会造成死锁的问题;

4、Synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;

5、Synchronized 是可重入锁,不可以中断的,属于非公平锁;Lock也是可重入锁,可以判断锁的状态,默认非公平锁(可以自己设置);

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

  三、生产者和消费者问题

1.synchronized版本:

/**

 * 线程之间的通信问题,生产者和消费者问题

 * 线程A B操作同一个变量 num=0

 * A:num+1;

 * B:num-1;

 */

public class Producter {

    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) {

                    throw new RuntimeException(e);

                }

            }

        },"A").start();

        new Thread(()->{

            for (int i = 0; i < 10; i++) {

                try {

                    data.decrement();

                } catch (InterruptedException e) {

                    throw new RuntimeException(e);

                }

            }

        },"B").start();

     }

}

class Data{

    private int number = 0;

    public synchronized void increment() throws InterruptedException {

        if (number!=0){

            this.wait();

        }

        number++;

        //通知其他线程,我+1完毕

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

        this.notifyAll();

    }

    public synchronized void decrement() throws InterruptedException {

        if (number==0){

            this.wait();

        }

        number--;

        //通知其他线程,我+1完毕

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

        this.notifyAll();

    }

}

这里如果我们再多增加两个线程的话,就会出现虚假唤醒的问题!(这时候在同步方法中就不能用if去进行判断而要用while)

使用if判断的话,线程被唤醒之后会从wait 之后的代码开始运行,不会重新判断if条件;

而使用while的话,也会从wait之后的代码运行,但是会重新执行判断while循环条件,成立的话继续wait;

2.JUC版本的生产者和消费者问题:(换汤不换药,只是取代wait转而变成condition来控制线程的等待与唤醒)

class Data02{

    private int number = 0;

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

//    condition.await();//等待

//    condition.signalAll();//唤醒全部线程

    public void increment() throws InterruptedException {

        try {

            lock.lock();

            //业务代码

            while (number!=0){

                //等待

                condition.await();

            }

            number++;

            //通知其他线程,我+1完毕

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

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }

    public void decrement() throws InterruptedException {

        try{

            lock.lock();

            while (number==0){

                condition.await();//等待

            }

            number--;

            //通知其他线程,我+1完毕

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

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }

}

3.Condition实现精准通知唤醒

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

   20

   21

   22

   23

   24

   25

   26

   27

   28

   29

   30

   31

   32

   33

   34

   35

   36

   37

   38

   39

   40

   41

   42

   43

   44

   45

   46

   47

   48

   49

   50

   51

   52

   53

   54

   55

   56

   57

   58

   59

   60

class Data03{

    private Lock lock = new ReentrantLock();

    //new 3个监视器

    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");

            //唤醒指定的人B

            number = 2;

            condition2.signal();

        } catch (Exception e) {

            throw new RuntimeException(e);

        } finally {

            lock.unlock();

        }

    }

    public void printB(){

        lock.lock();

        try {

            //业务,判断,执行,通知

            while (number!=2){

                condition2.await();

            }

            System.out.println(Thread.currentThread().getName()+"=>"+"BBBBB");

            //唤醒指定的人B

            number = 3;

            condition3.signal();

        } catch (Exception e) {

            throw new RuntimeException(e);

        } finally {

            lock.unlock();

        }

    }

    public void printC(){

        lock.lock();

        try {

            //业务,判断,执行,通知

            while (number!=3){

                condition3.await();

            }

            System.out.println(Thread.currentThread().getName()+"=>"+"CCCCC");

            //唤醒指定的人B

            number = 1;

            condition1.signal();

        } catch (Exception e) {

            throw new RuntimeException(e);

        } finally {

            lock.unlock();

        }

    }

}

三.八锁现象

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

   20

   21

   22

   23

   24

   25

   26

   27

   28

   29

   30

   31

   32

   33

   34

   35

   36

   37

   38

   39

   40

   41

   42

   43

   44

   45

   46

   47

   48

   49

/**

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

 * 2.sendMsg()延迟4s后,两个线程是先打印发短息还是打电话?  //发短信

 * 3.增加一个普通方法后,先执行发短信还是hello?//这个时候就不存在抢锁的问题了,看谁的延时少就去执行谁

 * 4.两个对象分别去调用两个同步方法,是先发短信还是先打电话?(sendMsg延迟4s,call延迟1s)  //打电话啊

 *

 */

public class Test1 {

    public static void main(String[] args) {

        Phone phone1 = new Phone();

        Phone phone2 = new Phone();

        //显然这里一定是发短信方法先拿到对象的锁,所以不管怎么样,都会先执行

        new Thread(()->{

            phone1.sendMsg();

        }).start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        new Thread(()->{

            phone2.call();

        }).start();

    }

}

class Phone{

    /**

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

     * 两个同步方法用的是同一把锁,谁先拿到就谁先执行!

     */

    public synchronized void sendMsg(){

        try {

            TimeUnit.SECONDS.sleep(4);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        System.out.println("发短信");

    }

    public synchronized void call(){

        System.out.println("打电话");

    }

    public void hello(){

        System.out.println("Hello");

    }

}

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

   20

   21

   22

   23

   24

   25

   26

   27

   28

   29

   30

   31

   32

   33

   34

   35

   36

   37

   38

   39

   40

   41

   42

   43

   44

   45

   46

   47

   48

/**

 * 5.增加两个静态的同步方法,只有一个对象,先打印发短信还是打电话?//发短信  因为static 是静态方法,在类进行加载的时

 * 候就已经初始化了所以这里synchronized是在给class进行加锁,所以会按照调用的先后顺序来进行输出

 * 6.new两个对象,两个同步的静态方法,先打印发短信还是打电话? //发短信

 */

public class Test2 {

    public static void main(String[] args) {

        //这两个对象的类加载器只有一个,static方法锁的是class

        Phone2 phone1 = new Phone2();

        Phone2 phone2 = new Phone2();

        //显然这里一定是发短信方法先拿到对象的锁,所以不管怎么样,都会先执行

        new Thread(()->{

            phone1.sendMsg();

        }).start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        new Thread(()->{

            phone2.call();

        }).start();

    }

}

//只有唯一的一个class对象

class Phone2{

    /**

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

     * static 是静态方法,在类进行加载的时候就已经初始化了

     * 所以这里synchronized是在给class进行加锁,所以会按照调用的先后顺序来进行输出

     */

    public static synchronized void sendMsg(){

        try {

            TimeUnit.SECONDS.sleep(4);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        System.out.println("发短信");

    }

    public static synchronized void call(){

        System.out.println("打电话");

    }

}

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

   20

   21

   22

   23

   24

   25

   26

   27

   28

   29

   30

   31

   32

   33

   34

   35

   36

   37

   38

   39

   40

   41

   42

   43

   44

   45

   46

   47

   48

   49

/**

 * 7.一个静态同步方法,一个普通同步方法,通过一个对象去分别在不同的线程里面调用,谁先谁后? //打电话,因为

 * 静态同步方法(类锁)和普通同步方法(对象的锁)用的不是一把锁,所以自然就是先打电话

 * 8.两个对象分别去调用一个静态同步方法和普通同步方法,先打印发短信还是打电话?//打电话  因为两个对象不是用的一把锁,

 * 自然就是谁的延迟小,先打印谁咯

 *

 */

public class Test3 {

    public static void main(String[] args) {

        //这两个对象的类加载器只有一个,static方法锁的是class

        Phone3 phone1 = new Phone3();

        Phone3 phone2 = new Phone3();

        //显然这里一定是发短信方法先拿到对象的锁,所以不管怎么样,都会先执行

        new Thread(()->{

            phone1.sendMsg();

        }).start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        new Thread(()->{

            phone2.call();

        }).start();

    }

}

//只有唯一的一个class对象

class Phone3{

    /**

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

     * static 是静态方法,在类进行加载的时候就已经初始化了

     * 所以这里synchronized是在给class进行加锁,所以会按照调用的先后顺序来进行输出

     */

    public static synchronized void sendMsg(){

        try {

            TimeUnit.SECONDS.sleep(4);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        System.out.println("发短信");

    }

    public synchronized void call(){

        System.out.println("打电话");

    }

}

四、多线程下的集合类不安全

List

            当在单线程中List是安全的,但在并发中ArrayList是不安全的如:

public class ListTest {

    public static void main(String[] args) {

        //并发操作下list是不安全的

        List<String> list = new ArrayList<>();

        for (int i = 0; i <=10; i++) {

            new Thread(()->{

                list.add(UUID.randomUUID().toString().substring(0,5));

                System.out.println(list);

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

        }

    }

}

 就会出现了:ConcurrentModificationException(并发修改异常),

解决方案如下

1.使用synchronizedArrayListadd方法加锁(ArrayList1.2出来的) ,也就是通过Collections.synchronizedList加锁

2.使用List的子类Vector集合类着里面加入了synchronized(Vector1.0出来的)

3.使用Collections.synchronizedList()方法将集合转换为安全的集合

4.使用CopyOnWriteArrayList类在java并发包concurrent下!

这里推荐使用CopyOnWriteArrayList而不使用vector是因为:

          vector所有的操作都有锁,但是copyonwritecow)读操作是没有锁的!

在开发中读比写更频繁,所以说cow好在这里,另外还有一个有点是数组扩容问题。cow不需要扩容

Set

同理,Set集合在多线程的环境下也是不安全的,和list一样会出现java.util.ConcurrentModificationException

解决方案:

1.通过Collections.synchronizedSet给Set加锁;

2.使用CopyOnWriteSet,同样的也在concurrent包下,底层是数组;

3.使用ConcurrentSkipListSet,底层是链表;

【】hashSet的底层就是一个hashMap

Map

new HashMap<>();<==>new HashMap<>(16,0.75);

这里的16是hashMap的默认容量,0.75是加载因子

1.当hashMap达到临界值的时候,会触发扩容机制,扩容的临界值是默认容量x加载因子

也就是16x0.75=12;

2.当位桶中链表的长度大于8时,底层的链表会自动转成红黑树;

3.位桶中链表的节点装换成红黑树时最小的hash表容量为64;

4.链表长度低于6,会从红黑树转成链表

ConcurrentHashMap

1.为什么要使用ConcurrentHashMap

当然是为了线程安全,为了规避HashMap在多线程模型下的缺点,传统线程安全的HashTable的问题等;

①hashMap在多线程环境下不安全;

②hashTable使用synchronized(非公平锁)给操作加锁,但是在线程

激烈竞争的情况下,hashTable的效率非常低下,会进入阻塞或者轮询状态;(比如说线程A在进行put操作,

那么线程B想要进行put或者get操作的话是不被允许的),所以竞争越激烈,效率越低;

③相较于笨重的hashTable,ConcurrentHashMap就显得很高效;ConcurrentHashMap降低了锁的粒度,在

JDK1.7中,设置了分段锁,来表示不同的数据;1.8以后取消了分段锁,进一步的降低了锁的粒度;

JDK1.8后,ConcurrentHashMap和HashMap的底层数据结构基本都是数组+链表+红黑树,但是在ConcurrentHashMap

里面,红黑树在Node数组里面存储的不是一个TreeNode对象,而是一个TreeBin对象,在TreeBin对象的内部,

维持着一个红黑树;

五、Callable

1.可以有返回值

2.可以抛出异常

3.方法不同,call()/run()

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建适配器

        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());

        //因为FutureTask实现了RunnableFuture接口所以可以通过Thread启动线程

        new Thread(futureTask,"A").start();

        //获取线程返回值

        Integer i = futureTask.get();

        System.out.println(i);

    }

}

class MyCallable implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

        System.out.println("call()");

        return 1024;

    }

}

六、常用辅助类

6.1 CountDownLatch

常用方法:

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

countDownLatch.await();               //等待计数器归零在往下执行!

原理:当每次有线程调用 countDown()方法则数量减一,假设数量变为0,await()方法就会被唤醒继续执行!

coding:

/**

 * 测试线程辅助类计数器

 */

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {

        //初始化,总数是6,在必须要执行完某个任务的时候,使用

        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {

            new Thread(()->{

                System.out.println(Thread.currentThread().getName()+"Go out");

                countDownLatch.countDown(); //数量减一

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

        }

        //等待计数器归0,然后再向下执行

        countDownLatch.await();

        System.out.println("close the door");

    }

}

总结 countDownLatch.countDown();会让其自动减一 ,而在CountDownLatch

await等待会等待其计数器归零时才会再向下执行,防止门提前关了里面的人出不去

6.2 CyclicBarrier

/**

 * 测试线程辅助类CyclicBarrierDemo

 */

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 temp = i;

            new Thread(()->{

                //注意!lambda表达式里面不能直接操作循环变量;需要通过 一个中间量来进行操作

                System.out.println(Thread.currentThread().getName()+"集齐"+temp+"颗龙珠;");

                try {

                    //等待线程执行完,触发相应的事件

                    cyclicBarrier.await();

                } catch (InterruptedException e) {

                    throw new RuntimeException(e);

                } catch (BrokenBarrierException e) {

                    throw new RuntimeException(e);

                }

            }).start();

        }

    }

}

总结:CyclicBarrier可以设置指定最终达到的数值(代表for里的最大数),还能创建线程,

CyclicBarrier下的await会等待其数值达到最终数值才会执行最终数值旁边的Runnable线程

Semaphore:(信号量)

6个车只有3个停车位,进行抢占车位和离开车位的操作;

coding

/**

 * 信号量

 */

public class SemaphoreDemo {

    public static void main(String[] args) {

        //这里的3是线程总数

        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <=6; i++) {

            new Thread(()->{

                try {

                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName()+"抢到了车位!");

                    TimeUnit.SECONDS.sleep(2); //等待2s

                    System.out.println(Thread.currentThread().getName()+"离开了车位!");

                } catch (InterruptedException e) {

                    throw new RuntimeException(e);

                }finally {

                    //释放车位

                    semaphore.release();

                }

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

        }

    }

}

总结:Semaphore就是信号量可以填写线程总数,acquire()许可证同意进行操作,release()释放让其离开,

然后会让后面的数值上来代替直到走完。类似于操作系统中的P、V操作。

七、阻塞队列BlockingQueue

写入:如果队列满了,就必须阻塞等待

:如果队列是空的就必须阻塞等待生产

学会使用队列:四组api

1.抛出异常

2.不会抛出异常

3.阻塞等待

4.超时等待

/**

     * 会抛异常的

     */

    //如果add操作超出了初始化的容量,会抛java.lang.IllegalStateException

    //而如果队列没有数据了的话,依然执行remove操作的话,会抛出java.util.NoSuchElementException

    public static void test1(){

        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("1"));

        System.out.println(blockingQueue.add("2"));

        System.out.println(blockingQueue.add("3"));

//        System.out.println(blockingQueue.add("3"));

        System.out.println("===================");

        //这里我们看到遵循了先进先出(FIFO)的原则

        System.out.println(blockingQueue.remove());

        System.out.println(blockingQueue.remove());

        System.out.println(blockingQueue.remove());

//        System.out.println(blockingQueue.remove());

    }

 

-----------------------------------------------------------------------------------------

    /**

     * 有返回值,不抛出异常

     */

    public static void test2(){

        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));

        System.out.println(blockingQueue.offer("b"));

        System.out.println(blockingQueue.offer("c"));

        System.out.println("====================");

        //如果offer添加操作超过了队列的初始化容量,会返回false,不会抛出异常

//        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.peek());  //查看队列首部元素  a

        System.out.println("********************");

        System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.poll());

        //如果队列中没有数据,进行poll移除操作时,会返回null

//        System.out.println(blockingQueue.poll());

    }

 

------------------------------------------------------------------------------------------------------------------------

    /**

     * 等待阻塞(一直阻塞)

     */

    public static void test3() throws InterruptedException {

        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        blockingQueue.put("i");

        blockingQueue.put("j");

        blockingQueue.put("k");

        //队列初始化容量满了再进行put,会一直阻塞

//        blockingQueue.put("n");

        System.out.println("==================");

        System.out.println(blockingQueue.take());

        System.out.println(blockingQueue.take());

        System.out.println(blockingQueue.take());

        //队列没有元素的情况下,再进行take(取)操作,会一直阻塞等待

        System.out.println(blockingQueue.take());

    }

----------------------------------------------------------------------------------------

/**

     * 等待,阻塞(超时等待)

     */

    public static void test4() throws InterruptedException {

        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("11"));

        System.out.println(blockingQueue.offer("22"));

        System.out.println(blockingQueue.offer("33"));

        System.out.println("====================");

        //如果offer添加操作超过了队列的初始化容量,在等待1s未果后,会终止添加

        System.out.println(blockingQueue.offer("d",1, TimeUnit.SECONDS));

        System.out.println("********************");

        System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.poll());

        //如果队列中没有数据时,进行poll移除操作,在等待1s未果后,会自动终止取数据

        System.out.println(blockingQueue.poll(1,TimeUnit.SECONDS));

    }

八、同步队列

SynchronousQueue:没有容量,进去一个元素,必须等待取出来之后,才能往里面放另一个元素;

coding

/**

 * 测试同步队列

 */

public class SynchronousQueueDemo {

    public static void main(String[] args) {

        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(()->{

            System.out.println(Thread.currentThread().getName() + "put1");

            try {

                blockingQueue.put("1");

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            System.out.println(Thread.currentThread().getName() + "put2");

            try {

                blockingQueue.put("2");

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            System.out.println(Thread.currentThread().getName() + "put3");

            try {

                blockingQueue.put("3");

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

        },"T1").start();

        new Thread(()->{

            //******************************

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            try {

                System.out.println(Thread.currentThread().getName()+"=>"+ blockingQueue.take());

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            //******************************

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            try {

                System.out.println(Thread.currentThread().getName()+"=>" + blockingQueue.take());

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            //**********************************

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

            //**********************************

            try {

                System.out.println(Thread.currentThread().getName()+"=>" + blockingQueue.take());

            } catch (InterruptedException e) {

                throw new RuntimeException(e);

            }

        },"T2").start();

    }

}

九、线程池(重点)

9.1简介

池化技术:事先准备好一些资源,有需要了从池子中取,取完了再放回去;

程序的运行本质:占用系统的资源,优化资源的使用!

常见的池化技术:线程池、jdbc数据库连接池、内存池、对象池

线程池的好处:

1.降低资源的消耗;

2.提高响应的速度;

3.方便管理;

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

9.2 三大方法: 

①newSingleThreadExecutor   //创建单个线程

②newFixedThreadPool       //创建一个固定大小的线程池

③newCacheThreadPool    //创建一个可伸缩的线程池

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学

更加明确线程池的运行规则,规避资源耗尽的风险。

【说明:】Executors各个方法的弊端:

①newFixedThreadPool和newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM

②newCacheThreadPool和newScheduledThreadPool:

主要问题是线程最大数是Integer.MAX_VALUE(约为21亿),可能会创建非常多的线程,甚至OOM

newCacheThreadPool:

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

*那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,

*此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,

*线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

9.3 七大参数及自定义线程池

源码分析:

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

    }

七大参数:

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.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;

    }

四种拒绝策略:

①ThreadPoolExecutor.AbortPolicy        //默认拒绝策略,拒绝任务并抛出任务

②ThreadPoolExecutor.CallerRunsPolicy        //使用调用的线程直接运行任务(例如测试的main线程)

③ThreadPoolExecutor.DiscardPolicy        //直接拒绝任务,不抛出错误

④ThreadPoolExecutor.DiscardOldestPolicy   //触发拒绝策略,只要还有任务新增,

    一直会丢弃阻塞队列的最老的任务,并将新的任务加入

coding

//自定义线程池

        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,

                5,

                3,

                TimeUnit.SECONDS,

                new LinkedBlockingQueue<>(3),

                Executors.defaultThreadFactory(),

                new ThreadPoolExecutor.DiscardOldestPolicy());

        try {

            //最大承载:阻塞队列值+最大线程数(这里是5+3=8)

            for (int i = 0; i < 9; i++) {

                poolExecutor.execute(()->{

                    System.out.println(Thread.currentThread().getName()+"ok");

                });

            }

        } catch (Exception e) {

            throw new RuntimeException(e);

        }finally {

            poolExecutor.shutdown();

        }

    }

【最大线程到底该如何定义?】

1.cpu 密集型:几核就是几,可以充分利用CPU的资源

2.IO密集型:判断程序中十分耗IO的线程,

十、四大函数式接口

10.1 :函数型接口

(只有一个方法的接口)

@FunctionalInterface

public interface Runnable {

       public abstract void run();

Runnable接口就是一个典型的例子

@FunctionalInterface(简化编程模型,jdk1.8之后大量使用)

    coding:

//判断字符串是否为空

        Predicate<String> predicate = new Predicate<String>() {

            @Override

            public boolean test(String s) {

                return s.isEmpty();

            }

        };

        System.out.println(predicate.test(""));

10.2 断定型接口

(有一个输入参数)

coding:

//判断字符串是否为空

        Predicate<String> predicate = new Predicate<String>() {

            @Override

            public boolean test(String s) {

                return s.isEmpty();

            }

        };

        System.out.println(predicate.test(""));

10.3 消费型接口

(只有输入,无返回值)

coding:

//        Consumer<String> consumer = new Consumer<String>() {

//            @Override

//            public void accept(String str) {

//                System.out.println(str);

//            }

//        };

        Consumer<String> consumer1 = (str)->{

            System.out.println(str);

        };

        consumer1.accept("adada");

    }

10.4 供给型接口

(没有参数,只有返回值)

//        Supplier<Integer> supplier = new Supplier<Integer>() {

//            @Override

//            public Integer get() {

//                return 1024;

//            }

//        };

        Supplier<Integer> supplier1 = ()->{

            return 1024;

        };

        System.out.println(supplier1.get());

    }

十一、Stream流式计算

/**

 * 题目要求:(一分钟内完成此题,只能用一行代码实现)

 * 现在有5个用户,筛选:

 * 1. ID必须是偶数;

 * 2. 年龄必须大于23岁;

 * 3. 用户名转为大写字母;

 * 4. 用户名字母倒着排序;

 * 5. 只输出一个用户

 */

coding:

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(5,"e",25);

        User u6 = new User(6,"f",26);

        List<User> list = Arrays.asList(u1,u2,u3,u4,u5,u6);

        list.stream().filter((u)->{return u.getId()%2==0;})

                .filter((u)->{return u.getAge()>23;})

                .map((u)->{return u.getName().toUpperCase();})

                .sorted((us1,us2)->{return us2.compareTo(us1);})

                .limit(1)

                .forEach(System.out::println);

    }

}

十二、ForkJoin

11.1:什么是ForkJoin

核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果,

其实现思想与MapReduce有异曲同工之妙。

Fork/Join框架使用一个巧妙的算法来平衡线程的负载,称为工作窃取(work-stealing)算法。工作窃取的运行流程图如下:

假如我们要做一个很大的任务,分多个线程处理,其中一些线程处理完毕没有事情了,这时候

空闲的线程会去还在执行任务的线程的尾部"窃取"一个任务来帮忙执行;

工作流程:

1.计算类继承ForkJoinTask的子类---RecursiveTask<>

2.然后通过forkJoinPool.execute(ForkJoinTask task)来执行计算

coding:

public class ForkJoinDemo extends RecursiveTask<Long> {

    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)<temp){

            Long sum = 0L;

            for (Long i = start; i <= end; i++) {

                sum += i;

            }

            return sum;

        }else { // forkjoin 递归

            long middle = (start + end) / 2; // 中间值

            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);

            task1.fork(); // 拆分任务,把任务压入线程队列

            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);

            task2.fork(); // 拆分任务,把任务压入线程队列

            return task1.join() + task2.join();

        }

    }

}

public class Test {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

//         test1();//5165

//         test2();//3608

         test3();//167

    }

    // 基础的for循环

    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<Long> task = new ForkJoinDemo(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="+sum+"\t时间:"+(end-start));

    }

}

十三、JMM

JMM描述的是一组规则,通过这组规则控制程Java序中各个变量在共享数据区域

和私有数据区域的访问方式,JMM是围绕原子性,有序性、可见性拓展延伸的。

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

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

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

3.加锁和解锁用的是同一把锁;

十四、Volatile

(Java虚拟机提供的轻量级的同步机制)

【volatile的作用:】

             1.保证可见性;

  2.不保证原子性;

 3.由于内存屏障,禁止指令的重排;

保证可见性:

public class JMMDemo {

//    private static int num = 0;

    //加了volatile关键字后就不会出现死循环了(volatile的保证可见性)

    private volatile static int num = 0;

 

    public static void main(String[] args){

        new Thread(()->{

            while (num==0){

            }

        }).start();

        try {

            TimeUnit.SECONDS.sleep(2);

            num=1;

            System.out.println(num);

        }catch (Exception e){

            e.printStackTrace();

        }

    }

}

不保证原子性:

public class JMMDemo02 {

    //volatile不保证原子性

    private volatile static int num = 0;

    public static void add(){

        num++; //不是一个原子性操作

    }

    public static void main(String[] args) {

        //理论上num结果应该为20000

        for (int i = 0; i < 20; i++) {

            new Thread(()->{

                for (int j = 0; j < 1000; j++) {

                    add();

                }

            }).start();

        }

        while(Thread.activeCount()>2){

            //让出计算机资源并且重新竞争资源

            Thread.yield();

        }

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

    }

}

通过加lock锁和synchronized关键字可以保证原子性;

除了上面这两种方式,还可以使用原子类进行操作;

eg:

##为什么Atomic类可以保证原子性?

以AtomicInteger为例。在AtomicInteger中有一个volatile[0]修饰的value变量,

也就是这个整型的值。在调用getAndIncrement()时,AtomicInteger会通过Unsafe类的

getAndAddInt方法对变量value进行一次CAS[1]操作。由于CAS是具有原子性的,

所以AtomicInteger就保证了操作的线程安全。

十五、指令重排

你写的程序,计算机并不是按照你写的那样去执行

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

int x=1;

int y=1;

x=x+5;

y=x+x;

我们期望的是1234,但是可能是21344,1324

不可能是4123,==处理器在执行指定重排的时候,考虑数据之间的依赖性

可能造成影响的结果x,y,a,b默认是0

Volitale可以避免指令重排:

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

2.可以保证某些变量的内存可见性;

十六、深入理解CAS

16.1 什么是CAS

          CAS即Compare And Swap,比较并交换,这是为了解决多线程环境下使用锁而造成性能损耗的一种机制,

它包含三个操作数------内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值的值相匹配,那么处理器

会自动将内存位置的值更新为新值。否则,处理器将不做任何操作;直白点说就是,我认为V这个位置应该有A,如果

有,那么就把B放到这个位置;否则,还是返回原始的值;

图示:

16.2 CAS可能存在的一些问题以及解决

16.2.1 ABA问题

       CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化就更新,但是如果一个值原来是A,

变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了 —> 这就是

所谓的ABA问题

       举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,

你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,还好 ; 但是假若你是一个比较讲

卫生的人,那你肯定就不高兴了。。。

       ABA问题的解决思路其实也很简单,就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本

号加1,那么A→B→A就会变成1A→2B→3A了。

详情见16.4.3

16.2.2 循环时间长开销大

自旋CAS如果一直不成功的话,会给CPU带来非常大的开销;

16.2.3 只能保证一个共享变量的原子操作

当我们对一个共享变量执行操作时,可以采用循环CAS的方式进行操作;但是如果对多个共享变量进行操作时,

循环CAS就无法保证操作的原子性了,这个时候我们就需要采用锁的概念了;

还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,

合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的

原子性,就可以把多个变量放在一个对象里来进行CAS操作。

16.3 JDK中对CAS的支持 — Unsafe类

Java中提供了对CAS操作的支持:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

参数var1:表示要操作的对象

参数var2:表示要操作对象中属性地址的偏移量

参数var4:表示需要修改数据的期望的值

参数var5/var6:表示需要修改为的新值

【注意:】UnSafe类让Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。但是

这个类就如它的名字一样,是不安全的;UnSafe对象不能直接调用,只能哦通过反射获得;

16.4 JDK中的相关原子操作类简介--底层CAS机制

16.4.1 AtomicInteger

  ·int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。

  ·boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。

  ·int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。

  ·int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

16.4.2 AtomicIntegerArray

     这个类提供了更新数组中元素的原子操作,常用方法如下:

·int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。

·boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,

则以原子方式将数组位置i的元素设置成update值。

       ----需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,

所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

16.4.3 更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个

原子更新引用类型的类;

①AtomicReference 原子更新引用类型

②AtomicStampedReference  利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA的问题了

③AtomicMarkableReference 原子更新带有标记位的引用类型,

16.4.4 原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段

更新。要想原子地更新字段类需要两步。

第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态

方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

第二步,更新类的字段(属性)必须使用public volatile修饰符

· AtomicIntegerFieldUpdater 原子更新整型字段的更新器

· AtomicLongFieldUpdater  原子更新长整型字段的更新器

· AtomicReferenceFieldUpdater  原子更新引用类型的字段

十七、悲观锁和乐观锁

悲观锁:(总有刁民想谋害朕)

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这

样别人想拿这个数据就会阻塞。因此synchronized我们也将其称之为悲观锁。JDK中的ReentrantLock也是一种悲观锁。性能较差!

乐观锁:(从乐观的情况出发)

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,就算改了也没关系,再重试即可。所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去修改这个数据,如何没有人修改则更新,如果有人修改则重试。

CAS这种机制也可以将其称之为乐观锁。综合性能较好!

CAS获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。结合CAS和volatile可以实现无锁并发,

  适用于竞争不激烈、多核 CPU 的场景下。

(1)因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。

(2)但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。需要综合考虑!

;