多线程进阶——JUC并发编程

本文是我学习多线程进阶——JUC并发编程的笔记,其中包含了:

  1. JUC的介绍
  2. 线程和进程的回顾
  3. Lock锁
  4. 生产者与消费者问题
  5. 八锁现象
  6. 集合类不安全
  7. Callable
  8. 常用的辅助类
  9. 读写锁
  10. 阻塞队列
  11. 线程池
  12. 四大接口函数
  13. Stream流式计算
  14. ForkJoin详解
  15. 异步回调
  16. JMM
  17. Volatile
  18. 玩转单例模式
  19. 理解CAS
  20. 原子引用
  21. 各种锁的理解

1.什么是JUC

学习方向:源码+官方文档(高频面试部分)

JUC全称:Java.util.concurrent Java工具类中并发相关的包

​ Java.util.concurrent.atomic 原子性

​ Java.util.concurrent.locks Lock锁

java.util工具包:包主要用来分类

业务:普通的线程代码 Thread

​ Runnable没有返回值、效率相比Callable相对较低!

image

image

2.线程和进程

线程和进程。如果不能用一句话说出来的技术说明你不扎实

进程:一个程序,如:QQ.exe、Music.exe 程序的集合;

一个进程中包含多个线程,至少包含一个线程!

java默认有几个线程?2个(main线程和GC线程)

线程:开了一个进程Typora,输入文字和自动保存(线程负责)

对于Java而言创建线程方式:Tread、Runnable、callable

java真的可以开启线程吗?——开不了的,调用了本地的方法。Java是没有权限开启线程的

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    //本地方法 调用了底层的C++,Java无法操作硬件,运行在虚拟机之上
    private native void start0();

并发、并行

并发编程:并发、并行

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

  • 一核CUP,模拟出来多条线程,(天下武功唯快不破) 快速交换。

并行:多个人一起行走

  • 多核CPU,多个线程可以同时执行;线程池
public class Test1 {
    public static void main(String[] args) {
        //new Thread().start();
        //获取CPU的核数
        /*
        CUP密集型和IO密集型
         */
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用CPU的资源

所有公司都很看重的一个板块

企业:挣钱—>同高效率,裁员 —>找一个厉害的人顶替三个不怎么样的人

人员(减),技术成本(加)

线程有几个状态

public enum State {
   //新生
    NEW,

   //运行
    RUNNABLE,

    //阻塞
    BLOCKED,

    //等待(死死的等)
    WAITING,

    //超时等待
    TIMED_WAITING,

   //终止
    TERMINATED;
}

wait和sleep的区别

  1. 来自不同的类

    wait=>Object

    sleep=>Thread

    企业中让线程休眠不会用到sleep,

    TimeUnit.DAYS.sleep(1);//睡一天
    TimeUnit.SECONDS.sleep(1);//睡一秒
    
  2. 关于锁的释放

    wait会释放锁;sleep抱着锁睡觉,不会释放

  3. 使用的范围

    wait必须在同步代码块中

    sleep可以在任何地方谁

  4. 是否需要捕获异常

    wait和sleep都需要捕获异常InterruptedException

3.Lock锁(重点)

传统Synchronized

package com.xiaozhi.demo1;

/**
 * 基本的卖票例子
 * 真正的多线程开发,公司中的开发,一定要降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作
 * 1.属性   2.方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        //@FunctionalInterface  函数式接口,jdk1.8之后可以使用lambda表达式 (参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"A").start();

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

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

//资源类  OOP编程(只有属性和方法,不实现继承)
class Ticket{
    //属性
    private int number=50;
    //方法
    //卖票的方式
    //synchronized本质就是队列,锁
    //锁:锁两个东西,对象和class
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number + "张票");
        }
    }
}

锁的作用:锁住对象和class

Lock锁

image

image

image

公平锁:非常公平,可以先来后到

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

package com.xiaozhi.demo1;

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

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();

        //@FunctionalInterface  函数式接口,jdk1.8之后可以使用lambda表达式 (参数)->{代码}
        new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();

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

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

//Lock锁  三步曲
/*
1.new ReentrantLock();
2.lock.lock();//加锁
3.finally=> 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() + "卖出了" + (number--) + "票,剩余:" + number + "张票");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();//解锁
        }
    }
}

Synchronized 和 Lock锁的区别

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

锁是什么?如何判断锁的是谁!

4.生产者和消费者问题

笔试手写:单例模式、八大排序算法、生产者和消费者、死锁问题

Synchronized版

package com.xiaozhi.pc;

/**
 * 线程之间的通信问题,生产者和消费者问题  等待唤醒,通知唤醒
 * 线程交替执行 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();
    }
}

//判断等待、业务、通知
class Data{//数字 资源类
    private int number=0;
    //+1
    public synchronized void increment() throws InterruptedException {
       if (number!=0){
            //等待操作
           this.wait();
       }
       number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            //等待操作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我-1完毕
        this.notifyAll();
    }
}

问题存在,A、B、C、D四个线程!会出现虚假唤醒

image

解决方案:将if判断改为while判断

package com.xiaozhi.pc;

/**
 * 线程之间的通信问题,生产者和消费者问题  等待唤醒,通知唤醒
 * 线程交替执行 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 {
       while (number!=0){
            //等待操作
           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();
    }
}

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

image

通过Lock找到Condition

image

代码实现:

package com.xiaozhi.pc;

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

/**
 * 线程之间的通信问题,生产者和消费者问题  等待唤醒,通知唤醒
 * 线程交替执行 A B 操作同一个变量   num=0
 * A num+1
 * B num-1
 * C num+1
 * D num-1
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

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

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

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

//判断等待、业务、通知
class Data2{//数字 资源类
    private int number=0;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    //+1
    public void increment() throws InterruptedException {
        lock.lock();
       try {
           //业务代码
           while (number!=0){
               //等待操作
               condition.await();
           }
           number++;
           System.out.println(Thread.currentThread().getName() + "=>" + number);
           //通知其他线程,我+1完毕
           condition.signalAll();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
    }
    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                //等待操作
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我-1完毕
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,一定会有优势和补充

Condition,精准的通知和唤醒线程

image

代码测试:

package com.xiaozhi.pc;

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

/**
 * 要求:执行顺序为A-B-C
 * 生产线:思路举例:下单->支付->交易->物流。(举例只是让更好的理解,并不是真实的购物是这样实现的)
 */
public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        },"C").start();
    }
}

