JUC并发编程学习知识点整理

一、JUC并发编程

面试高频问!

jdk帮助文档: http://docs.oracle.com/javase/8/docs/api/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Tj3sTCz-1639826957348)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216194401961.png)]

业务:普通的线程代码: Thread

Runnable: 没有返回值,效率相比Callable相对较低

1.1 回顾以前

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VrLC7wes-1639826957349)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216195356605.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2f1SJeZr-1639826957350)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216195419335.png)]

二、线程和进程

线程、进程

进程

一个程序,QQ.exe, Music.exe 程序的集合:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wubBLGHw-1639826957352)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216195631738.png)]

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

Java固定有几个线程?

  1. main主线程
  2. GC垃圾回收线程

线程

开启了一个进程Typorya,写字,自动保存(线程负责的)

Thread、Runnable、Callable

Java真的可以开启线程吗?

不可以!

java是开启native,启动底层的C++去让CPU开启一个线程,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++去操作硬盘开启一个线程
private native void start0();

并发、并行

并发编程: 并发、并行

并发(多线程操作同一个资源)

  • CPU一核,模拟出来多条线程,快速交替

并行(多个人一起行走)

  • 逻辑处理器有几个可以同时处理几个; 线程池

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgHT99Yd-1639826957353)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216200548112.png)]

  • Java代码查看CPU核数

    // 获取CPU的核数
    System.out.println(Runtime.getRuntime().availableProcessors());
    

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

    所有的公司都很看重这个东西

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

    人员(减)、技术成本(高)

2.1 线程的状态

Thread.State()

 public enum State {
    // 新生
     NEW,
     
	// 运行
     RUNNABLE,

    // 阻塞
     BLOCKED,

    // 等待,死死的灯
     WAITING,

     // 超时等待
     TIMED_WAITING,

     // 终止
     TERMINATED;
 }

2.2 wait/sleep区别

  1. 来自不同的类

    wait => Object

    sleep => Thread

  2. 关于锁的释放

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

  3. 使用范围不同

    wait : 必须在同步代码块中

    sleeep : 可以在任何地方睡

  4. 是否需要补货异常

    wait: 不需要捕获异常

    sleep: 需要捕获异常

2.3 Lock锁(重点)

synchronized

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

    //@FunctionalInterface 函数式接口
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"A").start();
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"B").start();
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"C").start();
  }
}

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

  // synchronized 排队等资源,队列 锁
  public synchronized void sale() {
    if (number > 0) {
      System.out.println(Thread.currentThread().getName() + "卖出了:" + (number--) + "票剩余:" + number);
    }
  }
}

lock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oanQ76oh-1639826957354)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211216204428347.png)]

公平锁:十分公平:挨个排序

非公平锁: 不公平,可以插队去执行(系统默认)

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

    //@FunctionalInterface 函数式接口
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"A").start();
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"B").start();
    new Thread(()-> {
      for (int i = 0; i < 50; i++) {
        ticket.sale();
      }
    },"C").start();
  }
}

// 资源类 OOP
class Ticket2 {
  // 属性、方法
  private int number = 50;
  // 导入lock锁的类
  Lock lock =  new ReentrantLock();
  // lock手动锁
  public void sale() {
    try {
      // 加锁
      lock.lock();

      //业务块代码
      if (number > 0) {
        System.out.println(Thread.currentThread().getName() + "卖出了:" + (number--) + "票剩余:" + number);
      }
    }catch (Exception e) {

    }finally {
      lock.unlock();
    }
  }
}

2.4 Synchronized和Lock区别

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

2.5 锁是什么,如何判断锁的是谁

见3.5 8锁现象

三、生产者和消费者问题

3.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;
  public synchronized void increment() throws InterruptedException {
    if (number != 0) {
      // 等待
      this.wait();
    }
    // 业务
    number ++;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    this.notifyAll();
  }

  public synchronized  void decrement() throws InterruptedException {
    if (number == 0) {
      // 等待
      this.wait();
    }
    // 业务
    number--;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    this.notifyAll();
  }

}

