JUC编程

源码+官方文档

JUC编程 - 图1

java.util 工具包

current

业务

2、线程和进程

线程、进程

  • 进程:一个程序,程序的集合一个
  • 进程:包含多个线程,至少包含一个

并发和并行

  • 并发:同一时间段,多个任务都在执行。(多线程操作同一个资源)
    cpu 单核 模拟多条线程,快速交替

  • 并行:单位时间内,多个任务同时执行
    多核,多个线程可以同时执行

  1. java默认有几个线程?
    main线程、GC
    线程:某个单个小功能就是由线程控制的。

  2. java真的可以开启线程吗?
    只能通过native方法(底层的C++),不能自己开启

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

回顾多线程

线程状态

  • new 新生
  • Runnable 运行
  • Blocked阻塞
  • wait 等待
  • time_waitting 超时等待
  • terminated 终止

创建线程的方式

  1. 继承:Thread
  2. 接口: Runnable
  3. 接口: Callable
  4. 通过线程池创建对象

wait 和sleep的区别

1、来自不同的类

  • wait object类

  • sleep Thread类

2、关于锁的释放

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

3、使用的范围是不同的

wait 必须在同步代码块中

sleep可以在任何地方睡

4、是否需要捕获异常

wait不需要捕获异常

sleep必须要捕获异常

3、Lock锁(重点)

3.1、Synchronized(关键字,JVM)

传统synchronized

  1. package com.zmy.demo;
  2. //基本的卖票
  3. /*
  4. 多线程
  5. 线程就是一个单独的资源类,没有任何的附属的操作
  6. 1、属性
  7. 2、方法
  8. */
  9. public class SaleTicket {
  10. public static void main(String[] args) {
  11. //并发:多线程操作同一个资源类,把资源丢入线程
  12. final Ticket ticket = new Ticket();
  13. //@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }
  14. new Thread(()->{
  15. for (int i = 1; i < 500; i++){
  16. ticket.sale();
  17. }
  18. },"a").start();
  19. new Thread(()->{
  20. for (int i = 1; i < 500; i++){
  21. ticket.sale();
  22. }
  23. },"b").start();
  24. new Thread(()->{
  25. for (int i = 1; i < 500; i++){
  26. ticket.sale();
  27. }
  28. },"c").start();
  29. }
  30. }
  31. class MyThread implements Runnable{
  32. public void run() {
  33. }
  34. }
  35. //资源类OOP
  36. class Ticket{
  37. private int number = 500;
  38. //卖票的方式
  39. //不加synchronized,结果是乱的
  40. //synchronized本质是:队列,锁
  41. //
  42. public synchronized void sale(){
  43. if (number>0){
  44. System.out.println(Thread.currentThread().getName()+
  45. "卖出了第"+(number--)+"票,剩余"+number);
  46. }
  47. }
  48. }

3.2、Lock(类)

Lock

Lock三部曲

  • new ReentrantLock()
  • lock.lock(); 加锁
  • finally => lock.unlock(); 解锁

JUC编程 - 图2

JUC编程 - 图3

  • 锁的分类:
  1. package com.zmy.demo;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. //基本的卖票
  5. /*
  6. 多线程
  7. 线程就是一个单独的资源类,没有任何的附属的操作
  8. 1、属性
  9. 2、方法
  10. */
  11. public class SaleTicket01 {
  12. public static void main(String[] args) {
  13. //并发:多线程操作同一个资源类,把资源丢入线程
  14. Ticket1 ticket = new Ticket1();
  15. //@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }
  16. new Thread(()->{
  17. for (int i = 1; i < 500; i++){
  18. ticket.sale();
  19. }
  20. },"a").start();
  21. new Thread(()->{
  22. for (int i = 1; i < 500; i++){
  23. ticket.sale();
  24. }
  25. },"b").start();
  26. new Thread(()->{
  27. for (int i = 1; i < 500; i++){
  28. ticket.sale();
  29. }
  30. },"c").start();
  31. }
  32. }
  33. class Ticket1{
  34. //卖票的方式
  35. //Lock三部曲
  36. //1、new ReentrantLock();
  37. //2、lock.lock(); 加锁
  38. //3、finally => lock.unlock(); 解锁
  39. private int number = 500;
  40. Lock lock= new ReentrantLock();
  41. public void sale(){
  42. lock.lock();
  43. try {
  44. if (number>0){
  45. System.out.println(Thread.currentThread().getName()+
  46. "卖出了第"+(number--)+"票,剩余"+number);
  47. };
  48. }catch (Exception e){
  49. e.printStackTrace();
  50. }finally {
  51. lock.unlock();
  52. }
  53. }
  54. }

3.3、Synchronized和lock的区别

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

2、Synchronized 无法判断获取锁的状态,Lock可以判断

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

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

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

4. 生产者和消费者的关系