class Data3{//资源类 Lock锁
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number=1;//1A 2B 3C
    public void printA(){
        lock.lock();
        try {
            while (number!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
            //唤醒指定的人 B
            number=2;
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            //业务:判断->执行->通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB");
            number=3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while (number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCC");
            number=1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}

5.八锁现象

如何判断锁锁的是谁!什么是锁,锁的对象是谁!

深刻理解锁

package com.xiaozhi.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁:就是关于锁的八个问题
 * 标准情况:两个线程先打印发短信还是打电话   1.发短信 2.打电话
 *      原因不是先后调用的问题,而是锁的问题,因为有锁的存在
 * 发短信休息4s:两个线程先打印发短信还是打电话   1.发短信 2.打电话
 *
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

class Phone{
    //synchronized,锁的对象是方法的调用者!
    //两个方法用的同一把锁(phone的锁),谁先拿到谁执行
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
package com.xiaozhi.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁:就是关于锁的八个问题
 * 一个对象,增加了一个普通方法hello,是先打印hello还是先打印发短信? 1.hello2.发短信
 *两个对象,两个同步方法,是先打印打电话还是先打印发短信? 1.打电话 2.发短信
 *      根据时间来确定
 *
 */
public class Test2 {
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

class Phone2{
    //synchronized,锁的对象是方法的调用者!
    //两个方法用的同一把锁(phone的锁),谁先拿到谁执行
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    //这里没有锁,不是同步方法,不受锁的影响
    public  void hello(){
        System.out.println("hello");
    }
}
package com.xiaozhi.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁:就是关于锁的八个问题
 * 增加两个静态方法,只有一个对象,先打印打电话还是发短信? 发短信
 * 增加两个静态方法,两个对象,先打印打电话还是发短信? 发短信
 *
 */
public class Test3 {
    public static void main(String[] args) {
        //两个对象的Class类模板只有一个。static,锁的是Class
        Phone3 phone = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
//Phone3只有唯一的class对象(Class<Phone3> phone3Class = Phone3.class;)
class Phone3{
    //synchronized,锁的对象是方法的调用者!
    //static 静态方法,类一加载就有了,锁的是class(模板)
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }

}
package com.xiaozhi.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁:就是关于锁的八个问题
 *一个静态同步方法,一个普通同步方法,一个对象,先打印打电话还是发短信?打电话
 *一个静态同步方法,一个普通同步方法,两个对象,先打印打电话还是发短信?打电话
 */
public class Test4 {
    public static void main(String[] args) {
        //两个对象的Class类模板只有一个。static,锁的是Class
        Phone4 phone = new Phone4();
        Phone4 phone2 = new Phone4();

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

        try {
            TimeUnit.SECONDS.sleep(1);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
//Phone3只有唯一的class对象(Class<Phone3> phone3Class = Phone3.class;)
class Phone4{
    //静态同步方法  锁的class类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);//休息1s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //普通同步方法  锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }

}

小结

  • 锁 锁的是什么?
    • new出来的,表示this,具体的一个手机
    • static的,class,唯一的一个模板

6.集合类不安全

List不安全

package com.xiaozhi.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 集合的安全
 * java.util.ConcurrentModificationException  并发修改异常
 * 并发下ArrayList是不安全的
 * 解决方案:
 *      1.Vector  默认安全,在1.0发行,ArrayList在1.2发行。ArrayList的add方法为扩容,Vector add方法加了synchronized锁
 *      2.让ArrayList转变的安全
 *      3.JUC包下解决方案
 *          3.1 CopyOnWriteArrayList  写入时复制 COW思想,计算机程序设计领域的一种优化策略
 *              多个线程操作同一资源时,读取的时候是固定的,写入的时候会有覆盖现象
 *              此方法是为了在写入时避免覆盖,造成数据问题
 *              此处引发问题:读写分离,
 *              CopyOnWriteArrayList 比 Vector好在哪里?
 *              1. Vector的add方法加有synchronized方法,效率相对较低;CopyOnWriteArrayList用的lock锁
 *          3.2
 */
public class ListTest {
    public static void main(String[] args) {
    //单线程下
        List<String> list = Arrays.asList("1", "2", "3");
        list.forEach(System.out::println);//遍历集合(forEach中是函数式接口)

        System.out.println("===================");
        List<String> list1 = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list1.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list1);
        }

    //多线程下
        System.out.println("===================");
        //List<String> list2 = new ArrayList<>();//报错
        //List<String> list2 = new Vector<>();//解决方案1
        //List<String> list2 = Collections.synchronizedList(new ArrayList<>());//解决方案2
        List<String> list2 = new CopyOnWriteArrayList<>();//解决方案3.1
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                list2.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list2);
            },String.valueOf(i)).start();
        }
    }
}
  • CopyOnWriteArrayList 和 Vector 中Add方法对比

image

image

  • 小智学习方法推荐

    • 先会用
    • 货比三家(寻找其他解决方案)
    • 分析源码

Set 不安全

package com.xiaozhi.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理可证(List):ConcurrentModificationException
 * 解决方案:
 *     1.采用工具类
 *     2.写入时复制
 */
public class SetTest {
    public static void main(String[] args) {
        //HashSet<String> set = new HashSet<>();//报错ConcurrentModificationException
        //Set<String> set = Collections.synchronizedSet(new HashSet<>());//解决方案1
        CopyOnWriteArraySet<Object> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
               set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • hashSet的底层原理

    • 底层就是HashMap

      image

    • Set的add方法,本质就是map的key,map的key无法重复。PRESENT是一个常量,不变的值

      image

HashMap 不安全

回顾Map基本操作

image

package com.xiaozhi.unsafe;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
        //问题:1.map是这样用的吗?  不是,工作中不用HashMap
        //     2.默认等价于什么?  new HashMap<>(16,0.75);
        //Map<String,String>map= new HashMap<>();//报错,ConcurrentModificationException
        //Map<String,String>map=Collections.synchronizedMap(new HashMap<>());//解决方案1
        //研究ConcurrentHashMap,查看API帮助文档
        Map<String,String> map = new ConcurrentHashMap<>();//解决方案2
        for (int i =1; i <= 20; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}

7.Callable

image

  1. Callable可以有返回值
  2. Callable可以抛出异常
  3. 方法不同,Runnable为run方法,Callable为call方法

泛型的参数等于方法的返回值

image

代码测试

image

image

package com.xiaozhi.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 1.喜欢探究原理
 * 2.觉得自己会用就OK
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(Callable)).start();
        new Thread().start();//怎么启动Callable

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);//适配类

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

        Integer o = (Integer) futureTask.get();//获取Callable的返回结果,这个get方法可能会产生阻塞,把它放到最后一行,或异步通信
        System.out.println("Callable的返回结果: "+o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() {
        System.out.println("call()");
        return 1024;
    }
}
package com.xiaozhi.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 1.喜欢探究原理
 * 2.觉得自己会用就OK
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(Callable)).start();
        new Thread().start();//怎么启动Callable

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);//适配类

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果会被缓存,效率高

        Integer o = (Integer) futureTask.get();//获取Callable的返回结果,这个get方法可能会产生阻塞,把它放到最后一行,或异步通信
        System.out.println("Callable的返回结果: "+o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() {
        System.out.println("call()");
        return 1024;
    }
}

细节问题

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

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

8.1、CountDownLatch(减法计数器)

image

代码演示:

package com.xiaozhi.add;

import java.util.concurrent.CountDownLatch;

/**
 * 计数器
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是 6  必须要执行的任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "Go out");
                countDownLatch.countDown();//数量减1
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println("Close Door");
    }
}

原理:

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

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

8.2、CyclicBarrier(加法计数器)

image

加法计数器

package com.xiaozhi.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 集齐七颗龙珠召唤神龙
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp=i;//由于Lambda中无法获得i,所以创建了此中间件
            //Lambda能操作到i吗?
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "收集了"+temp+"个龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

8.3、Semaphore

Semaphore:信号量

image

代码演示:抢车位!(有六辆车,三个停车位)

package com.xiaozhi.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //参数:线程数量——>停车位  限流使用
        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);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}
  • 原理:
    • semaphore.acquire();//获得,假设如果已经满了,等待,等到被释放为止。
    • semaphore.release();//释放,会将当前的信号量释放+1,然后唤醒等待的线程!
  • 作用
    • 多个共享资源互斥的使用
    • 控制限流,控制最大的线程数

9.读写锁

ReadWriteLock

image

package com.xiaozhi.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁(又称为:独占锁(写锁)一次只能被一个线程占有,共享锁(读锁)多个线程可以同时占有)
 * ReadWriteLock
 * 读和读:可共存!
 * 读和写:不能共存!
 * 写和写:不能共存!
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        //MyCache myCache = new MyCache();
        MyCacheLock myCache = new MyCacheLock();

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

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

    }
}

/**
 * 自定义缓存
 */
class MyCache{
    private volatile Map<String,Object>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);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取OK");
    }
}