生产者消费者存在的问题(虚假唤醒,解决办法:改用while进行判断)

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.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    },"B").start();
    new Thread(()-> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } 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;
  public synchronized void increment() throws InterruptedException {
    //改用while循环判断,避免虚假唤醒
    while (number != 0) {
      // 等待
      this.wait();
    }
    // 业务
    number ++;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    this.notifyAll();
  }

  public synchronized  void decrement() throws InterruptedException {
    //改用while循环判断,避免虚假唤醒
    while (number == 0) {
      // 等待
      this.wait();
    }
    // 业务
    number--;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    this.notifyAll();
  }
}

3.2 采用lock来实现生产者消费者问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LQILU4XV-1639826957356)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217123406704.png)]

3.3 采用Lock实现指定线程的通知

/**
 * 采用JUC的方式进实现生产者消费者问题
 */
public class B {
  public static void main(String[] args) {
    Data2 data2 = new Data2();
    new Thread(()-> {
      for (int i = 0; i < 10; i++) {
        data2.print1();
      }
    },"A").start();

    new Thread(()-> {
      for (int i = 0; i < 10; i++) {
        data2.print2();
      }
    },"B").start();

    new Thread(()-> {
      for (int i = 0; i < 10; i++) {
        data2.print3();
      }
    },"C").start();
  }
}


// 定义一个资源类
class Data2{
  private int number = 1;
  final Lock lock = new ReentrantLock();
  Condition condition1 =  lock.newCondition();
  Condition condition2 =  lock.newCondition();
  Condition condition3 =  lock.newCondition();

  // 打印方法
  public void print1() {
    //加锁
    lock.lock();
    try {
      //放止虚假唤醒状态
      while (number != 1) {
        //线程等待
        condition1.await();
      }
      //业务
      System.out.println(Thread.currentThread().getName() + "=>" + "执行了AAA");
      number = 2;
      //通知线程2
      condition2.signal();
    }catch (Exception e) {

    }finally {
      lock.unlock();
    }
  }

  public void print2() {
    lock.lock();
    try{
      while (number!=2) {
        condition2.await();
      }
      System.out.println(Thread.currentThread().getName() + "=>执行了BBB");
      number = 3;
      condition3.signal();
    }catch (Exception e) {

    }finally {
      lock.unlock();
    }
  }

  public void print3() {
    lock.lock();
    try {
      while (number!=3) {
        condition3.await();
      }
      System.out.println(Thread.currentThread().getName() + "=>执行了CCC");
      number = 1;
      condition1.signal();
    }catch (Exception e) {

    }finally {
      lock.unlock();
    }
  }
}

3.4 区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXWF9Ebm-1639826957357)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217123543385.png)]

四、8锁现象

/**
 * 8锁:就是关于锁的8个问题
 * 1、标准情况下,两个线程先打印,是先发短信还是打电话? A.发短信   B.打电话                    A
 * 2、sendSms加一个等待时间,两个线程先打印,是先发短信还是打电话? A.发短信   B.打电话          A
 */
