当前位置:   article > 正文

基于《狂神说Java》JUC并发编程--学习笔记_狂神说java课件笔记

狂神说java课件笔记

前言:


本笔记仅做学习与复习使用,不存在刻意抄袭。

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

给各位学友强烈推荐《遇见狂神说》他的整套Java学习路线使我获益匪浅!!!

点击跳转至遇见狂神说哔哩哔哩首页

如果你也是狂神的小迷弟,可以加我好友一起探讨学习。
 

JUC简述

JUC是什么?

JUC就是java.util.concurrent包,俗称java并发包,是Java开发工程师学习并发的时候需要掌握的内容。

为什么要学习JUC?

我认为起码有以下几点:

  1. 一个初级java程序员进阶学习的必经之路;
  2. 让我们深刻了解并发的思想,为以后的微服务,分布式打好基础;
  3. 能在脑海中构建一个计算机底层处理问题的大概模型,这对研究算法是很有帮助的;
  4. 最后一点,也是最现实的一点,这是面试高频考点,学习JUC并发编程,除了能丰富知识,还能提高今后面对面试的底气。

接下来就是《狂神说Java》JUC并发编程的课程笔记和我自己的总结了,或许,没有看视频的学友看着会觉得有些混乱或者逻辑错误。这里还是强调一点,各位最好是配合视频学习,笔记只当作初学或者今后复习就好。
 

一、学习方式

源码+官方文档(面试高频)

这里简述一下juc在jdk帮助文档的位置(jdk帮助文档请自行百度下载):

我们打开jdk帮助文档

点击左侧目录:

 

二、回忆多线程的学习内容

2.1、实现多线程的三种方式

  • 继承Thread类(其实Thread类根本上也实现了Runnable接口)

我们按住ctrl加鼠标左键,点击Thread :

跳转到:

 可以看到其实现了Runnable接口。

这里简单写个小例子回忆一下继承Thread类如何使用。

  1. package com.example.demo1.demo02;
  2. /**
  3. * @author liar
  4. */
  5. public class TestThread {
  6. public static void main(String[] args) {
  7. Eat eat = new Eat();
  8. Out out = new Out();
  9. eat.start();
  10. out.start();
  11. }
  12. }
  13. class Eat extends Thread{
  14. @Override
  15. public void run() {
  16. for (int i = 0; i < 500; i++) {
  17. System.out.println( "我在吃饭呢");
  18. }
  19. }
  20. }
  21. class Out extends Thread{
  22. @Override
  23. public void run() {
  24. for (int i = 0; i < 500; i++) {
  25. System.out.println("我在拉屎呢");
  26. }
  27. }
  28. }