/**
 * 加锁的
 */
class MyCacheLock{
    private volatile Map<String,Object>map=new HashMap<>();
    //读写锁:更加细粒度的控制
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //存,写   只希望同时只有一个线程
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();

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

    //取,读  所有人都可以去读
    public void get(String key){
        readWriteLock.readLock().lock();

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


    }
}

10.阻塞队列

  • 阻塞队列

    • 阻塞:(不得不阻塞)

      • 写入时,队列满了必须阻塞,等待取出
      • 取出时,必须阻塞,等待写入
    • 队列:

      • 特性:FIFO(先进先出)

      image

阻塞队列:

image

image

BlockingQueue 不是新的东西,和List、Set是同级兄弟

image

什么时候什么阻塞队列? 多线程并发处理、线程池

学会使用队列:

添加、移除、四组API(抛出异常、不会抛出异常、阻塞等待、超时等待)

四组API:

方式 抛出异常 不会抛出异常,有返回值 阻塞 等待 超时等待
添加 add() offer() put() offer("d",2, TimeUnit.SECONDS)
移除 remove() poll() take() poll(2,TimeUnit.SECONDS)
检测队首元素 element() peek() - -
/**
 * 抛出异常
 */
public static void test1(){
    //队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.add("a"));
    System.out.println(blockingQueue.add("b"));
    System.out.println(blockingQueue.add("c"));
    //System.out.println(blockingQueue.add("d"));//抛出异常  java.lang.IllegalStateException: Queue full

    System.out.println("==================");
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());//抛出异常  java.util.NoSuchElementException
}
/**
 * 不抛出异常,有返回值
 */