public class Test1 {
  public static void main(String[] args) {
    Phone phone = new Phone();
    new Thread(() -> {
      phone.sendSms();
    },"A").start();

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

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

class Phone{
  // synchronized锁的对象是方法的调用者
  // 两个方法是拿的同一个锁,谁先拿到谁执行
  public synchronized void sendSms() {
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("sendSms");
  }

  public synchronized void call() {
    System.out.println("call");
  }
}
/**
 * 3、加一个普通方法,两个线程先打印,是先发短信还是打hello? A.发短信   B.hello                     B
 * 4、两个调用者,调用方法都加锁,先发短信还是先打电话 ?     A.发短信   B.打电话                     B
 */
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);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

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

class Phone2{
  // synchronized锁的对象是方法的调用者
  // 两个方法是拿的同一个锁,谁先拿到谁执行
  public synchronized void sendSms() {
    try {
      TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("sendSms");
  }

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

  public void hello() {
    System.out.println("hello world");
  }
}
/**
 * 5、一个对象,增加两个静态同步方法,只有一个对象,先打印发短信还是打电话?  A.发短信   B.打电话                     A
 * 6、两个对象,增加两个静态的同步方法,先打印发短信还是打电话             A.发短信   B.打电话                    A
 */
public class Test3 {
  public static void main(String[] args) {
    // 两个对象两个调用者
    Phone3 phone = new Phone3();
    Phone3 phone2 = new Phone3();
    new Thread(() -> {
      phone.sendSms();
    },"A").start();

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

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

class Phone3{
  // synchronized锁的对象是方法的调用者
  // 两个方法是拿的同一个锁,谁先拿到谁执行
  // static类一加载就有了,锁的是对象class模板
  public static synchronized void sendSms() {
    try {
      TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("sendSms");
  }

  public static synchronized void call() {
    System.out.println("call");
  }

  public void hello() {
    System.out.println("hello world");
  }
}
/**
 * 7、一个静态方法和一个普通的锁方法,一个对象,先发短信还是先打电话   打电话
 * 8、一个静态方法和一个普通的锁方法,一个对象,先发短信还是先打电话   打电话
 */
public class Test4 {
  public static void main(String[] args) {
    Phone4 phone4 = new Phone4();
    new Thread(()-> {
      Phone4.sendMsg();
    },"A").start();

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

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

class Phone4{
  public static synchronized void sendMsg() {
    try {
      TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("发短信");
  }

  public synchronized void call() {
    System.out.println("打电话");
  }
}

五、集合不安全

5.1 List不安全

  1. 在并发操作写入ArrayList的时候,会报错:并发修改异常

    // Exception in thread "Thread-9" java.util.ConcurrentModificationException 并发修改异常
    
  2. 代码

    public class ListTest {
      public static void main(String[] args) {
        //并发下ArrayList 不安全的
        /**
         * 解决方法
         * 1. List<String> list = new Vector<>();                                      jdk1.0就出现了
         * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());     jdk自带的
         * 3. CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
         */
        // List<String> list = new ArrayList<>();
        // List<String> list = new Vector<>();
        // List<String> list = Collections.synchronizedList(new ArrayList<>());
        // CopyOnWrite 写入时复制  COW  计算机程序设计的一种优化策略
        // 多个线程调用时候,list,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖,造成数据问题
        // 读写分离
        // CopyOnWriteArrayList 比Vector 牛逼在哪里?   synchronized的效率比较低,没有lock手动解锁快
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    
        for (int i = 0; i < 10; i++) {
          new Thread(() -> {
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
          }).start();
        }
      }
    }
    

5.2 Set不安全

  1. 在写入的时候,会出现并发异常

    // Exception in thread "Thread-9" java.util.ConcurrentModificationException 并发修改异常
    
  2. 代码

    Set<Object> set2 = new HashSet<>();
    // Set<Object> set = Collections.synchronizedSet(new HashSet<>());
    CopyOnWriteArraySet<Object> set = new CopyOnWriteArraySet<>();
    for (int i = 0; i < 20; i++) {
        new Thread(()-> {
            set2.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(set);
        }).start();
    

5.3 hashSet底层

实际就是一个hashmap的key,key是不重复的

public HashSet() {
    map = new HashMap<>();
}

hashset的add方法实际上就是map put了一个值进去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1eL4TS2Y-1639826957358)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217152503476.png)]

5.4 Map不安全

// map是这样用的吗?  不是,工作中不用hashmap
// 默认等价于什么?    new HashMap<>(16,0.75);
Map<String,String> map = new ConcurrentHashMap<>();
// 加载因子,初始化容量
for (int i = 0; i < 30; i++) {
    new Thread(() -> {
        map.put(UUID.randomUUID().toString().substring(3,5),UUID.randomUUID().toString().substring(3,5));
        System.out.println(map);
    },String.valueOf(i)).start();
}
}

六、Collable

  1. FutureTask是一个Runnable的一个实现类,
  2. 可以通过调用类的方法达到实现调用接口成功
public class CallableTest {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    new Thread().start();
    MyThread myThread = new MyThread();
    FutureTask<String> futureTask = new FutureTask<>(myThread);   //适配类
    new Thread(futureTask,"A").start();
    String s = futureTask.get();    //将callable的结果返回
    System.out.println(s);
  }
}

class MyThread implements Callable<String> {
  @Override
  public String call() throws Exception {
    System.out.println("call()");
    return "你好啊世界";
  }
}

七、常见的辅助类

7.1 CountDownLatch

实际上是一个计数器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGFyY7Sn-1639826957358)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217174610440.png)]

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();   //数量减一
        System.out.println("程序计数器: " + countDownLatch.getCount());
      },String.valueOf(i)).start();
    }

    countDownLatch.await();	//等待计数器归零,再向下执行
    System.out.println("我要关门啦!");
  }
}

原理:

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