4.1、Synchronized 版本

  1. package com.zmy.ProAndConsumer;
  2. /**
  3. * 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
  4. * 线程交替执行 A B 操作同一变量资源 num = 0
  5. * A = num + 1
  6. * B = num - 1
  7. */
  8. public class ConsumeAndProduct {
  9. public static void main(String[] args) {
  10. Data data = new Data();
  11. new Thread(() -> {
  12. for (int i = 0; i < 10; i++) {
  13. try {
  14. data.increment();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }, "A").start();
  20. new Thread(() -> {
  21. for (int i = 0; i < 10; i++) {
  22. try {
  23. data.decrement();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }, "B").start();
  29. }
  30. }
  31. //判断等待,业务,通知
  32. class Data { //数字 资源类
  33. private int num = 0;
  34. // +1
  35. public synchronized void increment() throws InterruptedException {
  36. // 判断等待
  37. if (num != 0) {
  38. this.wait();
  39. }
  40. num++;
  41. System.out.println(Thread.currentThread().getName() + "=>" + num);
  42. // 通知其他线程 +1 执行完毕
  43. this.notifyAll();
  44. }
  45. // -1
  46. public synchronized void decrement() throws InterruptedException {
  47. // 判断等待
  48. if (num == 0) {
  49. this.wait();
  50. }
  51. num--;
  52. System.out.println(Thread.currentThread().getName() + "=>" + num);
  53. // 通知其他线程 -1 执行完毕
  54. this.notifyAll();
  55. }
  56. }

4.2、存在问题

如果存在四个线程,会出现虚假唤醒

  1. A=>1
  2. B=>0
  3. C=>1
  4. A=>2
  5. C=>3
  6. B=>2
  7. B=>1

JUC编程 - 图4

解决方式 ,

if 改为while即可,防止虚假唤醒

  1. package com.zmy.ProAndConsumer;
  2. /**
  3. * 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
  4. * 线程交替执行 A B 操作同一变量资源 num = 0
  5. * A = num + 1
  6. * B = num - 1
  7. */
  8. public class ConsumeAndProduct {
  9. public static void main(String[] args) {
  10. Data data = new Data();
  11. new Thread(() -> {
  12. for (int i = 0; i < 10; i++) {
  13. try {
  14. data.increment();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }, "A").start();
  20. new Thread(() -> {
  21. for (int i = 0; i < 10; i++) {
  22. try {
  23. data.increment();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }, "C").start();
  29. new Thread(() -> {
  30. for (int i = 0; i < 10; i++) {
  31. try {
  32. data.decrement();
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }, "B").start();
  38. new Thread(() -> {
  39. for (int i = 0; i < 10; i++) {
  40. try {
  41. data.decrement();
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. }, "D").start();
  47. }
  48. }
  49. //判断等待,业务,通知
  50. class Data { //数字 资源类
  51. private int num = 0;
  52. // +1
  53. public synchronized void increment() throws InterruptedException {
  54. // 判断等待
  55. while (num != 0) {
  56. this.wait();
  57. }
  58. num++;
  59. System.out.println(Thread.currentThread().getName() + "=>" + num);
  60. // 通知其他线程 +1 执行完毕
  61. this.notifyAll();
  62. }
  63. // -1
  64. public synchronized void decrement() throws InterruptedException {
  65. // 判断等待
  66. while (num == 0) {
  67. this.wait();
  68. }
  69. num--;
  70. System.out.println(Thread.currentThread().getName() + "=>" + num);
  71. // 通知其他线程 -1 执行完毕
  72. this.notifyAll();
  73. }
  74. }
  1. A=>1
  2. B=>0
  3. C=>1
  4. B=>0
  5. A=>1
  6. B=>0
  7. C=>1

4.3、Lock版

JUC编程 - 图5

  1. package com.zmy.ProAndConsumer;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.Lock;
  4. import java.util.concurrent.locks.ReentrantLock;
  5. /**
  6. * 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
  7. * 线程交替执行 A B 操作同一变量资源 num = 0
  8. * A = num + 1
  9. * B = num - 1
  10. */
  11. public class LockPAC {
  12. public static void main(String[] args) {
  13. Data1 data = new Data1();
  14. new Thread(() -> {
  15. for (int i = 0; i < 10; i++) {
  16. try {
  17. data.increment();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }, "A").start();
  23. new Thread(() -> {
  24. for (int i = 0; i < 10; i++) {
  25. try {
  26. data.decrement();
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }, "B").start();
  32. new Thread(() -> {
  33. for (int i = 0; i < 10; i++) {
  34. try {
  35. data.increment();
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. }, "C").start();
  41. new Thread(() -> {
  42. for (int i = 0; i < 10; i++) {
  43. try {
  44. data.decrement();
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }, "D").start();
  50. }
  51. }
  52. //判断等待,业务,通知
  53. class Data1 { //数字 资源类
  54. private int num = 0;
  55. Lock lock = new ReentrantLock();
  56. Condition condition = lock.newCondition(); //替代同步监视器
  57. //condition.await(); //等待
  58. //condition.signalAll(); //唤醒全部
  59. // +1
  60. public void increment() throws InterruptedException {
  61. try {
  62. lock.lock();
  63. //业务代码
  64. // 判断等待
  65. while (num != 0) {
  66. condition.await();
  67. }
  68. num++;
  69. System.out.println(Thread.currentThread().getName() + "=>" + num);
  70. // 通知其他线程 +1 执行完毕
  71. condition.signalAll();
  72. } catch (Exception e) {
  73. e.printStackTrace();
  74. } finally {
  75. lock.unlock();
  76. }
  77. }
  78. // -1
  79. public void decrement() throws InterruptedException {
  80. try {
  81. lock.lock();
  82. //业务代码
  83. // 判断等待
  84. while (num == 0) {
  85. condition.await();
  86. }
  87. num--;
  88. System.out.println(Thread.currentThread().getName() + "=>" + num);
  89. // 通知其他线程 -1 执行完毕
  90. condition.signalAll();
  91. } catch (Exception e) {
  92. e.printStackTrace();
  93. } finally {
  94. lock.unlock();
  95. }
  96. }
  97. }

4.4、Condition的优势

精准的通知和唤醒线程!

指定通知的下一个进行顺序怎么办? 可以用Condition来指定通知进程

  1. package com.zmy.ProAndConsumer;
  2. /**
  3. * A执行完调用B
  4. * B执行完调用C
  5. * C执行完调用A
  6. *
  7. */
  8. import java.util.concurrent.locks.Lock;
  9. import java.util.concurrent.locks.ReentrantLock;
  10. public class Condition {
  11. public static void main(String[] args) {
  12. Data2 data2 = new Data2();
  13. new Thread(()->{
  14. for (int i = 0; i < 10;i++){
  15. try {
  16. data2.printA();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. },"A").start();
  22. new Thread(()->{
  23. for (int i = 0; i < 10;i++){
  24. try {
  25. data2.printB();
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. },"B").start();
  31. new Thread(()->{
  32. for (int i = 0; i < 10;i++){
  33. try {
  34. data2.printC();
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. },"C").start();
  40. }
  41. }
  42. class Data2{ //资源类 Lock
  43. private Lock lock = new ReentrantLock();
  44. private java.util.concurrent.locks.Condition condition1 = lock.newCondition();
  45. private java.util.concurrent.locks.Condition condition2 = lock.newCondition();
  46. private java.util.concurrent.locks.Condition condition3 = lock.newCondition();
  47. private int number = 1; // 1A 2B 3C
  48. public void printA(){
  49. try {
  50. lock.lock();
  51. //业务代码
  52. while (number != 1){
  53. //等待
  54. condition1.await();
  55. }
  56. System.out.println(Thread.currentThread().getName()+"====AAAAAA");
  57. //唤醒指定的人 B
  58. number = 2;
  59. condition2.signal();
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. } finally {
  63. lock.unlock();
  64. }
  65. };
  66. public void printB(){
  67. try {
  68. lock.lock();
  69. //业务代码
  70. while (number != 2){
  71. //等待
  72. condition2.await();
  73. }
  74. System.out.println(Thread.currentThread().getName()+"====BBBB");
  75. //唤醒指定的人 B
  76. number = 3;
  77. condition3.signal();
  78. } catch (Exception e) {
  79. e.printStackTrace();
  80. } finally {
  81. lock.unlock();
  82. }
  83. };
  84. public void printC(){
  85. try {
  86. lock.lock();
  87. //业务代码
  88. while (number != 3){
  89. //等待
  90. condition3.await();
  91. }
  92. System.out.println(Thread.currentThread().getName()+"====CCCCC");
  93. //唤醒指定的人 B
  94. number = 1;
  95. condition1.signal();
  96. } catch (Exception e) {
  97. e.printStackTrace();
  98. } finally {
  99. lock.unlock();
  100. }
  101. };
  102. }

5、八锁现象

8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)

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

  • 2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话

  • synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到(代码的执行顺序)谁先执行!另外一个则等待!

  • 3、增加了一个普通方法后! 执行的顺序又是什么 普通方法

  • 4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)

  • 5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系

  • 6、两个对象,两个静态同步方法 两个对象用的是同一个Class

  • 7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间

  • 8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序

  1. package com.zmy.lock;
  2. /**
  3. * 8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)
  4. * 1、标准情况下,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
  5. * 2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
  6. * 3、增加了一个普通方法后! 执行的顺序又是什么 普通方法
  7. * 4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)
  8. * 5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系
  9. * 6、两个对象,两个静态同步方法 两个对象用的是同一个Class
  10. * 7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间
  11. * 8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序
  12. */
  13. import java.util.concurrent.TimeUnit;
  14. public class Test1 {
  15. public static void main(String[] args) {
  16. Phone phone = new Phone();
  17. Phone phone2 = new Phone();
  18. //锁的存在
  19. new Thread(()->{
  20. phone2.sendSms();
  21. }).start();
  22. try {
  23. TimeUnit.SECONDS.sleep(1);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. new Thread(()->{
  28. phone2.call();
  29. }).start();
  30. new Thread(()->{
  31. phone.call();
  32. }).start();
  33. new Thread(()->{
  34. phone.hello();
  35. }).start();
  36. }
  37. }
  38. class Phone{
  39. //synchronized 锁的对象是方法的调用者 也就是phone
  40. //两个方法用的是同一把锁,谁先拿到谁先执行
  41. //static 静态方法
  42. //类一加载就有了! Class模板
  43. public static synchronized void sendSms(){
  44. System.out.println("发短信");
  45. };
  46. public synchronized void call(){
  47. try {
  48. TimeUnit.SECONDS.sleep(4);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. System.out.println("打电话");
  53. }
  54. //这里没有锁,不是同步方法,不受锁的影响
  55. public void hello(){
  56. System.out.println("hello");
  57. }
  58. }

6、集合类不安全

6.1、List不安全

  1. //java.util.ConcurrentModificationException 并发修改异常!
  2. public class ListTest {
  3. public static void main(String[] args) {
  4. List<Object> arrayList = new ArrayList<>();
  5. for(int i=1;i<=10;i++){
  6. new Thread(()->{
  7. arrayList.add(UUID.randomUUID().toString().substring(0,5));
  8. System.out.println(arrayList);
  9. },String.valueOf(i)).start();
  10. }
  11. }
  12. }

会导致 java.util.ConcurrentModificationException 并发修改异常!

ArrayList 在并发情况下是不安全的

解决方案:

  1. public class ListTest {
  2. public static void main(String[] args) {
  3. /**
  4. * 解决方案
  5. * 1. List<String> list = new Vector<>();
  6. * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
  7. * 3. List<String> list = new CopyOnWriteArrayList<>();
  8. */
  9. List<String> list = new CopyOnWriteArrayList<>();
  10. for (int i = 1; i <=10; i++) {
  11. new Thread(() -> {
  12. list.add(UUID.randomUUID().toString().substring(0,5));
  13. System.out.println(list);
  14. },String.valueOf(i)).start();
  15. }
  16. }
  17. }

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;

CopyOnWriteArrayListVector厉害在哪里

  • Vector底层是使用synchronized关键字来实现的:效率特别低下。
  • CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

6.2、set不安全

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案还是两种:

  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
  1. public class SetTest {
  2. public static void main(String[] args) {
  3. /**
  4. * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
  5. * 2. Set<String> set = new CopyOnWriteArraySet<>();
  6. */
  7. // Set<String> set = new HashSet<>();
  8. Set<String> set = new CopyOnWriteArraySet<>();
  9. for (int i = 1; i <= 30; i++) {
  10. new Thread(() -> {
  11. set.add(UUID.randomUUID().toString().substring(0,5));
  12. System.out.println(set);
  13. },String.valueOf(i)).start();
  14. }
  15. }
  16. }

HashSet底层是什么?

hashSet底层就是一个HashMap

JUC编程 - 图6

6.3、Map不安全

  1. //map 是这样用的吗? 不是,工作中不使用这个
  2. //默认等价什么? new HashMap<>(16,0.75);
  3. Map<String, String> map = new HashMap<>();
  4. //加载因子、初始化容量
  • currentHashMap

7、Callable

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

  1. public class CallableTest {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. for (int i = 1; i < 10; i++) {
  4. MyThread1 myThread1 = new MyThread1();
  5. FutureTask<Integer> futureTask = new FutureTask<>(myThread1);
  6. // 放入Thread中使用,结果会被缓存
  7. new Thread(futureTask,String.valueOf(i)).start();
  8. // 这个get方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信
  9. int a = futureTask.get();
  10. System.out.println("返回值:" + s);
  11. }
  12. }
  13. }
  14. class MyThread1 implements Callable<Integer> {
  15. @Override
  16. public Integer call() throws Exception {
  17. System.out.println("call()");
  18. return 1024;
  19. }
  20. }

8. 常用的辅助类

8.1、CountDownLatch

  1. public class CountDownLatchDemo {
  2. public static void main(String[] args) throws InterruptedException {
  3. // 总数是6
  4. CountDownLatch countDownLatch = new CountDownLatch(6);
  5. for (int i = 1; i <= 6; i++) {
  6. new Thread(() -> {
  7. System.out.println(Thread.currentThread().getName() + "==> Go Out");
  8. countDownLatch.countDown(); // 每个线程都数量 -1
  9. },String.valueOf(i)).start();
  10. }
  11. countDownLatch.await(); // 等待计数器归零 然后向下执行
  12. System.out.println("close door");
  13. }
  14. }

主要方法:

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

await 等待计数器归零,就唤醒,再继续向下运行

8.2、CyclickBarrier

JUC编程 - 图7

  1. public class CyclicBarrierDemo {
  2. public static void main(String[] args) {
  3. // 主线程
  4. CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
  5. System.out.println("召唤神龙");
  6. });
  7. for (int i = 1; i <= 7; i++) {
  8. // 子线程
  9. int finalI = i;
  10. new Thread(() -> {
  11. System.out.println(Thread.currentThread().getName() + "收集了第" + finalI + "颗龙珠");
  12. try {
  13. cyclicBarrier.await(); // 加法计数 等待
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } catch (BrokenBarrierException e) {
  17. e.printStackTrace();
  18. }
  19. }).start();
  20. }
  21. }
  22. }

8.3、Semaphore

  1. public class SemaphoreDemo {
  2. public static void main(String[] args) {
  3. // 线程数量,停车位,限流
  4. Semaphore semaphore = new Semaphore(3);
  5. for (int i = 0; i <= 6; i++) {
  6. new Thread(() -> {
  7. // acquire() 得到
  8. try {
  9. semaphore.acquire();
  10. System.out.println(Thread.currentThread().getName() + "抢到车位");
  11. TimeUnit.SECONDS.sleep(2);
  12. System.out.println(Thread.currentThread().getName() + "离开车位");
  13. }catch (Exception e) {
  14. e.printStackTrace();
  15. }finally {
  16. semaphore.release(); // release() 释放
  17. }
  18. }).start();
  19. }
  20. }
  21. }
  22. Thread-1抢到车位
  23. Thread-0抢到车位
  24. Thread-2抢到车位
  25. Thread-0离开车位
  26. Thread-2离开车位
  27. Thread-1离开车位
  28. Thread-5抢到车位
  29. Thread-3抢到车位
  30. Thread-4抢到车位
  31. Thread-5离开车位
  32. Thread-3离开车位
  33. Thread-6抢到车位
  34. Thread-4离开车位
  35. Thread-6离开车位
  36. Process finished with exit code 0

原理:

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

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

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

9. 读写锁

  1. public class ReadWriteLockDemo {
  2. public static void main(String[] args) {
  3. MyCache myCache = new MyCache();
  4. int num = 6;
  5. for (int i = 1; i <= num; i++) {
  6. int finalI = i;
  7. new Thread(() -> {
  8. myCache.write(String.valueOf(finalI), String.valueOf(finalI));
  9. },String.valueOf(i)).start();
  10. }
  11. for (int i = 1; i <= num; i++) {
  12. int finalI = i;
  13. new Thread(() -> {
  14. myCache.read(String.valueOf(finalI));
  15. },String.valueOf(i)).start();
  16. }
  17. }
  18. }
  19. /**
  20. * 方法未加锁,导致写的时候被插队
  21. */
  22. class MyCache {
  23. private volatile Map<String, String> map = new HashMap<>();
  24. public void write(String key, String value) {
  25. System.out.println(Thread.currentThread().getName() + "线程开始写入");
  26. map.put(key, value);
  27. System.out.println(Thread.currentThread().getName() + "线程写入ok");
  28. }
  29. public void read(String key) {
  30. System.out.println(Thread.currentThread().getName() + "线程开始读取");
  31. map.get(key);
  32. System.out.println(Thread.currentThread().getName() + "线程写读取ok");
  33. }
  34. }
  1. 2线程开始写入
  2. 2线程写入ok
  3. 3线程开始写入
  4. 3线程写入ok
  5. 1线程开始写入 # 插入了其他线程的写入,导致数据不一致
  6. 4线程开始写入
  7. 4线程写入ok
  8. 1线程写入ok
  9. 6线程开始写入
  10. 6线程写入ok
  11. 5线程开始写入
  12. 5线程写入ok
  13. 1线程开始读取
  14. 1线程写读取ok
  15. 2线程开始读取
  16. 2线程写读取ok
  17. 3线程开始读取
  18. 3线程写读取ok
  19. 4线程开始读取
  20. 4线程写读取ok
  21. 5线程开始读取
  22. 6线程开始读取
  23. 6线程写读取ok
  24. 5线程写读取ok
  25. Process finished with exit code 0

所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。

我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。

但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证

JUC编程 - 图8

  1. public class ReadWriteLockDemo {
  2. public static void main(String[] args) {
  3. MyCache2 myCache = new MyCache2();
  4. int num = 6;
  5. for (int i = 1; i <= num; i++) {
  6. int finalI = i;
  7. new Thread(() -> {
  8. myCache.write(String.valueOf(finalI), String.valueOf(finalI));
  9. },String.valueOf(i)).start();
  10. }
  11. for (int i = 1; i <= num; i++) {
  12. int finalI = i;
  13. new Thread(() -> {
  14. myCache.read(String.valueOf(finalI));
  15. },String.valueOf(i)).start();
  16. }
  17. }
  18. }
  19. class MyCache2 {
  20. private volatile Map<String, String> map = new HashMap<>();
  21. private ReadWriteLock lock = new ReentrantReadWriteLock();
  22. public void write(String key, String value) {
  23. lock.writeLock().lock(); // 写锁
  24. try {
  25. System.out.println(Thread.currentThread().getName() + "线程开始写入");
  26. map.put(key, value);
  27. System.out.println(Thread.currentThread().getName() + "线程写入ok");
  28. }finally {
  29. lock.writeLock().unlock(); // 释放写锁
  30. }
  31. }
  32. public void read(String key) {
  33. lock.readLock().lock(); // 读锁
  34. try {
  35. System.out.println(Thread.currentThread().getName() + "线程开始读取");
  36. map.get(key);
  37. System.out.println(Thread.currentThread().getName() + "线程写读取ok");
  38. }finally {
  39. lock.readLock().unlock(); // 释放读锁
  40. }
  41. }
  42. }
  1. 1线程开始写入
  2. 1线程写入ok
  3. 6线程开始写入
  4. 6线程写入ok
  5. 3线程开始写入
  6. 3线程写入ok
  7. 2线程开始写入
  8. 2线程写入ok
  9. 5线程开始写入
  10. 5线程写入ok
  11. 4线程开始写入
  12. 4线程写入ok
  13. 1线程开始读取
  14. 5线程开始读取
  15. 2线程开始读取
  16. 1线程写读取ok
  17. 3线程开始读取
  18. 2线程写读取ok
  19. 6线程开始读取
  20. 6线程写读取ok
  21. 5线程写读取ok
  22. 4线程开始读取
  23. 4线程写读取ok
  24. 3线程写读取ok
  25. Process finished with exit code 0

10. 阻塞队列

JUC编程 - 图9

JUC编程 - 图10

10.1、BlockQueue

是Collection的一个子类

什么情况下我们会使用阻塞队列

多线程并发处理、线程池

JUC编程 - 图11

BlockingQueue 有四组api

方式 抛出异常 不会抛出异常,有返回值 阻塞,等待 超时等待
添加 add offer put offer(timenum.timeUnit)
移出 remove poll take poll(timenum,timeUnit)
判断队首元素 element peek - -
  1. /**
  2. * 抛出异常
  3. */
  4. public static void test1(){
  5. //需要初始化队列的大小
  6. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  7. System.out.println(blockingQueue.add("a"));
  8. System.out.println(blockingQueue.add("b"));
  9. System.out.println(blockingQueue.add("c"));
  10. //抛出异常:java.lang.IllegalStateException: Queue full
  11. // System.out.println(blockingQueue.add("d"));
  12. System.out.println(blockingQueue.remove());
  13. System.out.println(blockingQueue.remove());
  14. System.out.println(blockingQueue.remove());
  15. //如果多移除一个
  16. //这也会造成 java.util.NoSuchElementException 抛出异常
  17. System.out.println(blockingQueue.remove());
  18. }
  19. =======================================================================================
  20. /**
  21. * 不抛出异常,有返回值
  22. */
  23. public static void test2(){
  24. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  25. System.out.println(blockingQueue.offer("a"));
  26. System.out.println(blockingQueue.offer("b"));
  27. System.out.println(blockingQueue.offer("c"));
  28. //添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
  29. System.out.println(blockingQueue.offer("d"));
  30. System.out.println(blockingQueue.poll());
  31. System.out.println(blockingQueue.poll());
  32. System.out.println(blockingQueue.poll());
  33. //弹出 如果没有元素 只会返回null 不会抛出异常
  34. System.out.println(blockingQueue.poll());
  35. }
  36. =======================================================================================
  37. /**
  38. * 等待 一直阻塞
  39. */
  40. public static void test3() throws InterruptedException {
  41. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  42. //一直阻塞 不会返回
  43. blockingQueue.put("a");
  44. blockingQueue.put("b");
  45. blockingQueue.put("c");
  46. //如果队列已经满了, 再进去一个元素 这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止
  47. // blockingQueue.put("d");
  48. System.out.println(blockingQueue.take());
  49. System.out.println(blockingQueue.take());
  50. System.out.println(blockingQueue.take());
  51. //如果我们再来一个 这种情况也会等待,程序会一直运行 阻塞
  52. System.out.println(blockingQueue.take());
  53. }
  54. =======================================================================================
  55. /**
  56. * 等待 超时阻塞
  57. * 这种情况也会等待队列有位置 或者有产品 但是会超时结束
  58. */
  59. public static void test4() throws InterruptedException {
  60. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  61. blockingQueue.offer("a");
  62. blockingQueue.offer("b");
  63. blockingQueue.offer("c");
  64. System.out.println("开始等待");
  65. blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待
  66. System.out.println("结束等待");
  67. System.out.println("===========取值==================");
  68. System.out.println(blockingQueue.poll());
  69. System.out.println(blockingQueue.poll());
  70. System.out.println(blockingQueue.poll());
  71. System.out.println("开始等待");
  72. blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
  73. System.out.println("结束等待");
  74. }

10.2、同步队列

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

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;

put方法 和 take方法;

Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

  1. package com.marchsoft.queue;
  2. import java.util.concurrent.BlockingDeque;
  3. import java.util.concurrent.BlockingQueue;
  4. /**
  5. * Description:
  6. *
  7. * @author jiaoqianjin
  8. * Date: 2020/8/12 10:02
  9. **/
  10. public class SynchronousQueue {
  11. public static void main(String[] args) {
  12. BlockingQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();
  13. // 网queue中添加元素
  14. new Thread(() -> {
  15. try {
  16. System.out.println(Thread.currentThread().getName() + "put 01");
  17. synchronousQueue.put("1");
  18. System.out.println(Thread.currentThread().getName() + "put 02");
  19. synchronousQueue.put("2");
  20. System.out.println(Thread.currentThread().getName() + "put 03");
  21. synchronousQueue.put("3");
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }).start();
  26. // 取出元素
  27. new Thread(()-> {
  28. try {
  29. System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
  30. System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
  31. System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
  32. }catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }).start();
  36. }
  37. }
  1. Thread-0put 01
  2. Thread-1take1
  3. Thread-0put 02
  4. Thread-1take2
  5. Thread-0put 03
  6. Thread-1take3
  7. Process finished with exit code 0

11. 线程池

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

池化技术

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

线程池、JDBC的连接池、内存池、对象池 等等。。。。

资源的创建、销毁十分消耗资源

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

11.1、线程池的好处:

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理;

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

11.2、线程池:三大方法

  • ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  • ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
  1. //工具类 Executors 三大方法;
  2. public class Demo01 {
  3. public static void main(String[] args) {
  4. ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  5. ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  6. ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
  7. //线程池用完必须要关闭线程池
  8. try {
  9. for (int i = 1; i <=100 ; i++) {
  10. //通过线程池创建线程
  11. threadPool.execute(()->{
  12. System.out.println(Thread.currentThread().getName()+ " ok");
  13. });
  14. }
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } finally {
  18. threadPool.shutdown();
  19. }
  20. }
  21. }

11.3、七大参数

JUC编程 - 图12

  1. public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
  2. int maximumPoolSize, //最大的线程池大小
  3. long keepAliveTime, //超时了没有人调用就会释放
  4. TimeUnit unit, //超时单位
  5. BlockingQueue<Runnable> workQueue, //阻塞队列
  6. ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
  7. RejectedExecutionHandler handler //拒绝策略
  8. ) {
  9. if (corePoolSize < 0 ||
  10. maximumPoolSize <= 0 ||
  11. maximumPoolSize < corePoolSize ||
  12. keepAliveTime < 0)
  13. throw new IllegalArgumentException();
  14. if (workQueue == null || threadFactory == null || handler == null)
  15. throw new NullPointerException();
  16. this.corePoolSize = corePoolSize;
  17. this.maximumPoolSize = maximumPoolSize;
  18. this.workQueue = workQueue;
  19. this.keepAliveTime = unit.toNanos(keepAliveTime);
  20. this.threadFactory = threadFactory;
  21. this.handler = handler;
  22. }

阿里巴巴规范

JUC编程 - 图13

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。

  1. public class PollDemo {
  2. public static void main(String[] args) {
  3. // 获取cpu 的核数
  4. int max = Runtime.getRuntime().availableProcessors();
  5. ExecutorService service =new ThreadPoolExecutor(
  6. 2,
  7. max,
  8. 3,
  9. TimeUnit.SECONDS,
  10. new LinkedBlockingDeque<>(3),
  11. Executors.defaultThreadFactory(),
  12. new ThreadPoolExecutor.AbortPolicy()
  13. );
  14. try {
  15. for (int i = 1; i <= 10; i++) {
  16. service.execute(() -> {
  17. System.out.println(Thread.currentThread().getName() + "ok");
  18. });
  19. }
  20. }catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. finally {
  24. service.shutdown();
  25. }
  26. }
  27. }

11.4、四种拒绝策略

  1. new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

  1. new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

  2. new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

  3. new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

11.5、如何设置线程池的大小

1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

  1. // 获取cpu 的核数
  2. int max = Runtime.getRuntime().availableProcessors();
  3. ExecutorService service =new ThreadPoolExecutor(
  4. 2,
  5. max,
  6. 3,
  7. TimeUnit.SECONDS,
  8. new LinkedBlockingDeque<>(3),
  9. Executors.defaultThreadFactory(),
  10. new ThreadPoolExecutor.AbortPolicy()
  11. );

2、I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

小结和扩展

了解io密集型、CPU密集型 调优

池的最大线程如何设置

12、四大函数式接口

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

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

  1. @FunctionalInterface
  2. public interface Runnable {
  3. public abstract void run();
  4. }
  5. //超级多的@FunctionalInterface
  6. //简化编程模型,在新版本的框架底层大量应用
  7. //foreach()的参数也是一个函数式接口,消费者类的函数式接口

16、JMM

请你谈谈你对Volatile 的理解

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

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

什么是JMM?

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

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

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

8种操作:

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

JUC编程 - 图14

JUC编程 - 图15

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

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

17、volatile

17.1、保证可见性

  1. public class JMMDemo01 {
  2. // 如果不加volatile 程序会死循环
  3. // 加了volatile是可以保证可见性的
  4. private volatile static Integer number = 0;
  5. public static void main(String[] args) {
  6. //main线程
  7. //子线程1
  8. new Thread(()->{
  9. while (number==0){
  10. }
  11. }).start();
  12. try {
  13. TimeUnit.SECONDS.sleep(2);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. //子线程2
  18. new Thread(()->{
  19. while (number==0){
  20. }
  21. }).start();
  22. try {
  23. TimeUnit.SECONDS.sleep(2);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. number=1;
  28. System.out.println(number);
  29. }
  30. }

17.2、不保证原子性

原子性:不可分割;

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

  1. /**
  2. * 不保证原子性
  3. * number <=2w
  4. *
  5. */
  6. public class VDemo02 {
  7. private static volatile int number = 0;
  8. public static void add(){
  9. number++;
  10. //++ 不是一个原子性操作,是两个~3个操作
  11. //
  12. }
  13. public static void main(String[] args) {
  14. //理论上number === 20000
  15. for (int i = 1; i <= 20; i++) {
  16. new Thread(()->{
  17. for (int j = 1; j <= 1000 ; j++) {
  18. add();
  19. }
  20. }).start();
  21. }
  22. while (Thread.activeCount()>2){
  23. //main gc
  24. Thread.yield();
  25. }
  26. System.out.println(Thread.currentThread().getName()+",num="+number);
  27. }
  28. }

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

JUC编程 - 图16

解决方法:使用JUC下的原子包下的class;

JUC编程 - 图17

  1. public class VDemo02 {
  2. private static volatile AtomicInteger number = new AtomicInteger();
  3. public static void add(){
  4. // number++;
  5. number.incrementAndGet(); //底层是CAS保证的原子性
  6. }
  7. public static void main(String[] args) {
  8. //理论上number === 20000
  9. for (int i = 1; i <= 20; i++) {
  10. new Thread(()->{
  11. for (int j = 1; j <= 1000 ; j++) {
  12. add();
  13. }
  14. }).start();
  15. }
  16. while (Thread.activeCount()>2){
  17. //main gc
  18. Thread.yield();
  19. }
  20. System.out.println(Thread.currentThread().getName()+",num="+number);
  21. }
  22. }

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

Unsafe类是一个很特殊的存在;

原子类为什么这么高级?

17.3、禁止指令重排

什么是指令重排?

  • 我们写的程序,计算机并不是按照我们自己写的那样去执行的

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

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

  1. int x=1; //1
  2. int y=2; //2
  3. x=x+5; //3
  4. y=x*x; //4
  5. //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324
  6. //可不可能是 4123? 不可能的

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

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

正常的结果: x = 0; y =0;

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

可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排:

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

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

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

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

18、单例模式

18.1、饿汉式

  1. /**
  2. * 饿汉式单例
  3. */
  4. public class Hungry {
  5. /**
  6. * 可能会浪费空间
  7. */
  8. private byte[] data1=new byte[1024*1024];
  9. private byte[] data2=new byte[1024*1024];
  10. private byte[] data3=new byte[1024*1024];
  11. private byte[] data4=new byte[1024*1024];
  12. private Hungry(){
  13. }
  14. private final static Hungry hungry = new Hungry();
  15. public static Hungry getInstance(){
  16. return hungry;
  17. }
  18. }

18.2、DCL懒汉式

  1. //懒汉式单例模式
  2. public class LazyMan {
  3. private static boolean key = false;
  4. private LazyMan(){
  5. synchronized (LazyMan.class){
  6. if (key==false){
  7. key=true;
  8. }
  9. else{
  10. throw new RuntimeException("不要试图使用反射破坏异常");
  11. }
  12. }
  13. System.out.println(Thread.currentThread().getName()+" ok");
  14. }
  15. private volatile static LazyMan lazyMan;
  16. //双重检测锁模式 简称DCL懒汉式
  17. public static LazyMan getInstance(){
  18. //需要加锁
  19. if(lazyMan==null){
  20. synchronized (LazyMan.class){
  21. if(lazyMan==null){
  22. lazyMan=new LazyMan();
  23. /**
  24. * 1、分配内存空间
  25. * 2、执行构造方法,初始化对象
  26. * 3、把这个对象指向这个空间
  27. *
  28. * 就有可能出现指令重排问题
  29. * 比如执行的顺序是1 3 2 等
  30. * 我们就可以添加volatile保证指令重排问题
  31. */
  32. }
  33. }
  34. }
  35. return lazyMan;
  36. }
  37. //单线程下 是ok的
  38. //但是如果是并发的
  39. public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
  40. //Java中有反射
  41. // LazyMan instance = LazyMan.getInstance();
  42. Field key = LazyMan.class.getDeclaredField("key");
  43. key.setAccessible(true);
  44. Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
  45. declaredConstructor.setAccessible(true); //无视了私有的构造器
  46. LazyMan lazyMan1 = declaredConstructor.newInstance();
  47. key.set(lazyMan1,false);
  48. LazyMan instance = declaredConstructor.newInstance();
  49. System.out.println(instance);
  50. System.out.println(lazyMan1);
  51. System.out.println(instance == lazyMan1);
  52. }
  53. }

19、深入理解CAS

19.1、什么是CAS?

  1. public class casDemo {
  2. //CAS : compareAndSet 比较并交换
  3. public static void main(String[] args) {
  4. AtomicInteger atomicInteger = new AtomicInteger(2020);
  5. //boolean compareAndSet(int expect, int update)
  6. //期望值、更新值
  7. //如果实际值 和 我的期望值相同,那么就更新
  8. //如果实际值 和 我的期望值不同,那么就不更新
  9. System.out.println(atomicInteger.compareAndSet(2020, 2021));
  10. System.out.println(atomicInteger.get());
  11. //因为期望值是2020 实际值却变成了2021 所以会修改失败
  12. //CAS 是CPU的并发原语
  13. atomicInteger.getAndIncrement(); //++操作
  14. System.out.println(atomicInteger.compareAndSet(2020, 2021));
  15. System.out.println(atomicInteger.get());
  16. }
  17. }

JUC编程 - 图18

19.2、unsafe类(java操作内存的后门)

JUC编程 - 图19

JUC编程 - 图20

总结

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

缺点:

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

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

JUC编程 - 图21

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

线程2:两个操作:

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

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

  1. package com.zmy.CAS;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. public class CASDemo {
  4. //CAS compareAndSet 比较并交换
  5. public static void main(String[] args) {
  6. AtomicInteger atomicInteger=new AtomicInteger(2020);
  7. //boolean compareAndSet(int expect, int update)
  8. //期望值、更新值
  9. //如果实际值 和 我的期望值相同,那么就更新
  10. //如果实际值 和 我的期望值不同,那么就不更新
  11. //==========================捣乱的线程==================
  12. System.out.println(atomicInteger.compareAndSet(2020,2021));
  13. System.out.println(atomicInteger.get());
  14. System.out.println(atomicInteger.compareAndSet(2021,2020));
  15. System.out.println(atomicInteger.get());
  16. //==================期望的线程============================
  17. //因为期望值是2020 实际值却变成了2021 所以会修改失败
  18. //CAS 是CPU的并发原语
  19. //atomicInteger.getAndIncrement(); //++操作
  20. System.out.println(atomicInteger.compareAndSet(2020,2021));
  21. System.out.println(atomicInteger.get());
  22. }
  23. }

20、原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

带版本号的 原子操作!

坑:如果泛型是包装类,要注意对象得引用问题-

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

JUC编程 - 图22

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

JUC编程 - 图23

那么如果我们使用小于128的时候:

JUC编程 - 图24

21、各种锁的理解

21.1、公平锁、非公平锁

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

  1. /**
  2. * Creates an instance of {@code ReentrantLock}.
  3. * This is equivalent to using {@code ReentrantLock(false)}.
  4. */
  5. public ReentrantLock() {
  6. sync = new NonfairSync();
  7. }

非公平锁:非常不公平,允许插队的,可以改变顺序。(默认都是非公平锁)

  1. /**
  2. * Creates an instance of {@code ReentrantLock} with the
  3. * given fairness policy.
  4. *
  5. * @param fair {@code true} if this lock should use a fair ordering policy
  6. */
  7. public ReentrantLock(boolean fair) {
  8. sync = fair ? new FairSync() : new NonfairSync();
  9. }

21.2、可重入锁

可重入锁(递归锁)

JUC编程 - 图25

Synchronized锁

  1. public class Demo01 {
  2. public static void main(String[] args) {
  3. Phone phone = new Phone();
  4. new Thread(()->{
  5. phone.sms();
  6. },"A").start();
  7. new Thread(()->{
  8. phone.sms();
  9. },"B").start();
  10. }
  11. }
  12. class Phone{
  13. public synchronized void sms(){
  14. System.out.println(Thread.currentThread().getName()+"=> sms");
  15. call();//这里也有一把锁
  16. }
  17. public synchronized void call(){
  18. System.out.println(Thread.currentThread().getName()+"=> call");
  19. }
  20. }

lock锁

  1. //lock
  2. public class Demo02 {
  3. public static void main(String[] args) {
  4. Phone2 phone = new Phone2();
  5. new Thread(()->{
  6. phone.sms();
  7. },"A").start();
  8. new Thread(()->{
  9. phone.sms();
  10. },"B").start();
  11. }
  12. }
  13. class Phone2{
  14. Lock lock=new ReentrantLock();
  15. public void sms(){
  16. lock.lock(); //细节:这个是两把锁,两个钥匙
  17. //lock锁必须配对,否则就会死锁在里面
  18. try {
  19. System.out.println(Thread.currentThread().getName()+"=> sms");
  20. call();//这里也有一把锁
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }finally {
  24. lock.unlock();
  25. }
  26. }
  27. public void call(){
  28. lock.lock();
  29. try {
  30. System.out.println(Thread.currentThread().getName() + "=> call");
  31. }catch (Exception e){
  32. e.printStackTrace();
  33. }
  34. finally {
  35. lock.unlock();
  36. }
  37. }
  38. }
  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

21.3、自旋锁

spinlock

  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6. return var5;
  7. }

自我设计自旋锁:

  1. public class SpinlockDemo {
  2. //int 0
  3. //thread null
  4. AtomicReference<Thread> atomicReference=new AtomicReference<>();
  5. //加锁
  6. public void myLock(){
  7. Thread thread = Thread.currentThread();
  8. System.out.println(thread.getName()+"===> mylock");
  9. //自旋锁
  10. while (!atomicReference.compareAndSet(null,thread)){
  11. System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
  12. }
  13. }
  14. //解锁
  15. public void myunlock(){
  16. Thread thread=Thread.currentThread();
  17. System.out.println(thread.getName()+"===> myUnlock");
  18. atomicReference.compareAndSet(thread,null);
  19. }
  20. }
  1. public class TestSpinLock {
  2. public static void main(String[] args) throws InterruptedException {
  3. ReentrantLock reentrantLock = new ReentrantLock();
  4. reentrantLock.lock();
  5. reentrantLock.unlock();
  6. //使用CAS实现自旋锁
  7. SpinlockDemo spinlockDemo=new SpinlockDemo();
  8. new Thread(()->{
  9. spinlockDemo.myLock();
  10. try {
  11. TimeUnit.SECONDS.sleep(3);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. } finally {
  15. spinlockDemo.myunlock();
  16. }
  17. },"t1").start();
  18. TimeUnit.SECONDS.sleep(1);
  19. new Thread(()->{
  20. spinlockDemo.myLock();
  21. try {
  22. TimeUnit.SECONDS.sleep(3);
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. } finally {
  26. spinlockDemo.myunlock();
  27. }
  28. },"t2").start();
  29. }
  30. }

21.4、死锁

JUC编程 - 图26

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

  1. package com.ogj.lock;
  2. import java.util.concurrent.TimeUnit;
  3. public class DeadLock {
  4. public static void main(String[] args) {
  5. String lockA= "lockA";
  6. String lockB= "lockB";
  7. new Thread(new MyThread(lockA,lockB),"t1").start();
  8. new Thread(new MyThread(lockB,lockA),"t2").start();
  9. }
  10. }
  11. class MyThread implements Runnable{
  12. private String lockA;
  13. private String lockB;
  14. public MyThread(String lockA, String lockB) {
  15. this.lockA = lockA;
  16. this.lockB = lockB;
  17. }
  18. @Override
  19. public void run() {
  20. synchronized (lockA){
  21. System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);
  22. try {
  23. TimeUnit.SECONDS.sleep(2);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. synchronized (lockB){
  28. System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
  29. }
  30. }
  31. }
  32. }

解决问题

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

JUC编程 - 图27

2、使用jstack 进程进程号 找到死锁信息

JUC编程 - 图28

一般情况信息在最后:

JUC编程 - 图29