public static void test2(){
    //队列的大小
    ArrayBlockingQueue 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(blockingQueue.offer("d"));//false  不抛出异常

    System.out.println("==================");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());//null 不抛出异常

    System.out.println("==================");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());//null 不抛出异常
}
/**
 * 阻塞等待 (一直阻塞)
 */
public static void test3() throws InterruptedException {
    //队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    //blockingQueue.put("d");//队列没有位置,一直等待
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());//队列没有元素,一直等待

}
/**
 * 阻塞等待 (超时退出)
 */
public static void test4() throws InterruptedException {
    //队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.offer("a");
    blockingQueue.offer("b");
    blockingQueue.offer("c");
    blockingQueue.offer("d",2, TimeUnit.SECONDS);//队列没有位置,等待两秒后退出
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));//队列没有元素,等待两秒后退出,如果为空返回null

}

SynchronousQueue 同步队列

不存储元素,没有容量;进去一个元素,必须等待取出之后,才能再放入一个元素!

存:put 取:take

package com.xiaozhi.bq;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

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

        new Thread(()->{
            try {
                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 (Exception e){
                e.printStackTrace();
            }
        },"T2").start();


    }
}

学了技术不会用?看得少!

11.线程池(重点)

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

池化技术

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

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

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

线程池的好处:

  1. 降低资源消耗

  2. 提高响应速率

  3. 方便管理

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

线程池:三大方法

image

OOM:全称“Out Of Memory”,翻译成中文就是“内存用完了”

package com.xiaozhi.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *Executors  工具类:三大方法
 */
public class Demo1 {
    public static void main(String[] args) {
       // ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
       // ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i < 10; 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<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,//约等于21亿
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }    

本质:调用了ThreadPoolExecutor()
    
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                          int maximumPoolSize,//最大核心线程池大小
                          long keepAliveTime,//超时了没有人调用就是释放
                          TimeUnit unit,//超时单位
                          BlockingQueue<Runnable> workQueue,//阻塞队列
                          ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
                          RejectedExecutionHandler handler//拒绝策略) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

image

image

举例解释:如上图。银行

  • corePoolSize 核心线程池大小

    为平时去银行开设的业务窗口数量(在不忙时银行窗口不会全部开)

  • maximumPoolSize 最大核心线程池大小

    为银行设有的窗口数量(在客户特别多时,最大可开的窗口数量)

  • keepAliveTime 超时了没有人调用就是释放

    当忙完时,等待keepAliveTime 时长,如果不会有人办理业务,将关闭除corePoolSize以外的窗口

  • TimeUnit unit 超时单位

  • BlockingQueue workQueue 阻塞队列

    等候区(设置大小为最大可容多少客户等待)

  • ThreadFactory threadFactory 线程工厂,用于创建线程,一般不用动

  • RejectedExecutionHandler handler 拒绝策略

    当业务窗口和等候区人满时,再进入的可会处理策略

手动创建一个线程池

package com.xiaozhi.pool;

import java.util.concurrent.*;

/**
 *自定义线程池:七大参数
 */
public class Demo2 {
    public static void main(String[] args) {
      //自定义线程池,工作中只会用 ThreadPoolExecutor
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());

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

四种拒绝策略

image

/**
 *四大拒绝策略
 * new ThreadPoolExecutor.AbortPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将不处理此线程,抛出异常java.util.concurrent.RejectedExecutionException
 * new ThreadPoolExecutor.CallerRunsPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将哪里来的回哪里去
 * new ThreadPoolExecutor.DiscardPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将丢掉任务,不会抛出异常
 * new ThreadPoolExecutor.AbortPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将尝试和最早的竞争,最早的结束将被执行,未结束将被丢弃,不会抛出异常
 */

小结和拓展

了解IO密集型和CPU密集型:(调优)