每次调用的时候,程序会去进行计数,直到0再往下执行

7.2 CyclicBarrier

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSj8rm9Q-1639826957359)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217180225629.png)]

public class CyclicBarrierDemo {
  public static void main(String[] args) {
    // 等待7个线程进来则执行该方法
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()-> {
      System.out.println("我要召唤神龙了哦!");
    });

    for (int i = 0; i < 7; i++) {
      //在lombda表达式中不允许使用final以外的变量
      final int temp = i;
      new Thread(() -> {
        try {
          //开始等待
          cyclicBarrier.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } catch (BrokenBarrierException e) {
          e.printStackTrace();
        }
      }).start();
    }
  }
}

7.3 Semaphore

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YwcRljig-1639826957359)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217181100911.png)]

public class SemaphoreDemo {
  public static void main(String[] args) {
    // 线程数量: 停车位!  限制流量
    Semaphore semaphore = new Semaphore(3);

    for (int i = 0; i < 6; i++) {
      new Thread(()-> {
        try {
          semaphore.acquire();    //得到信号
          System.out.println(Thread.currentThread().getName() + "抢到了车位");
          TimeUnit.SECONDS.sleep(2);
          System.out.println(Thread.currentThread().getName() + "离开了车位");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }finally {
          semaphore.release(); //释放信号
        }
      },String.valueOf(i)).start();
    }
  }
}

原理

semaphore.acquire();    //得到信号,如果满了,则等待,等待到释放为止
semaphore.release(); 	//释放信号,会将当前的信号量释放+1,然后唤醒等待的线程

八、读写锁

/**
 * 独占锁
 * 共享锁
 *
 * ReadWriteLock
 * 读-读  可以共存
 * 读-写  不能共存
 * 写写   不能共存
 */
public class ReadWriteLockDemo {
  public static void main(String[] args) {
    MyCache2 myCache = new MyCache2();

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

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

class MyCache2 {
  private Map<String,String> hashMap =  new HashMap<String,String>();
  private ReadWriteLock readWriteLock =  new ReentrantReadWriteLock();

  public void put(String key,String value) {
    readWriteLock.writeLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + "写入=>" + key);
      hashMap.put(key,value);
      System.out.println(Thread.currentThread().getName() + "写入OK");
    }finally {
      readWriteLock.writeLock().unlock();
    }

  }

  public void get(String key) {
    readWriteLock.readLock().lock();
   try {
     System.out.println(Thread.currentThread().getName() + "来读了!" +key);
     hashMap.get(key);
     System.out.println(Thread.currentThread().getName() + "读完了!" );
   }finally {
     readWriteLock.readLock().unlock();
   }
  }
}

class MyCache {
  private Map<String,String> hashMap =  new HashMap<String,String>();

  public void put(String key,String value) {
    hashMap.put(key,value);
    System.out.println(Thread.currentThread().getName() + "=>" + hashMap);
  }

  public void get(String key) {
    System.out.println(Thread.currentThread().getName() + "来读了!" + hashMap.get(key));
  }
}

九、阻塞队列

用处

  1. 多线程并发处理
  2. 线程池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQKFCxGK-1639826957360)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217190303757.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QrRwHlTi-1639826957360)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211217191242611.png)]

9.1 学会使用队列

添加、移除

9.2 四组API