其核心是重写run方法;其运行结果是:

  • 实现Runnable接口
  1. package com.example.demo1.demo02;
  2. /**
  3. * @author liar
  4. */
  5. public class TestRunnable {
  6. public static void main(String[] args) {
  7. Who who = new Who();
  8. FBI fbi = new FBI();
  9. Thread who1 = new Thread(who,"who");
  10. Thread fbi1 = new Thread(fbi,"FBI");
  11. who1.start();
  12. fbi1.start();
  13. }
  14. }
  15. class Who implements Runnable{
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 1000; i++) {
  19. System.out.println("Who are you ?");
  20. }
  21. }
  22. }
  23. class FBI implements Runnable{
  24. @Override
  25. public void run() {
  26. for (int i = 0; i < 1000; i++) {
  27. System.out.println("Open the door!!!FBI!!!");
  28. }
  29. }
  30. }

 运行结果也如预期。

  • 实现Callable接口
  1. package com.example.demo1.demo02;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class TestCallable {
  6. public static void main(String[] args) {
  7. Hit hit = new Hit();
  8. Connection connection = new Connection();
  9. FutureTask futureTask1 = new FutureTask(hit);
  10. FutureTask futureTask2 = new FutureTask(connection);
  11. Thread t1 = new Thread(futureTask1);
  12. Thread t2 = new Thread(futureTask2);
  13. t1.start();
  14. t2.start();
  15. try {
  16. //get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
  17. Object o1 = futureTask1.get();
  18. Object o2 = futureTask2.get();
  19. System.out.println(o1);
  20. System.out.println(o2);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } catch (ExecutionException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. class Hit implements Callable{
  29. @Override
  30. public Object call() throws Exception {
  31. for (int i = 0; i < 500; i++) {
  32. System.out.println("怎么样!!你打我啊!!!");
  33. }
  34. return "hit";
  35. }
  36. }
  37. class Connection implements Callable{
  38. @Override
  39. public Object call() throws Exception {
  40. for (int i = 0; i < 500; i++) {
  41. System.out.println("小伙子,出来混,是要讲人脉的!!!");
  42. }
  43. return "connection";
  44. }
  45. }

其运行结果:

 其与前两者的区别是,可以通过FutureTask获取返回值的。

 

 2.2、总结

普通的线程代码:继承Thread类

Runnable接口:没有返回值,效率相比于Callable接口较低

2.3、线程和进程

关于线程和进程,如果不能用一句话说出来,说明你掌握的还不够扎实!

进程:

  • 一个正在执行的程序
  • 例如:QQ.exe;是运行程序的集合
  • 一个进程可以包含多个线程且至少包含一个线程

Java真的能开启线程吗?

  • 不能开启线程
  • 分析Thread类的原码了解,最后该类还是调用了本地方法(native)
  • Java的底层是c++

2.4、并发和并行

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

  • CPU一核,模拟出来多线程;天下武功唯快不破,快速交替

并行:(多个人同时走路)

  • CPU多核,多个线程同时执行

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

2.5、线程有几个状态

  1. public enum State {
  2. NEW,//新生
  3. RUNNABLE,//运行
  4. BLOCKED,//阻塞
  5. WAITING,//等待,一直等待
  6. TIMED_WAITING,//超时等待,过期不候
  7. TERMINATED;//终止
  8. }

2.6、wait和sleep的区别

来自不同的类:

  • wait==>Object
  • sleep==>Thread

关于锁的释放

  • wait:会释放锁
  • sleep:“抱着锁睡觉,不会释放锁”

使用的范围是不同的

  • wait:必须在同步代码块中使用
  • sleep:任何地方

是否需要捕获异常

  • wait:不需要捕获异常(什么叫中断异常?)
  • sleep:需要捕获异常(存在超时等待的情况)
     

三、Lock锁(重点)

3.1、回忆"synchronized"

作用:

保证线程安全,例如当多个线程访问统一资源时,会发生数据紊乱问题;

怎么理解:

队列+锁

实例:没有synchronized修饰的方法

  1. package com.example.demo1.demo02;
  2. /**
  3. * @author liar
  4. */
  5. public class UnsafeTicket {
  6. public static void main(String[] args) {
  7. BuyTicket ticket = new BuyTicket();
  8. new Thread(ticket,"倒霉的我").start();
  9. new Thread(ticket,"幸运的你们").start();
  10. new Thread(ticket,"可恶的黄牛党").start();
  11. }
  12. }
  13. class BuyTicket implements Runnable{
  14. private int tickets = 10;
  15. private boolean flag = true;
  16. @Override
  17. public void run() {
  18. while (flag){
  19. try {
  20. Thread.sleep(100);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. buy();
  25. }
  26. }
  27. //在方法名前加上"synchronized",那么该方法就变成了同步方法了
  28. //!!!锁的是"this"!!!
  29. //'synchronized'默认锁的是this
  30. private synchronized void buy(){
  31. if (tickets<=0){
  32. flag=false;
  33. //这个return放在这里,如果符合if条件,那么返回出去,不执行下面的sout语句
  34. return;
  35. }
  36. System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
  37. }
  38. }

我们看一下运行效果:

 实例:有synchronized修饰的方法

  1. package com.example.demo1.demo02;
  2. /**
  3. * @author liar
  4. */
  5. public class UnsafeTicket {
  6. public static void main(String[] args) {
  7. BuyTicket ticket = new BuyTicket();
  8. new Thread(ticket,"倒霉的我").start();
  9. new Thread(ticket,"幸运的你们").start();
  10. new Thread(ticket,"可恶的黄牛党").start();
  11. }
  12. }
  13. class BuyTicket implements Runnable{
  14. private int tickets = 10;
  15. private boolean flag = true;
  16. @Override
  17. public void run() {
  18. while (flag){
  19. try {
  20. Thread.sleep(100);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. buy();
  25. }
  26. }
  27. //在方法名前加上"synchronized",那么该方法就变成了同步方法了
  28. //!!!锁的是"this"!!!
  29. //'synchronized'默认锁的是this
  30. private synchronized void buy(){
  31. if (tickets<=0){
  32. flag=false;
  33. //这个return放在这里,如果符合if条件,那么返回出去,不执行下面的sout语句
  34. return;
  35. }
  36. System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
  37. }
  38. }

 这下,就是线程安全的了。

3.2、Lock接口(Lock锁)

 

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

3.3、Synchronized和Lock的区别


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

3.5、思考

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

四、生产者和消费者问题

面试:单例模式、排序算法、生产者和消费者问题、死锁

4.1、synchronized版

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

这样会出现问题!!–虚假唤醒

场景:当出现多个生产者和消费者时,会出现数据紊乱?

原因:就是这个if语句只判断一次:当消费者线程阻塞,来唤醒生产者线程时,多个生产者线程都会被唤醒,但是只有第二个生产者线程执行了if判断,然后第二个生产者线程阻塞(基于if语句的机理),因为第一个生产者线程能够正常执行,而剩余的其他生产者线程,则会执行if语句后面的方法(“意思就是后面的生产者线程在if判断前面生产者线程时,利用了if语句的bug捡了漏”)

(1)if判断流水线状态为空时,线程被阻塞,这时if判断就完成了,线程被唤醒后直接执行线程剩余操作

(2)while判断流水线状态为空时,线程被阻塞,这时的while循环没有完成,线程被唤醒后会先进行while判断

解决方法:

把if语句改为while语句

  1. package com.my.pc;
  2. /*
  3. * 线程之间的通信问题:生产者和消费者问题(等待唤醒,通知唤醒)
  4. *
  5. * 线程交替执行:
  6. * 生产者和消费者操作同一个变量
  7. * 生产者:+1
  8. * 消费者:-1
  9. * */
  10. public class Test {
  11. public static void main(String[] args) {
  12. Data data = new Data();
  13. new Thread(()->{
  14. try {
  15. for (int i = 0; i < 10; i++) {
  16. data.increment();
  17. }
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. },"生产者").start();
  22. new Thread(()->{
  23. try {
  24. for (int i = 0; i < 10; i++) {
  25. data.decrement();
  26. }
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. },"消费者").start();
  31. new Thread(()->{
  32. try {
  33. for (int i = 0; i < 10; i++) {
  34. data.decrement();
  35. }
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. },"C").start();
  40. new Thread(()->{
  41. try {
  42. for (int i = 0; i < 10; i++) {
  43. data.decrement();
  44. }
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. },"D").start();
  49. }
  50. }
  51. //资源类
  52. /*
  53. * 编写资源类的思路:
  54. * 判断等待、业务、通知
  55. * */
  56. class Data{
  57. private int number = 0;
  58. public synchronized void increment() throws Interrupted
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/892865
推荐阅读
相关标签
  

闽ICP备14008679号