  • 线程池的最大线程数(maximumPoolSize)该如何定义?

    1. CPU密集型:几核就定义为几,可以保证CPU效率最高

      获取CUP核数的方式:

      Runtime.getRuntime().availableProcessors()
      
    2. IO密集型:一般设置为你程序十分耗IO的线程数量的2倍

12.四大函数式接口(必须掌握)

旧时代的程序员:泛型、枚举、反射、注解

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

函数式接口:只有一个方法的接口。Runnable接口为典型

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
//FunctionalInterface此注解使用超多
//简化编程模式,在新版本的框架底层大量应用
//foreach(消费者类型的函数式接口)

image

代码测试:

Function 函数式接口

image

package com.xiaozhi.function;

import java.util.function.Function;

/**
 * Function 函数型接口
 * 有一个输入参数,有一个输出
 *只要是函数式接口,我们就可以用Lambda表达式简化
 */
public class Demo1 {
    public static void main(String[] args) {
        //工具类:输出输入的值
        /*Function<String, String> function = new Function<String, String>(){

            @Override
            public String apply(String str) {
                return str;
            }
        };*/
        Function<String, String> function =(str)->{return str;};//Lambda表达式简化
        System.out.println(function.apply("qwe"));
    }
}

Predicate 断定型接口

image

package com.xiaozhi.function;

import java.util.function.Predicate;

/**
 * Predicate:断定型接口
 *    有一个输入参数,返回值只能为布尔值
 */
public class Demo2 {
    public static void main(String[] args) {
        //判断字符串 是否为空
        /*Predicate<String> predicate = new Predicate<String>(){

            @Override
            public boolean test(String o) {
                return o.isEmpty();
            }
        };*/

        Predicate<String> predicate = (o)->{ return o.isEmpty(); };
        System.out.println(predicate.test(""));
    }
}

Consumer 消费型接口

image

package com.xiaozhi.function;

import java.util.function.Consumer;

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

        Consumer<String> consumer =(o)->{System.out.println(o);};
        consumer.accept("asd");
    }
}

Supplier 供给型接口

image

package com.xiaozhi.function;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口
 *没有参数,只有返回值
 */
public class Demo4 {
    public static void main(String[] args) {
       /* Supplier<Integer> supplier = new Supplier<Integer>() {

            @Override
            public Integer get() {
                System.out.println("get()");
                return 1024;
            }
        };*/

        Supplier<Integer> supplier =()->{
            System.out.println("get()");
            return 1024;
        };
        System.out.println(supplier.get());
    }
}

13.Stream流式计算

什么是Stream流式计算?

大数据主要:存储+计算

存储:集合、MySQL的本质

计算应该交给流来操作

image

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 *题目要求:一分钟内完成此题,只能用一行代码实现!
 *现在有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<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        //计算交给Stream流
        Stream<User> stream = list.stream();//将集合转换为stream对象
        //用到了 lambda表达式、链式编程、函数式接口、stream流式计算
        stream
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>23;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);

    }
}

package com.xiaozhi.stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//有参、无参构造、get、set、toString方法!
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}

14.ForkJoin详解

什么是ForkJoin

ForkJoin:意思为分支合并

ForkJoin在JDK1.7发行,用于并行执行任务!在大数据量提高效率!一个线程并发成多个操作

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

image

ForkJoin特点:工作窃取

这个里面维护的都是双端队列

image

B执行完会窃取A的任务执行,从而提高效率

ForkJoin操作

image

image

package com.xiaozhi.forkJoin;

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务
 * 人分369等
 * 3(常规方法)   6(ForkJoin) 9(Stream并行流)
 * 如何使用?
 *     1.forkjoinPool通过他执行
 *     2.计算任务forkjoinPool.execute(ForkJoinTask<?> task)
 *     3.计算类要继承ForkJoinTask
 *     4.
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    //ForkJoin方法
    private Long start;
    private Long end;
    //临界值
    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();//拆分任务,把任务压入线程队列
            long sum = task1.join() + task2.join();//两个分支任务结果相加
            return sum;
        }
    }
}
package com.xiaozhi.forkJoin;

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

/**
 * 当数据量大时:常规 < ForkJoin < Stream
 * 同一任务别人效率高你几十倍
 */
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("test1-->sum" +sum+ "time:" + (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("test2-->sum" +sum+ "time:" + (end - start));
    }
    //Stream并行流
    public static void test3(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("test3-->sum" +sum+ "time:" + (end - start));
    }
}

15.异步回调

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

image

package com.xiaozhi.future;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 异步调用:CompletableFuture
 * 异步执行
 * 成功回调
 * 失败回调
 *
 */