方式抛出异常有返回值阻塞等待超时等待
添加add()offer()put()offer(,)
移除remove()poll()take()poll(,
判断队列首element()peek()
public static void test() {
    BlockingQueue 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"));
    // 队列满了  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());

    // 队列没有元素 NoSuchElementException

}

public static void test2() {
    BlockingQueue 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"));

    System.out.println("============");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
}

public static void test3() throws InterruptedException {
    BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    // 一直等待
    // blockingQueue.put("d");
    System.out.println(blockingQueue);
    blockingQueue.take();
    blockingQueue.take();
    blockingQueue.take();
    blockingQueue.take();
}

public static void test4() throws InterruptedException {
    BlockingQueue 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",2, TimeUnit.SECONDS));

    System.out.println("============");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}

9.3 同步队列:SynchronousQueue

概念

只允许一个进入队列出来之后再进行后面的操作

public class SynchronousQueueDemo {
  public static void main(String[] args) {
    BlockingQueue<Object> queue = new SynchronousQueue<>();

    new Thread(()-> {
      try {
        System.out.println("我来咯1");
        queue.put("a");
        System.out.println("我来咯2");
        queue.put("b");
        System.out.println("我来咯3");
        queue.put("c");
        System.out.println("我来咯4");
        queue.put("d");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    },"T1").start();

    new Thread(()-> {
      try {
        TimeUnit.SECONDS.sleep(2);
        queue.take();
        System.out.println("我走咯1");
        TimeUnit.SECONDS.sleep(2);
        queue.take();
        System.out.println("我走咯2");
        TimeUnit.SECONDS.sleep(2);
        queue.take();
        System.out.println("我走咯3");
        TimeUnit.SECONDS.sleep(2);
        queue.take();
        System.out.println("我走咯4");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    },"T1").start();
  }
}

十、线程池(重点)

10.1 池化技术

线程池: 三大方法、七大参数、四大拒绝 策略

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

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

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

10.2 线程池的好处

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

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

10.3 三大方法

  • ExecutorService threadExecutor = Executors.newSingleThreadExecutor(); //单个线程
  • ExecutorService threadExecutor = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
  • ExecutorService threadExecutor = Executors.newCachedThreadPool(); //创建一个可伸缩的,遇到强则强,遇弱则弱

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oc2QtZC3-1639826957361)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218093335647.png)]

public class Demo01 {
  public static void main(String[] args) {
    //ExecutorService threadExecutor = Executors.newSingleThreadExecutor();//单个线程
//   ExecutorService threadExecutor = Executors.newFixedThreadPool(5);    //创建一个固定大小的线程池
    ExecutorService threadExecutor = Executors.newCachedThreadPool();            //创建一个可伸缩的,遇到强则强,遇弱则弱
    try {
      for (int i = 0; i < 100; i++) {
        // 使用了线程池之后,根据线程池去创建线程
        threadExecutor.execute(()-> {
          System.out.println(Thread.currentThread().getName() + " ok");
        });
      }
    }finally {
        //线程池用完需要关闭
        threadExecutor.shutdown();
    }
  }
}

10.4 七大参数

单线程池

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亿  导致OOM
                                  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.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;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rCFdpW3x-1639826957361)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218095423764.png)]

手动创建一个线程池

 // 自定义线程池,工作必用的ThreadPoolExecutor
ThreadPoolExecutor threadExecutor = new ThreadPoolExecutor(
    2,
    5,
    3,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.DiscardOldestPolicy()
);

10.5 四大拒绝策略

// new ThreadPoolExecutor.AbortPolicy()        银行满了,还有人进来,不处理这个,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy()   哪来的去哪里,一般去main里边执行
// new ThreadPoolExecutor.DiscardPolicy()      队列满了,丢掉任务。不会抛出异常
// new ThreadPoolExecutor.DiscardOldestPolicy()队列满了,尝试和最早的竞争,也不会抛出异常

10.6 小结和拓展

IO密集型

// IO密集型  判断程序中十分耗IO的线程
//    程序  15个大型任务 io十分占用资源

CPU密集型

// CPU 密集型 几核 就是几 12条线程同时进行
Runtime.getRuntime().availableProcessors()	//获取最大线程数

十一、四大函数式接口

概念

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

11.1 函数式接口

概念

只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// 超级多FunctionalInterface
// 简单化编程模型,在新版本的底层大量运用
// foreach就是一个函数式接口

java.util.function

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e1YDMmBq-1639826957362)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218103520865.png)]

11.2 Function 函数型接口

概念

有入参,有返回值

public class Demo01 {
  public static void main(String[] args) {
//    Function<String, String> function = new Function<String, String>() {
//      @Override
//      public String apply(String s) {
//        return s;
//      }
//    };
    Function function = (str)-> str;
    System.out.println(function.apply("你好啊,世界"));
  }
}

11.3 Predicate 断定型接口

概念

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

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

11.4 Consumer 消费型接口

概念

只有输入,没有返回值

public class Demo03 {
  public static void main(String[] args) {
//    Consumer<String> consumer = new Consumer<String>() {
//      @Override
//      public void accept(String s) {
//        System.out.println(s);
//      }
//    };
    Consumer<String> consumer = s -> System.out.println(s);
    consumer.accept(111+"");
  }
}

11.5 Supplier 供给型接口

概念

只有返回值,没有输入