public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值的 runAsync 异步回调
        /*CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
        });
        System.out.println("1111");
        completableFuture.get();//阻塞获取执行结果*/

        //有返回值的 supplyAsync 异步回调
        //ajax 成功和失败的回调
        //返回的是错误信息
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
            int i=1/0;
            return 1024;
        });
        completableFuture.whenComplete((t,u)->{//编译成功时
            System.out.println("t=>" + t);//正常的返回结果
            System.out.println("u=>" + u);//错误信息
        }).exceptionally((e)->{//编译失败时
            System.out.println(e.getMessage());//获取异常详细信息
            return 233;//可以获得错误的返回结果
        }).get();

    }
}

16.JMM

请你谈谈Volatile理解

Volatile时Java虚拟机提供轻量级的同步机制(和synchronized差不多,但是没有synchronized强大)

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

聊到可见性就谈到JMM

JMM:Java内存模型,不存在的东西,就是一个概念(约定)!

关于JMM同步的约定:

  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中
  3. 必须保证加锁和解锁是同一把锁

线程分为工作内存主内存

JMM八种操作:

image

image

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

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

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

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

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

​ Volatile可解决此问题

image

代码:

package tvolatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    private 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);
    }
    /**
     * 问题:线程1无法获得main线程修改num后的值。所以导致程序无法停止
     */
}

17.Volatile

验证Volatile三大特性—保证可见性

package tvolatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    //不加volatile线程1就会死循环,因为感知不到主内存的变化,加了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);
    }
}

验证Volatile三大特性—不保证原子性

原子性:ACID原则之一,意为不可分割

线程A 执行任务时,不能被打扰和分割,要么同时成功,要么同时失败。JDBC和MySQL中详细讲到ACID原则

package tvolatile;

/**
 * 不保证原子性
 */
public class VDmeo2 {
    /*private static int num=0;
    public static void add(){
        num++;
    }
    public static void main(String[] args) {
        //理论上是2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){//main线程 和 GC线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "" + num);//main19654
    }*/


    private volatile static int num=0;//不保证原子性
    public static void add(){
        num++;
    }
    public static void main(String[] args) {
        //理论上是2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){//main线程 和 GC线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "" + num);//main19000
    }

    /*private static int num=0;//保证原子性
    public synchronized static void add(){
        num++;
    }
    public static void main(String[] args) {
        //理论上是2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){//main线程 和 GC线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "" + num);//main20000
    }*/

}

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

image

使用原子类,解决原子性问题(锁更耗费资源)

image

//源自类操作
private volatile static AtomicInteger num=new AtomicInteger();//保证原子性
public static void add(){
    num.getAndIncrement();//AtomicInteger+1,Increment为+1方法,并不是简单的+1操作,用的是CAS(CPU的并发原理效率极高)
}
public static void main(String[] args) {
    //理论上是2万
    for (int i = 1; i <= 20; i++) {
        new Thread(()->{
            for (int j = 0; j < 1000; j++) {
                add();
            }
        }).start();
    }
    while (Thread.activeCount()>2){//main线程 和 GC线程
        Thread.yield();
    }
    System.out.println(Thread.currentThread().getName() + "" + num);//main19000
}

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

验证Volatile三大特性—禁止指令重排

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

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

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

int x=1;  //1
int y=2;  //2
x=x+5;  //3
y=x*x;  //4
我们期望的执行顺序是1234  但是可能执行的时候会变成2134  1324
    但不可能是4123!

可能造成影响的结果:a b x y 这四个值默认都为0;

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

正常结果:x=0; y=0; 但是可能由于指令重排造成:

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

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

volatile可以避免指令重排:

  • 内存屏障、CPU指令,作用:
  1. 保证特定的操作的执行顺序

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

    image

    只要加了Volatile就会在前后加油一道内存屏障

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

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

18.彻底玩转单例模式

  • 饿汉式
  • 懒汉式
    • DCL懒汉式用到了内存屏障

搞懂单例模式、搞懂枚举(可以避免单例模式被破坏)

饿汉式

package com.xiaozhi.single;

/**
 * 饿汉式(一上来就把这个对象进行加载)
 */
public class Hungry {
    //由于一上来就加载,可能浪费空间
    private byte[]data1=new byte[1024*1024];
    private byte[]data2=new byte[1024*1024];
    private byte[]data3=new byte[1024*1024];
    private byte[]data4=new byte[1024*1024];

    //私有构造器,别人无法new此对象,保证内存中只有一个对象
    private Hungry(){

    }
    //不管怎么样先new对象
    private final static Hungry HUNGRY=new Hungry();

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

懒汉式

package com.xiaozhi.single;

/**
 * 懒汉式单例模式
 * 单线程下线程安全,并发情况有问题
 * 不是一上来就加载对象,而实用到时才会加载
 */
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "OK");
    }
    /*//不安全的懒汉式单例模式
    private static LazyMan lazyMan;
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }*/

    //双重检测锁模式的懒汉式单例  DCL懒汉式
    private volatile static LazyMan lazyMan;//需要避免指令重排
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){//锁当前对象,使对象只有一个
                if (lazyMan==null){
                     lazyMan=new LazyMan();//不是原子性操作
                    /*
                    1.分配内存空间
                    2.执行构造方法,初始化对象
                    3.把这个对象指向这个空间
                    理想123顺序
                    可能132顺序  A线程,如果有B线程进入,B会判断为!=null,没完成构造时返回
                     */
                }
            }
        }
        return lazyMan;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

通过反射破获加锁的懒汉式单例模式(并发安全)

package com.xiaozhi.single;

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

/**
 * 通过反射破解安全的懒汉式
 * 任何代码和关键字在反射面前都是不安全的
 */
public class Demo4 {
    private Demo4(){
        //解决方案
        synchronized (Demo4.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图通过反射破坏");
            }
        }
    }
    //双重检测锁模式的懒汉式单例  DCL懒汉式
    private volatile static Demo4 lazyMan;//需要避免指令重排
    public static Demo4 getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){//锁当前对象,使对象只有一个
                if (lazyMan==null){
                    lazyMan=new Demo4();//不是原子性操作
                }
            }
        }
        return lazyMan;
    }
    //反射
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Demo4 instance = Demo4.getInstance();
        Constructor<Demo4> declaredConstructor = Demo4.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//无视私有
        Demo4 instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

当不用Demo4.getInstance();方式创建对象时,用declaredConstructor.newInstance();创建对象将出现新的问题

解决方案

package com.xiaozhi.single;

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

/**
 * 通过反射破解安全的懒汉式
 * 任何代码和关键字在反射面前都是不安全的
 */
public class Demo5 {
    //解决方案:添加红绿灯
    private static boolean xiaozhi=false;
    private Demo5(){
        if (xiaozhi==false){
            xiaozhi=true;
        }else {
            throw new RuntimeException("不要试图通过反射破坏");
        }
    }
    //双重检测锁模式的懒汉式单例  DCL懒汉式
    private volatile static Demo5 lazyMan;//需要避免指令重排
    public static Demo5 getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){//锁当前对象,使对象只有一个
                if (lazyMan==null){
                    lazyMan=new Demo5();//不是原子性操作
                }
            }
        }
        return lazyMan;
    }
    //反射
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /*Demo4 instance = Demo4.getInstance();*/
        Constructor<Demo5> declaredConstructor = Demo5.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//无视私有
        Demo5 instance = declaredConstructor.newInstance();
        Demo5 instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

若通过反编译将其破解则不安全

package com.xiaozhi.single;

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

/**
 * 通过反射破解安全的懒汉式
 * 任何代码和关键字在反射面前都是不安全的
 * 道高一尺魔高一丈
 */
public class Demo5 {
    //解决方案:添加红绿灯
    private static boolean xiaozhi=false;
    private Demo5(){
        if (xiaozhi==false){
            xiaozhi=true;
        }else {
            throw new RuntimeException("不要试图通过反射破坏");
        }
    }
    //双重检测锁模式的懒汉式单例  DCL懒汉式
    private volatile static Demo5 lazyMan;//需要避免指令重排
    public static Demo5 getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){//锁当前对象,使对象只有一个
                if (lazyMan==null){
                    lazyMan=new Demo5();//不是原子性操作
                }
            }
        }
        return lazyMan;
    }
    //反射
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Field xiaozhi = Demo5.class.getDeclaredField("xiaozhi");//获取字段
        xiaozhi.setAccessible(true);//无视私有
        Constructor<Demo5> declaredConstructor = Demo5.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//无视私有
        Demo5 instance = declaredConstructor.newInstance();
        xiaozhi.set(instance,false);//更改值
        Demo5 instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

反射无法攻破枚举验证

image

package com.xiaozhi.single;

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

/**
 * enum 是一个什么?本身也是一个class类
 */
public enum  EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

枚举的最终反编译源码:

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space 
// Source File Name:   EnumSingle.java

package com.xiaozhi.single;


public final class EnumSingle extends Enum
{

	public static final EnumSingle INSTANCE;
	private static final EnumSingle $VALUES[];

	public static EnumSingle[] values()
	{
		return (EnumSingle[])$VALUES.clone();
	}

	public static EnumSingle valueOf(String name)
	{
		return (EnumSingle)Enum.valueOf(com/xiaozhi/single/EnumSingle, name);
	}

	private EnumSingle(String s, int i)
	{
		super(s, i);
	}

	public EnumSingle getInstance()
	{
		return INSTANCE;
	}

	static 
	{
		INSTANCE = new EnumSingle("INSTANCE", 0);
		$VALUES = (new EnumSingle[] {
			INSTANCE
		});
	}
}

静态内部类

package com.xiaozhi.single;

/**
 * 静态内部类
 */