public class Demo04 {
  public static void main(String[] args) {
//    Supplier<Integer> supplier = new Supplier<Integer>() {
//      @Override
//      public Integer get() {
//        return 1024;
//      }
//    };
    Supplier<Integer> supplier = () -> 1024;

    System.out.println(supplier.get());
  }
}

十二、Stream流式计算

什么是Stream流式计算

大数据: 存储 + 计算

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

计算的东西都应该交给流来计算!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDrjcI8T-1639826957362)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218113003715.png)]

Stream流计算全部采用的是函数式接口编程,其中的:

  • filter 是一个断定性接口Predicate
  • map是一个函数式接口
public class Test {
  public static void main(String[] args) {
    User a = new User(1, "a", 21);
    User b = new User(2, "b", 22);
    User c = new User(3, "c", 23);
    User d = new User(4, "d", 24);
    User e = new User(6, "e", 25);
    List<User> list = Arrays.asList(a, b, c, d, e);
    list.stream()
            .filter((item)-> item.getId() % 2 == 0)
            .filter(item -> item.getAge() > 23)
            .map((item)-> item.getName().toUpperCase())
            .sorted((String::compareTo))
            .limit(1)
            .forEach(System.out::println);
    System.out.println(list);
  }
}

十三、ForkJoin

ForkJoin在jdk1.7之后,并行执行任务,提高效率,大数据量

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6l1WKfM8-1639826957363)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218115108999.png)]

ForkJoin特点: 工作窃取

所有的任务维护都都是双端队列,故可以操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MsxRTn1Y-1639826957363)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218115245435.png)]

ForkJoin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3cgWB22k-1639826957363)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218120509027.png)]

**
 * 求和计算的任务
 * 3000
 * 6000 ForkJoin
 * 9000 Stream并行流计算
 * 如何使用ForkJoin
 * 1、forkjoinpool 通过他来执行
 * 2、计算任务forkjoinpool.
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
  private Long start;
  private Long end;

  public ForkJoinDemo(long start,long end) {
    this.start = start;
    this.end = end;
  }
  private Long temp = 10000L;

  @Override
  protected Long compute() {
    Long sum = 0L;
    if ((end - start) <= temp){
      for (Long i = start; i <= end ; i++) {
        sum += i;
      }
      return sum;
    }else {
      // forkjoin
      long middle = (end + start) / 2;
      ForkJoinDemo forkJoinDemo = new ForkJoinDemo(start,middle );
      forkJoinDemo.fork();       //拆分任务,把任务压入线程队列
      ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo((middle+ 1), end);
      forkJoinDemo2.fork();       //拆分任务,把任务压入线程队列
      return forkJoinDemo.join() + forkJoinDemo2.join();
    }
  }
}

13.1 三种程序员的效率

public class Test {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    test1();    //339
    //test2();
    test3();
  }

  // 普通程序员
  public static void test1() {
    long sum = 0;
    long start = System.currentTimeMillis();
    for (long i = 0L; i <= 10_0000_0000; i++) {
      sum += i;
    }
    long end = System.currentTimeMillis();
    System.out.println("普通程序员的时间:" + (end - start) + "   sum=>" + sum);
  }

  // 6000程序员
  public static void test2() throws ExecutionException, InterruptedException {
    long start = System.currentTimeMillis();
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    ForkJoinTask<Long> task = new ForkJoinDemo(0,10_0000_0000);
    ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交有结果
    //forkJoinPool.execute(task);   //执行无结果
    Long aLong = submit.get();
    long end = System.currentTimeMillis();
    System.out.println("6000程序员的时间:" + (end - start) + "  sum=> " + aLong);
  }

  // 架构师
  public static void test3() {
    long start = System.currentTimeMillis();
    long reduce = LongStream.rangeClosed(0, 10_0000_0000).parallel().reduce(0, Long::sum);
    long end = System.currentTimeMillis();
    System.out.println("架构师的时间:" + (end - start)+ "  sum=> " + reduce);
  }
}

十四、异步回调

类似于JavaScript的AJAX,成功有返回值,失败有返回值

 CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
     System.out.println("supplyAsync => Integer");
     int i = 10/0;
     return 1024;
 });

completableFuture.whenComplete((t,v)-> {
    System.out.println("t=> " + t);
    System.out.println("v=> " + v);
}).exceptionally(throwable -> {
    System.out.println(throwable.getMessage());
    return 233;
});