public class Holder {
    private Holder(){

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

19.深入理解CAS

什么是CAS

大厂必须深入研究底层!有所突破!修内功!操作系统、计算机网络框架

CAS为compareAndSet的缩写。

package com.xiaozhi.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS:CPU的并发原语
 * 原子类的底层是CAS
 */
public class CASDemo {

    public static void main(String[] args) {
        //atomicInteger是原子类的Integer
        AtomicInteger atomicInteger=new AtomicInteger(2020);//初始值
        //public final boolean compareAndSet(int expect, int update)
        //我期望的值是2020,如果初始值达到则更新为2021,否则不更新
        atomicInteger.compareAndSet(2020,2021);//比较并交换!,返回值为boolean值
        System.out.println(atomicInteger.get());
        
        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

image

image

image

image

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

缺点:

  1. 由于底层是自旋锁,会一直循环,耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

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

image

ABA问题:有两条线程都获取到初始值为1,B线程经过两次cas操作后,A线程在进行cas操作时并不知道,现在A的值是B经过修改的值(1—>3—>1),还以为是最初A本身的1

package com.xiaozhi.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS:CPU的并发原语
 * 原子类的底层是CAS
 */
public class CASDemo1 {

    public static void main(String[] args) {
        //atomicInteger是原子类的Integer
        AtomicInteger atomicInteger=new AtomicInteger(2020);//初始值
        //public final boolean compareAndSet(int expect, int update)
        //我期望的值是2020,如果初始值达到则更新为2021,否则不更新
        //=================捣乱的线程=========================
        atomicInteger.compareAndSet(2020,2021);//比较并交换!,返回值为boolean值
        System.out.println(atomicInteger.get());
        atomicInteger.compareAndSet(2021,2020);//比较并交换!,返回值为boolean值
        System.out.println(atomicInteger.get());

        //====================期望的线程=======================
        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(2020, 666));
        System.out.println(atomicInteger.get());
    }
}

解决方案:运用原子引用

20.原子引用

解决ABA问题,引入原子引用,对应的思想就是一个乐观锁

带版本号的原子操作!

package com.xiaozhi.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 原子引用:和乐观锁的原理相同
 * 注意:如果泛型是一个包装类,注意对象的引用问题
 * 正常在业务操作中,比较的是一个对象,而不是Integer
 */
public class CASDemo2 {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atom = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int stamp = atom.getStamp();//获得目前的版本号(initialStamp)

            System.out.println("A1=>" + stamp);

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

            System.out.println(atom.compareAndSet(1, 2
                    , atom.getStamp(), atom.getStamp() + 1));

            System.out.println("A2=>" + atom.getStamp());

            System.out.println(atom.compareAndSet(2, 1
                    , atom.getStamp(), atom.getStamp() + 1));

            System.out.println("A3=>" + atom.getStamp());

        },"A").start();

        new Thread(()->{
            int stamp = atom.getStamp();//获得目前的版本号(initialStamp)
            System.out.println("B1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atom.compareAndSet(1, 6
                    , stamp, stamp + 1));
            System.out.println("B2=>" + atom.getStamp());//打印最新版本号
        },"B").start();
    }
}

注意:

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

image

21.各种锁的理解

1.公平锁、非公平锁

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

//代码
Lock lock = new ReentrantLock(true);
//源码
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁:非常不公平,可插队,(默认都是非公平)例如:两个任务耗时:3s,3h,这种情况3s任务是可以插队的

//代码
Lock lock = new ReentrantLock();
//源码
public ReentrantLock() {
    sync = new NonfairSync();
}

公平锁和非公平锁—在Lock锁章节有讲解

2.可重入锁

所有的锁都是可重入锁(递归锁)

image

synchronized版

package com.xiaozhi.lock;

/**
 * synchronized 版可重入锁
 */
public class Demo1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();

        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone{
    //可重入锁
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + "sms");
        call();//这里有锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + "call");
    }
}

Lock版

package com.xiaozhi.lock;

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

/**
 * Lock版可重入锁
 * 在使用lock锁时,锁必须配对,否则容易发生死锁现象
 */
public class Demo2 {
    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone2.sms();
        },"A").start();

        new Thread(()->{
            phone2.sms();
        },"B").start();
    }
}
class Phone2{
    Lock lock=new ReentrantLock();
    //可重入锁  //与synchronized相比,这里是两把锁
    public void sms(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "sms");
            call();//这里有锁
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "call");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


注:在使用lock锁时,锁必须配对,否则容易发生死锁现象

3.自旋锁(spinLock)

image

自旋锁:不断地循环迭代,直到成功为止

自定义自旋锁

package com.xiaozhi.lock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinLockDemo {
    AtomicReference<Thread>atomicReference=new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> myLock");
        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }
    //解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "myUnLock");
        atomicReference.compareAndSet(thread,null);
    }



}

测试自定义自旋锁

package com.xiaozhi.lock;

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

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

        TimeUnit.SECONDS.sleep(1);

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

运行结果:

image

4.死锁

死锁是什么?

image

相当于两个线程互相抢夺资源

死锁测试,怎么排除死锁?

package com.xiaozhi.lock;

import java.util.concurrent.TimeUnit;

/**
 * 死锁
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA="lockA";
        String lockB="lockB";
        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}
class MyThread implements Runnable{
    private String lockA;
    private String lockB;

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


    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "lock:" + lockA + "=>" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>" + lockA);
            }
        }
    }
}

解决问题

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

    image

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

    image

面试或者工作中排查问题方法:

  1. 看日志
  2. 堆栈信息

死锁产生的4个必要条件:

  1. 互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
  2. 占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
  3. 不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  4. 循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。

注:本文是我在再次温习注解和反射的过程中记下的笔记。谢谢大家的阅读。如有不足之处,请留言,QQ:825888114!

posted @ 2021-04-20 23:59  小智学编程  阅读(188)  评论(0)    收藏  举报