十五、JMM

谈谈Volatile的理解

Volatile是Java虚拟机提供的 轻量级的同步机制

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

什么是JMM

JVM:java虚拟机

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

关于JMM一些同步的约定:

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

线程工作内存主内存

8种操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uceDIqPF-1639826957364)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218143214576.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtgQSZ2e-1639826957364)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218143432894.png)]

15.1 内存交互操作

内存交互操作有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

保证可见性

public class JMMdemo {
  // 不加volatile 程序就会死循环
  private volatile static int num = 0;
  public static void main(String[] args) {

    new Thread(()-> {   //线程对主内存的变化是不知道的
      while (num == 0){

      }
    }).start();

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

    num = 1;
    System.out.println("执行完了哦");
  }
}

不保证原子性

原子性: 不可分割

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5jihdxY3-1639826957364)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218150024180.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RCZJrkMs-1639826957365)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218150106460.png)]

使用原子类去实现就行了

原子类

public class VDemo02 {
  private volatile static AtomicInteger num = new AtomicInteger();

  public static void add() {
//    num++;
    num.getAndIncrement();        // AtomicInteger + 1方法,CAS
  }

  public static void main(String[] args) {
    for (int i = 0; i < 20; i++) {
      new Thread(()-> {
        for (int i1 = 0; i1 < 1000; i1++) {
          add();
        }
      }).start();
    }
    while (Thread.activeCount() != 2) {
      Thread.yield();
    }
    System.out.println(num);
  }
}

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

指令重排

什么是指令重排?

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

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

int x = 1;		//1
int y = 2;		//2
x = x + 5; 		//3
y = x * x;		//4
我们所期望的: 1234  但是执行的时候可能会编程  2134  1324

可能造成的影响

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjy8In9X-1639826957365)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218151644496.png)]

指令重排

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QwgpfeZ9-1639826957366)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218151936442.png)]

十七、单例模式

17.1 饿汉式

饿汉式单例设计模式每次new对象都会直接创建占用内存,造成资源浪费

// 饿汉式单例
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];

    // 构造器私有
    private Hungry() {

    }

    private final static Hungry HUNGRY= new Hungry();

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

17.2 DCL懒汉式,深究!

采用正常的类

// 懒汉式单例设计模式
public class LazyMan {
  private LazyMan() {
    System.out.println(Thread.currentThread().getName() + "ok");
  }

  // 加上volatile 确保不会指令重排
  private volatile static LazyMan lazyMan;

  // 双重锁检测模式,懒汉单例,DCL懒汉式
  public static LazyMan getInstance() {
    if (lazyMan == null) {
      synchronized (LazyMan.class){
        if (lazyMan == null) {
          lazyMan = new LazyMan();       //不是原子性操作
          /**
           * 1. 分配内存空间
           * 2. 执行构造方法,初始化对象
           * 3. 把这个对象指向这个空间
           *
           * 123
           * 132  A
           *      B  //次数lazyMan还没有完成构造
           */
        }
      }
    }
    return lazyMan;
  }

  // 单线程下确实没问题

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

如果使用反射可以进行破解,道高一尺,魔高一丈

枚举反编译之后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NO1i3Eql-1639826957366)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218164849509.png)]

枚举采用jad反编译就不会骗人了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PORPaD50-1639826957367)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218165008193.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1sN1kWqg-1639826957367)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218165157873.png)]

我们用反射也破解不了啦!

十八、深入理解CAS

什么是CAS

大厂你必须要研究底层,有所突破

public class CASDemo {
  //CAS  cpomareAndSet : 比较并交换
  public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(2020);
    // 期望、更新
    // public final boolean compareAndSet(int expect, int update)
    // 如果我的期望值达到了。那么就进行更新,如果没达到,则不更新    CAP  是CPU的并发原语
    atomicInteger.compareAndSet(2020,2021);
    System.out.println(atomicInteger.get());

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

Unsafe类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6qGOmR8-1639826957367)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218170550772.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtIMUfTO-1639826957368)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218171016480.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bLcANIN-1639826957368)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218171211023.png)]

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

缺点:

1、循环会耗时

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

3、ABA问题

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nx5wIkWn-1639826957368)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218172601073.png)]

public class CASDemo {
  //CAS  cpomareAndSet : 比较并交换
  public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(2020);
    // 期望、更新
    // public final boolean compareAndSet(int expect, int update)
    // 如果我的期望值达到了。那么就进行更新,如果没达到,则不更新    CAP  是CPU的并发原语
    // ============= 捣乱的线程 ================
    atomicInteger.compareAndSet(2020,2021);
    System.out.println(atomicInteger.get());

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

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

十九、原子引用

带版本的原子操作

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuJCFvxq-1639826957369)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218175303337.png)]

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

public class ABADemo {
  public static void main(String[] args) {
    // AtomicStampedReference注意,如果泛型是一个包装类,注意对象的引用
    AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);

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

      System.out.println(atomicStampedReference.compareAndSet(2, 1
              , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
      System.out.println("a3=>" + atomicStampedReference.getStamp());
      },"a").start();

    new Thread(()-> {
      int samp = atomicStampedReference.getStamp();
      System.out.println("b1=>" + samp);
      try {
        TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("期望的版本号" + samp);
      System.out.println(atomicStampedReference.compareAndSet(1, 6
              , samp, atomicStampedReference.getStamp() + 1));
      System.out.println("b2=>" + atomicStampedReference.getStamp());
    },"b").start();
  }
}

二十、各种锁的 理解

20.1 公平锁,非公平锁

公平锁

非常工程,不能插队,必须先来后到

非公平锁

不公平,可以插队

public ReentrantLock() {
    sync = new NonfairSync();
}

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

20.2 可重入锁

可重入锁(锁递归)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agDHXcfE-1639826957369)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218181455564.png)]

传统的锁

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

    new Thread(()-> {
      phone.sms();
    },"T2").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");
  }
}

采用JUC的方式去

public class Demo02 {
  public static void main(String[] args) {
    Phone2 phone = new Phone2();
    new Thread(()-> phone.sms(),"T1").start();

    new Thread(()-> phone.sms(),"T2").start();
  }
}

class Phone2{
  Lock lock = new ReentrantLock();
  public synchronized void sms() {
    lock.lock();
    try {
      System.out.println(Thread.currentThread().getName() + "sms");
      call();
    }finally {
      lock.unlock();
    }
  }

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

20.3 自旋锁

spinlock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkY3470E-1639826957369)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218183245311.png)]

public class Demo03 {
  // 创建一个thread null
  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);
  }
}

20.4 死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oe2Dzfxx-1639826957370)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218184022710.png)]

public class Demo04 {
  public static void main(String[] args) {
    String lockA = "locakA";
    String lockB = "locakB";
    new Thread(new MyThread2(lockA,lockB),"T1").start();
    new Thread(new MyThread2(lockB,lockA),"T2").start();
  }
}

class MyThread2 implements Runnable {
  private String lockA;
  private String lockB;

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

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

解决死锁

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3ufddI2-1639826957370)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218192721625.png)]

  2. 使用 jstack 进程号

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3EmfXXM-1639826957371)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218192750881.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R8PlMYPn-1639826957371)(F:\人生苦短,我用JUC\笔记区域\JUC并发编程.assets\image-20211218192808573.png)]
    zed void sms() {
    lock.lock();
    try {
    System.out.println(Thread.currentThread().getName() + “sms”);
    call();
    }finally {
    lock.unlock();
    }
    }

public synchronized void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + “call”);
}finally {
lock.unlock();
}
}
}




## 20.3 自旋锁

spinlock

[外链图片转存中...(img-CkY3470E-1639826957369)]

```java
public class Demo03 {
  // 创建一个thread null
  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);
  }
}

20.4 死锁

[外链图片转存中…(img-Oe2Dzfxx-1639826957370)]

public class Demo04 {
  public static void main(String[] args) {
    String lockA = "locakA";
    String lockB = "locakB";
    new Thread(new MyThread2(lockA,lockB),"T1").start();
    new Thread(new MyThread2(lockB,lockA),"T2").start();
  }
}

class MyThread2 implements Runnable {
  private String lockA;
  private String lockB;

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

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

解决死锁

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

    [外链图片转存中…(img-o3ufddI2-1639826957370)]

  2. 使用 jstack 进程号

    [外链图片转存中…(img-p3EmfXXM-1639826957371)]

    [外链图片转存中…(img-R8PlMYPn-1639826957371)]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值