03 任务总结.png

一、基本概念

(1)程序和进程的概念

  • 程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
  • 进程 - 主要指运行在内存中的可执行文件。
  • 目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,
  • 也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。

    (2)线程的概念

  • 为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多

  • 进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资
  • 源,因此目前主流的开发都是采用多线程。
  • 多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机
  • 制。

注:若是单核CPU,则将进程分成多个线程,每个线程分配一个时间片,快速执行完一个就到下一个,这让微观看起来是串行的操作,从宏观看起来是并行,而对于现在的硬件水平来说,4、8核的CPU能更好地去处理并发文问题。

二、线程的创建(重点)

来自CSDN,参考链接:https://blog.csdn.net/m0_37840000/article/details/79756932

(1) Thread类的概念

java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

(2) 创建方式

1、自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
2、自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对
象,然后使用Thread类型的对象调用start方法。

(3)常用方法

image.png

(4)run方法

注:调用前两个方法,run()方法实际上啥都不做

  1. package task14;
  2. /*
  3. * 通过代码看看run()是不是真的啥都不干
  4. * */
  5. public class ThreadTest {
  6. public static void main(String[] args) {
  7. //使用无参构造方法,创建Thread 对象
  8. //由源码可是,Thread类中的成员变量target为null
  9. Thread T=new Thread();
  10. //调用run()方法,方法中如果target为空,则啥都不干
  11. /*
  12. * 源码:
  13. * @Override
  14. public void run() {
  15. if (target != null) {
  16. target.run();
  17. }
  18. }*/
  19. //所以由于成员变量中的target为null,并且if (target != null)后没有其他代码,跳出{},所以啥都不干
  20. T.run();
  21. //输出语句简单测试
  22. System.out.println("是否真的啥都不干!");
  23. }
  24. }

源码截图:
(1)Thread类
image.png
点击init进入
image.png
此时this.target是成员变量(this关键字的使用)
image.png
(2)run方法源码
image.png

(5)执行流程(创建方式一)

  • 执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。

  • main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成

  • 功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两
  • 个线程各自独立运行互不影响。

  • 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。

  • 两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。

(1)创建方式一and直接使用run方法
继承Thread类

  1. package task14;
  2. public class SubThread extends Thread {
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 20; i++) {
  6. System.out.println("run方法中的i="+i);
  7. }
  8. }
  9. }

测试类调用,直接调用main方法

  1. package task14;
  2. public class ThreadTest2 {
  3. public static void main(String[] args) {
  4. Thread t1=new SubThread();
  5. t1.run();
  6. for (int i = 0; i <20 ; i++) {
  7. System.out.println("-----------------------------这是main方法的i="+i);
  8. }
  9. }
  10. }

输出结果:
image.png
按顺序执行
(2)调用Start方法

  1. package task14;
  2. public class ThreadTest2 {
  3. public static void main(String[] args) {
  4. Thread t1=new SubThread();
  5. t1.start();
  6. for (int i = 0; i <20 ; i++) {
  7. System.out.println("-----------------------------这是main方法的i="+i);
  8. }
  9. }
  10. }

image.png
说明调用run()方法并没有启动线程,调用start()才启动线程。
宏观上实现并发操作,main为主线程。

  • 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。
  • 两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。

    (6)实现Runnable接口创建线程

    自定义类实现Runnable接口 ```java package task14;

public class SubRunnableTest implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(“run方法中的i=”+i); } } }

  1. ```java
  2. package task14;
  3. //测试类
  4. public class SubRunnableTest2 {
  5. public static void main(String[] args) {
  6. SubRunnableTest t=new SubRunnableTest();
  7. Thread t2=new Thread(t);
  8. t2.start();
  9. for (int i = 0; i <20 ; i++) {
  10. System.out.println("-----------------------------这是main方法的i="+i);
  11. }
  12. }
  13. }

传入t,相当于target=t,所以target.run(),实际上也是t.run();(具体参见源码)
image.png
(7)匿名内部类
未优化

  1. package task14;
  2. public class InnerRunnable {
  3. public static void main(String[] args) {
  4. //继承Thread父类创建线程并启动
  5. Thread t1=new Thread(){
  6. @Override
  7. public void run(){
  8. System.out.println("在吗?");
  9. }
  10. };
  11. t1.start();
  12. //实现接口并重写方法创建线程并启动
  13. Runnable r=new Runnable() {
  14. @Override
  15. public void run() {
  16. System.out.println("不在!");
  17. }
  18. };
  19. Thread t=new Thread(r);
  20. t.start();
  21. }
  22. }

优化后:
使用lambda更为简便

  1. package task14;
  2. public class InnerRunnable {
  3. public static void main(String[] args) {
  4. //继承Thread父类创建线程并启动
  5. /*Thread t1=new Thread(){
  6. @Override
  7. public void run(){
  8. System.out.println("在吗?");
  9. }
  10. };
  11. t1.start();*/
  12. new Thread(){
  13. @Override
  14. public void run(){
  15. System.out.println("在吗?");
  16. }
  17. }.start();
  18. //实现接口并重写方法创建线程并启动
  19. /* Runnable r=new Runnable() {
  20. @Override
  21. public void run() {
  22. System.out.println("不在!");
  23. }
  24. };
  25. Thread t=new Thread(r);
  26. t.start();*/
  27. new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. System.out.println("不在!");
  31. }
  32. }).start();
  33. //更为简便的方法还可以使用lambda表达式:(形参列表)->方法体
  34. Runnable ra=()-> System.out.println("不在!");
  35. new Thread(ra).start();
  36. }
  37. }

(7)方式的比较

1、继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现
2、Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中
推荐使用第二种方式。
第2种方式可以多实现

三、线程的生命周期

(1)生命周期图示

image.png
17、多线程 - 图13
(来源:https://www.cnblogs.com/luojack/p/10840669.html
image.png
来源(https://blog.csdn.net/xiaosheng900523/article/details/82964768

  • 新建状态 - 使用new关键字创建之后进入的状态,此时线程并没有开始执行。
  • 就绪状态 - 调用start方法后进入的状态,此时线程还是没有开始执行。
  • 运行状态 - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完
  • 毕后任务没有完成时回到就绪状态。
  • 消亡状态 - 当线程的任务执行完成后进入的状态,此时线程已经终止。
  • 阻塞状态 - 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。 唤醒后,再回到就绪状态(相当于重新排队)
  • 阻塞状态解除后进入就绪状态。

    四、线程的编号和名称

    image.png
    案例题目
    自定义类继承Thread类并重写run方法,在run方法中先打印当前线程的编号和名称,然后将线程
    的名称修改为”zhangfei”后再次打印编号和名称。 要求在main方法中也要打印主线程的编号和名称。

    (1)继承方式管理线程编号和名字

    ```java package task14;

import java.util.Currency;

public class ThreadNameTest extends Thread{ @Override public void run() { System.out.println(“子线程的编号是:”+getId()+” 线程名称是:”+getName()); setName(“Zhangfei”); System.out.println(“子线程的编号是:”+getId()+” 线程名称是:”+getName());

  1. }
  2. public static void main(String[] args) {
  3. ThreadNameTest t1=new ThreadNameTest();
  4. t1.start();
  5. //获取当前正在执行的线程,当前正在执行的线程为main
  6. Thread t2=Thread.currentThread();
  7. System.out.println("主线程的编号是:"+t2.getId()+"线程名字是:"+t2.getName());
  8. }

}

  1. 2)实现方式管理线程编号和名字
  2. ```java
  3. package task14;
  4. public class RunnableIdName implements Runnable{
  5. @Override
  6. public void run() {
  7. Thread t1=Thread.currentThread();
  8. System.out.println("子线程的编号是:"+t1.getId()+"子线程的名字是"+t1.getName());
  9. }
  10. public static void main(String[] args) {
  11. RunnableIdName r1=new RunnableIdName();
  12. Thread t3=new Thread(r1);
  13. t3.start();
  14. Thread t2=Thread.currentThread();
  15. System.out.println("主线程的编号是"+t2.getId()+"主线程的名称是"+t2.getName());
  16. }
  17. }

image.png

五、常用方法

image.png

(1)sleep()方法的使用

  1. package com.lagou.task18;
  2. import java.text.SimpleDateFormat;
  3. import java.time.LocalDateTime;
  4. import java.util.Date;
  5. public class ThreadSleepTest extends Thread {
  6. // 声明一个布尔类型的变量作为循环是否执行的条件
  7. private boolean flag = true;
  8. // 子类中重写的方法不能抛出更大的异常
  9. @Override
  10. public void run() {
  11. // 每隔一秒获取一次系统时间并打印,模拟时钟的效果
  12. while (flag) {
  13. // 获取当前系统时间并调整格式打印
  14. // LocalDateTime.now();
  15. Date d1 = new Date();
  16. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  17. System.out.println(sdf.format(d1));
  18. // 睡眠1秒钟
  19. try {
  20. Thread.sleep(1000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. public static void main(String[] args) {
  27. ThreadSleepTest tst = new ThreadSleepTest();
  28. tst.start();
  29. // 主线程等待5秒后结束子线程
  30. System.out.println("主线程开始等待...");
  31. try {
  32. Thread.sleep(5000);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. // 停止子线程 过时 不建议使用
  37. //tst.stop();
  38. tst.flag = false;
  39. System.out.println("主线程等待结束!");
  40. }
  41. }

(2)线程的优先级

优先级高的线程不一定先执行,但该线程获取到时间片的机会会更多一些

  1. package com.lagou.task18;
  2. public class ThreadPriorityTest extends Thread {
  3. @Override
  4. public void run() {
  5. //System.out.println("子线程的优先级是:" + getPriority()); // 5 10 优先级越高的线程不一定先执行。
  6. for (int i = 0; i < 20; i++) {
  7. System.out.println("子线程中:i = " + i);
  8. }
  9. }
  10. public static void main(String[] args) {
  11. ThreadPriorityTest tpt = new ThreadPriorityTest();
  12. // 设置子线程的优先级
  13. tpt.setPriority(Thread.MAX_PRIORITY);
  14. tpt.start();
  15. Thread t1 = Thread.currentThread();
  16. //System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级
  17. for (int i = 0; i < 20; i++) {
  18. System.out.println("--主线程中:i = " + i);
  19. }
  20. }
  21. }

(3)join()方法

  1. package com.lagou.task18;
  2. public class ThreadJoinTest extends Thread {
  3. @Override
  4. public void run() {
  5. // 模拟倒数10个数的效果
  6. System.out.println("倒计时开始...");
  7. for (int i = 10; i > 0; i--) {
  8. System.out.println(i);
  9. try {
  10. Thread.sleep(1000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. System.out.println("新年快乐!");
  16. }
  17. public static void main(String[] args) {
  18. ThreadJoinTest tjt = new ThreadJoinTest();
  19. tjt.start();
  20. // 主线程开始等待
  21. System.out.println("主线程开始等待...");
  22. try {
  23. // 表示当前正在执行的线程对象等待调用线程对象,也就是主线程等待子线程终止
  24. //tjt.join();
  25. tjt.join(5000); // 最多等待5秒
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. //System.out.println("终于等到你,还好没放弃!");
  30. System.out.println("可惜不是你,陪我到最后!");
  31. }
  32. }

注意:join和sleep()的区别,sleep()是自己睡大觉,join()是等别人执行完再执行,加上参数表示等到多久就不再等待了。

(4)线程的守护

  1. package com.lagou.task18;
  2. public class ThreadDaemonTest extends Thread {
  3. @Override
  4. public void run() {
  5. //System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程
  6. // 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止
  7. // 当子线程是守护线程时,当主线程结束后,则子线程随之结束
  8. for (int i = 0; i < 50; i++) {
  9. System.out.println("子线程中:i = " + i);
  10. }
  11. }
  12. public static void main(String[] args) {
  13. ThreadDaemonTest tdt = new ThreadDaemonTest();
  14. // 必须在线程启动之前设置子线程为守护线程
  15. tdt.setDaemon(true);
  16. tdt.start();
  17. for (int i = 0; i < 20; i++) {
  18. System.out.println("-------主线程中:i = " + i);
  19. }
  20. }
  21. }
  1. 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止<br /> 当子线程是守护线程时,当主线程结束后,则子线程随之结束<br />![ea4b30cfac26dad123178a07d9c867d.png](https://cdn.nlark.com/yuque/0/2021/png/22605889/1632323315781-30e9d6a6-bd6e-4c14-9ac4-21aa35f62083.png#clientId=u86b8e341-b31b-4&from=paste&height=535&id=oOTBk&margin=%5Bobject%20Object%5D&name=ea4b30cfac26dad123178a07d9c867d.png&originHeight=714&originWidth=807&originalType=binary&ratio=1&size=238677&status=done&style=none&taskId=u0843992e-2843-4862-bfae-9f8e2a33bec&width=605)

(5)案例

案例题目
编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数,其中线程二负责打印1 ~ 100之间的
所有偶数。
在main方法启动上述两个线程同时执行,主线程等待两个线程终止。

  1. package task14;
  2. //输出1~100的偶数
  3. public class Thread1 extends Thread {
  4. @Override
  5. public void run() {
  6. for (int i = 2; i <100 ; i+=2) {
  7. System.out.println("-线程1"+i);
  8. }
  9. }
  10. }
  1. package task14;
  2. //打印奇数
  3. public class Thread2 extends Thread{
  4. @Override
  5. public void run() {
  6. for (int i = 1; i <100 ; i+=2) {
  7. System.out.println("---------------------线程2"+i);
  8. }
  9. }
  10. }
  1. package task14;
  2. public class SubTest {
  3. public static void main(String[] args) {
  4. Thread1 t1=new Thread1();
  5. t1.start();
  6. Thread2 t2=new Thread2();
  7. t2.start();
  8. System.out.println("结束");
  9. }
  10. }

六、线程同步机制(重点)

(1)基本概念

  1. 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。 <br /> 多个线程并发读写同一个临界资源时会发生线程并发安全问题。 <br /> 异步操作:多线程并发的操作,各自独立运行。 <br /> 同步操作:多线程串行的操作,先后执行的顺序。
  1. package task14;
  2. public class AccountRunableTest implements Runnable{
  3. private int balance;
  4. public AccountRunableTest() {
  5. }
  6. public AccountRunableTest(int balance) {
  7. this.balance = balance;
  8. }
  9. public int getBalance() {
  10. return balance;
  11. }
  12. public void setBalance(int balance) {
  13. this.balance = balance;
  14. }
  15. @Override
  16. public void run() {
  17. int temp=getBalance();
  18. if (temp>=200){
  19. System.out.println("正在出钞!请您稍后...");
  20. temp-=200;
  21. try {
  22. Thread.sleep(5000); //模拟出钞的过程
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. System.out.println("请取走钞票");
  27. setBalance(temp);
  28. }
  29. else {
  30. System.out.println("余额不足!");
  31. }
  32. }
  33. public static void main(String[] args) {
  34. AccountRunableTest a=new AccountRunableTest(1000);
  35. Thread t1=new Thread(a);
  36. Thread t2=new Thread(a);
  37. t1.start();
  38. t2.start();
  39. System.out.println("主线程等待!");
  40. try {
  41. t1.join();
  42. t2.join();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. System.out.println("剩下的余额为"+a.getBalance());
  47. }
  48. }

image.png

线程不同步,出现问题!所以引入同步机制!

(2)解决方案

由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。
解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操
作。
经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。

(3)实现方式

在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体

方式如下:使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
}

使用同步方法的方式实现所有代码的锁定。
直接使用synchronized关键字来修饰整个方法即可
该方式等价于:
synchronized(this) { 整个方法体的代码 }
所有的对象都支持锁

(1)同步代码块实现线程同步方式一

使用锁锁住代码块

  1. package task14;
  2. public class AccountRunableTest implements Runnable{
  3. private int balance;
  4. private Demo demo=new Demo();
  5. public AccountRunableTest() {
  6. }
  7. public AccountRunableTest(int balance) {
  8. this.balance = balance;
  9. }
  10. public int getBalance() {
  11. return balance;
  12. }
  13. public void setBalance(int balance) {
  14. this.balance = balance;
  15. }
  16. @Override
  17. public void run() {
  18. System.out.println("线程启动"+Thread.currentThread());
  19. synchronized (demo) {
  20. int temp=getBalance();
  21. if (temp>=200){
  22. System.out.println("正在出钞!请您稍后...");
  23. temp-=200;
  24. try {
  25. Thread.sleep(5000); //模拟出钞的过程
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println("请取走钞票");
  30. setBalance(temp);
  31. }
  32. else {
  33. System.out.println("余额不足!");
  34. }
  35. }
  36. }
  37. public static void main(String[] args) {
  38. AccountRunableTest a=new AccountRunableTest(1000);
  39. Thread t1=new Thread(a);
  40. Thread t2=new Thread(a);
  41. t1.start();
  42. t2.start();
  43. System.out.println("主线程等待!");
  44. try {
  45. t1.join();
  46. // t2.start(); //等待线程1结束后再启动线程2 但是这样失去了多线程的意义
  47. t2.join();
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. System.out.println("剩下的余额为"+a.getBalance());
  52. }
  53. }
  54. class Demo{}

image.png
启动锁机制,则两个线程同时启动,但是只有一个在“出钞”

  1. ......
  2. try {
  3. t1.join();
  4. t2.start(); //等待线程1结束后再启动线程2 但是这样失去了多线程的意义
  5. t2.join();
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println("剩下的余额为"+a.getBalance());
  10. .......

若t2的启动在t1.join()后,只启动一个线程,等线程一结束后,线程二才会启动image.png

注意:
image.png
这样子锁不住,因为每次调用就new一个对象,那就不是同一把锁。

(2)同步代码块实现线程同步方式二

由于new两个对象,那么操作时就new了两个锁,所以要让其使用的是同一个锁,将Demo类的引用为静态,static

  1. package com.lagou.task18;
  2. public class AccountThreadTest extends Thread {
  3. private int balance; // 用于描述账户的余额
  4. private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个
  5. public AccountThreadTest() {
  6. }
  7. public AccountThreadTest(int balance) {
  8. this.balance = balance;
  9. }
  10. public int getBalance() {
  11. return balance;
  12. }
  13. public void setBalance(int balance) {
  14. this.balance = balance;
  15. }
  16. @Override
  17. public /*static*/ /*synchronized*/ void run() {
  18. /*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  19. //synchronized (dm) { // ok
  20. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  21. // 1.模拟从后台查询账户余额的过程
  22. int temp = getBalance(); // temp = 1000 temp = 1000
  23. // 2.模拟取款200元的过程
  24. if (temp >= 200) {
  25. System.out.println("正在出钞,请稍后...");
  26. temp -= 200; // temp = 800 temp = 800
  27. try {
  28. Thread.sleep(5000);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. System.out.println("请取走您的钞票!");
  33. } else {
  34. System.out.println("余额不足,请核对您的账户余额!");
  35. }
  36. // 3.模拟将最新的账户余额写入到后台
  37. setBalance(temp); // balance = 800 balance = 800
  38. //}*/
  39. test();
  40. }
  41. public /*synchronized*/ static void test() {
  42. synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步
  43. System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  44. //synchronized (dm) { // ok
  45. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  46. // 1.模拟从后台查询账户余额的过程
  47. int temp = 1000; //getBalance(); // temp = 1000 temp = 1000
  48. // 2.模拟取款200元的过程
  49. if (temp >= 200) {
  50. System.out.println("正在出钞,请稍后...");
  51. temp -= 200; // temp = 800 temp = 800
  52. try {
  53. Thread.sleep(5000);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. System.out.println("请取走您的钞票!");
  58. } else {
  59. System.out.println("余额不足,请核对您的账户余额!");
  60. }
  61. // 3.模拟将最新的账户余额写入到后台
  62. //setBalance(temp); // balance = 800 balance = 800
  63. }
  64. }
  65. public static void main(String[] args) {
  66. AccountThreadTest att1 = new AccountThreadTest(1000);
  67. att1.start();
  68. AccountThreadTest att2 = new AccountThreadTest(1000);
  69. att2.start();
  70. System.out.println("主线程开始等待...");
  71. try {
  72. att1.join();
  73. //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
  74. att2.join();
  75. } catch (InterruptedException e) {
  76. e.printStackTrace();
  77. }
  78. System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
  79. }
  80. }

(3)同步方法实现线程同步方式一

直接用synchronized修饰方法即可!

  1. package com.lagou.task18;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class AccountRunnableTest implements Runnable {
  4. private int balance; // 用于描述账户的余额
  5. private Demo dm = new Demo();
  6. private ReentrantLock lock = new ReentrantLock(); // 准备了一把锁
  7. public AccountRunnableTest() {
  8. }
  9. public AccountRunnableTest(int balance) {
  10. this.balance = balance;
  11. }
  12. public int getBalance() {
  13. return balance;
  14. }
  15. public void setBalance(int balance) {
  16. this.balance = balance;
  17. }
  18. @Override
  19. public synchronized void run() {
  20. //开始加锁
  21. lock.lock();
  22. // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
  23. //synchronized (this) { // ok 和直接使用synchronized锁住方法一样的。***
  24. System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  25. //synchronized (dm) { // ok
  26. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  27. // 1.模拟从后台查询账户余额的过程
  28. int temp = getBalance(); // temp = 1000 temp = 1000
  29. // 2.模拟取款200元的过程
  30. if (temp >= 200) {
  31. System.out.println("正在出钞,请稍后...");
  32. temp -= 200; // temp = 800 temp = 800
  33. try {
  34. Thread.sleep(5000);
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. System.out.println("请取走您的钞票!");
  39. } else {
  40. System.out.println("余额不足,请核对您的账户余额!");
  41. }
  42. // 3.模拟将最新的账户余额写入到后台
  43. setBalance(temp); // balance = 800 balance = 800
  44. //}
  45. lock.unlock(); // 实现解锁
  46. }
  47. public static void main(String[] args) {
  48. AccountRunnableTest account = new AccountRunnableTest(1000);
  49. //AccountRunnableTest account2 = new AccountRunnableTest(1000);
  50. Thread t1 = new Thread(account);
  51. Thread t2 = new Thread(account);
  52. //Thread t2 = new Thread(account2);
  53. t1.start();
  54. t2.start();
  55. System.out.println("主线程开始等待...");
  56. try {
  57. t1.join();
  58. //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
  59. t2.join();
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. System.out.println("最终的账户余额为:" + account.getBalance()); // 600 800
  64. }
  65. }
  66. class Demo{}

(4)同步方法实现线程同步二

image.png

  1. package com.lagou.task18;
  2. public class AccountThreadTest extends Thread {
  3. private int balance; // 用于描述账户的余额
  4. private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个
  5. public AccountThreadTest() {
  6. }
  7. public AccountThreadTest(int balance) {
  8. this.balance = balance;
  9. }
  10. public int getBalance() {
  11. return balance;
  12. }
  13. public void setBalance(int balance) {
  14. this.balance = balance;
  15. }
  16. @Override
  17. public /*static*/ /*synchronized*/ void run() {
  18. /*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  19. //synchronized (dm) { // ok
  20. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  21. // 1.模拟从后台查询账户余额的过程
  22. int temp = getBalance(); // temp = 1000 temp = 1000
  23. // 2.模拟取款200元的过程
  24. if (temp >= 200) {
  25. System.out.println("正在出钞,请稍后...");
  26. temp -= 200; // temp = 800 temp = 800
  27. try {
  28. Thread.sleep(5000);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. System.out.println("请取走您的钞票!");
  33. } else {
  34. System.out.println("余额不足,请核对您的账户余额!");
  35. }
  36. // 3.模拟将最新的账户余额写入到后台
  37. setBalance(temp); // balance = 800 balance = 800
  38. //}*/
  39. test();
  40. }
  41. public /*synchronized*/ static void test() {
  42. synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步
  43. System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  44. //synchronized (dm) { // ok
  45. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  46. // 1.模拟从后台查询账户余额的过程
  47. int temp = 1000; //getBalance(); // temp = 1000 temp = 1000
  48. // 2.模拟取款200元的过程
  49. if (temp >= 200) {
  50. System.out.println("正在出钞,请稍后...");
  51. temp -= 200; // temp = 800 temp = 800
  52. try {
  53. Thread.sleep(5000);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. System.out.println("请取走您的钞票!");
  58. } else {
  59. System.out.println("余额不足,请核对您的账户余额!");
  60. }
  61. // 3.模拟将最新的账户余额写入到后台
  62. //setBalance(temp); // balance = 800 balance = 800
  63. }
  64. }
  65. public static void main(String[] args) {
  66. AccountThreadTest att1 = new AccountThreadTest(1000);
  67. att1.start();
  68. AccountThreadTest att2 = new AccountThreadTest(1000);
  69. att2.start();
  70. System.out.println("主线程开始等待...");
  71. try {
  72. att1.join();
  73. //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
  74. att2.join();
  75. } catch (InterruptedException e) {
  76. e.printStackTrace();
  77. }
  78. System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
  79. }
  80. }

同步的方法实现线程同步——继承方式

(5) 静态方法的锁定

当我们对一个静态方法加锁,如:
public synchronized static void xxx(){….}
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。

(6)注意事项

使用synchronized保证线程同步应当注意:
多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
在使用同步块时应当尽量减少同步范围以提高并发的执行效率。

七、线程类和线程不安全类

StringBuffffer类是线程安全的类,但StringBuilder类不是线程安全的类。

参见源码:StrngBuffer的方法是用synchronized修饰的

Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。

如果不考虑线程安全就使用ArraryList 和 HashMap,如果考虑线程安全使用以下 的方法

Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。

八、死锁的概念

(1)死锁的概念

线程一执行的代码:
public void run(){
synchronized(a){ //持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
}
线程二执行的代码:
public void run(){
synchronized(b){ //持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
}
注意:
在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!

(2)再次认识死锁

image.png
image.png

  1. public class DeadLockDemo {
  2. private static Object resource1 = new Object();//资源 1
  3. private static Object resource2 = new Object();//资源 2
  4. public static void main(String[] args) {
  5. new Thread(() -> {
  6. synchronized (resource1) {
  7. System.out.println(Thread.currentThread() + "get resource1");
  8. try {
  9. Thread.sleep(1000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread() + "waiting get
  14. resource2");
  15. synchronized (resource2) {
  16. System.out.println(Thread.currentThread() + "get
  17. resource2");
  18. }
  19. }
  20. }, "线程 1").start();
  21. new Thread(() -> {
  22. synchronized (resource2) {
  23. System.out.println(Thread.currentThread() + "get resource2");
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println(Thread.currentThread() + "waiting get
  30. resource1");
  31. synchronized (resource1) {
  32. System.out.println(Thread.currentThread() + "get
  33. resource1");
  34. }
  35. }
  36. }, "线程 2").start();
  37. }
  38. }

output

  1. Thread[线程 1,5,main]get resource1
  2. Thread[线程 2,5,main]get resource2
  3. Thread[线程 1,5,main]waiting get resource2
  4. Thread[线程 2,5,main]waiting get resource1

image.png

(3)死锁的避免

image.png

  1. new Thread(() -> {
  2. synchronized (resource1) {
  3. System.out.println(Thread.currentThread() + "get resource1");
  4. try {
  5. Thread.sleep(1000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println(Thread.currentThread() + "waiting get
  10. resource2");
  11. synchronized (resource2) {
  12. System.out.println(Thread.currentThread() + "get
  13. resource2");
  14. }
  15. }
  16. }, "线程 2").start();

output

  1. Thread[线程 1,5,main]get resource1
  2. Thread[线程 1,5,main]waiting get resource2
  3. Thread[线程 1,5,main]get resource2
  4. Thread[线程 2,5,main]get resource1
  5. Thread[线程 2,5,main]waiting get resource2
  6. Thread[线程 2,5,main]get resource2
  7. Process finished with exit code 0

image.png

九、Lock锁实现线程同步

(1)基本概念

  1. Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。 java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程 安全控制中,经常使用ReentrantLock类显式加锁和释放锁。
  1. package com.lagou.task18;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class AccountRunnableTest implements Runnable {
  4. private int balance; // 用于描述账户的余额
  5. private Demo dm = new Demo();
  6. private ReentrantLock lock = new ReentrantLock(); // 准备了一把锁
  7. public AccountRunnableTest() {
  8. }
  9. public AccountRunnableTest(int balance) {
  10. this.balance = balance;
  11. }
  12. public int getBalance() {
  13. return balance;
  14. }
  15. public void setBalance(int balance) {
  16. this.balance = balance;
  17. }
  18. @Override
  19. public synchronized void run() {
  20. //开始加锁
  21. lock.lock();
  22. // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
  23. //synchronized (this) { // ok 和直接使用synchronized锁住方法一样的。***
  24. System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
  25. //synchronized (dm) { // ok
  26. //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
  27. // 1.模拟从后台查询账户余额的过程
  28. int temp = getBalance(); // temp = 1000 temp = 1000
  29. // 2.模拟取款200元的过程
  30. if (temp >= 200) {
  31. System.out.println("正在出钞,请稍后...");
  32. temp -= 200; // temp = 800 temp = 800
  33. try {
  34. Thread.sleep(5000);
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. System.out.println("请取走您的钞票!");
  39. } else {
  40. System.out.println("余额不足,请核对您的账户余额!");
  41. }
  42. // 3.模拟将最新的账户余额写入到后台
  43. setBalance(temp); // balance = 800 balance = 800
  44. //}
  45. lock.unlock(); // 实现解锁
  46. }
  47. public static void main(String[] args) {
  48. AccountRunnableTest account = new AccountRunnableTest(1000);
  49. //AccountRunnableTest account2 = new AccountRunnableTest(1000);
  50. Thread t1 = new Thread(account);
  51. Thread t2 = new Thread(account);
  52. //Thread t2 = new Thread(account2);
  53. t1.start();
  54. t2.start();
  55. System.out.println("主线程开始等待...");
  56. try {
  57. t1.join();
  58. //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
  59. t2.join();
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. System.out.println("最终的账户余额为:" + account.getBalance()); // 600 800
  64. }
  65. }
  66. class Demo{}

(2)常用的方法

image.png

(3)与synchronized方式的比较

  • Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动 释放。
  • Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
  • 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。

    十、线程通信

    (1)Object常用方法
    image.png

    (1)方法的使用

    ```java package Ch12_Neuedu;

public class CommunicateTest implements Runnable{ int ctt=1; @Override public void run() { while (true){ synchronized (this) { //唤醒另一个线程等待,因为我现在有锁,现在先让它在门口等着,等我调用完wait方法它再执行 notify(); if (ctt <= 100) { System.out.println(“启动”+Thread.currentThread().getName()+”——-“+ctt); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } ctt++; try { //当线程打印完一个整数后,防止线程再次打印,让线程等待,调用wait方法 wait();//线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中使用 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } }

  1. public static void main(String[] args) {
  2. CommunicateTest c1=new CommunicateTest();
  3. Thread t1=new Thread(c1);
  4. Thread t2=new Thread(c1);
  5. t1.start();
  6. t2.start();
  7. }

}

  1. <a name="il0Ml"></a>
  2. ## (2)课堂案例:
  3. ![c3465344b11d577a6bd81950c8b9f62.jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/22605889/1632557677271-37a63bef-0ff5-4620-9c16-f85ac30292b7.jpeg#clientId=ue2ee9f19-c708-4&from=paste&height=903&id=u9faef6a2&margin=%5Bobject%20Object%5D&name=c3465344b11d577a6bd81950c8b9f62.jpg&originHeight=1805&originWidth=1079&originalType=binary&ratio=1&size=197005&status=done&style=none&taskId=u2af9d2f5-5711-440b-8548-172806fa863&width=539.5)
  4. ```java
  5. public class Weather {
  6. private Integer temperature;
  7. private Integer humidity;
  8. Boolean flag = false;
  9. public Weather() {
  10. }
  11. public Weather(Integer temperature, Integer humidity) {
  12. this.temperature = temperature;
  13. this.humidity = humidity;
  14. }
  15. public Integer getTemperature() {
  16. return temperature;
  17. }
  18. public void setTemperature(Integer temperature) {
  19. this.temperature = temperature;
  20. }
  21. public Integer getHumidity() {
  22. return humidity;
  23. }
  24. public void setHumidity(Integer humidity) {
  25. this.humidity = humidity;
  26. }
  27. public String toString() {
  28. return "Weather{" +
  29. "temperature=" + temperature +
  30. ", humidity=" + humidity +
  31. '}';
  32. }
  33. public synchronized void generate() throws InterruptedException {
  34. Random random = new Random();
  35. if(flag) {
  36. wait();
  37. }
  38. this.temperature = random.nextInt(40);
  39. this.humidity = random.nextInt(100);
  40. System.out.println("生成天气数据[温度:"+this.temperature+"湿度:"+this.humidity+"]");
  41. flag = true;
  42. notifyAll();
  43. }
  44. public synchronized void read() throws InterruptedException {
  45. if(!flag) {
  46. wait();
  47. }
  48. System.out.println("读取天气数据[温度:"+this.temperature+"湿度:"+this.humidity+"]");
  49. flag = false;
  50. notifyAll();
  51. }
  52. }
  1. public class ReadWeather implements Runnable{
  2. Weather weather;
  3. public ReadWeather(Weather weather) {
  4. this.weather = weather;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. try {
  10. weather.read();
  11. Thread.sleep(100);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }
  17. }
  1. public class GenerateWeather implements Runnable{
  2. Weather weather;
  3. public GenerateWeather(Weather weather) {
  4. this.weather = weather;
  5. }
  6. public void run() {
  7. while (true) {
  8. try {
  9. weather.generate();
  10. Thread.sleep(5000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. }
  1. public class WeatherTest {
  2. public static void main(String[] args) {
  3. Weather weather = new Weather();
  4. GenerateWeather generateWeather = new GenerateWeather(weather);
  5. ReadWeather readWeather = new ReadWeather(weather);
  6. new Thread(generateWeather).start();
  7. new Thread(readWeather).start();
  8. }
  9. }

(3)生产者消费者的概念

01 生产者消费者模型.png
02 仓库类的原理.png

(4)生产者-消费者:案例

  1. package com.lagou.task18;
  2. /**
  3. * 编程实现仓库类
  4. */
  5. public class StoreHouse {
  6. private int cnt = 0; // 用于记录产品的数量
  7. public synchronized void produceProduct() {
  8. notify();
  9. if (cnt < 10) {
  10. System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");
  11. cnt++;
  12. } else {
  13. try {
  14. wait();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. public synchronized void consumerProduct() {
  21. notify();
  22. if (cnt > 0) {
  23. System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品");
  24. cnt--;
  25. } else {
  26. try {
  27. wait();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. }
  1. package com.lagou.task18;
  2. public class ConsumerThread extends Thread {
  3. // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法 合成复用原则
  4. private StoreHouse storeHouse;
  5. // 为了确保两个线程共用同一个仓库
  6. public ConsumerThread(StoreHouse storeHouse) {
  7. this.storeHouse = storeHouse;
  8. }
  9. @Override
  10. public void run() {
  11. while (true) {
  12. storeHouse.consumerProduct();
  13. try {
  14. Thread.sleep(100);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }
  1. package com.lagou.task18;
  2. /**
  3. * 编程实现生产者线程,不断地生产产品
  4. */
  5. public class ProduceThread extends Thread {
  6. // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法 合成复用原则
  7. private StoreHouse storeHouse;
  8. // 为了确保两个线程共用同一个仓库
  9. public ProduceThread(StoreHouse storeHouse) {
  10. this.storeHouse = storeHouse;
  11. }
  12. @Override
  13. public void run() {
  14. while (true) {
  15. storeHouse.produceProduct();
  16. try {
  17. Thread.sleep(1000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }
  1. package com.lagou.task18;
  2. public class StoreHouseTest {
  3. public static void main(String[] args) {
  4. // 创建仓库类的对象
  5. StoreHouse storeHouse = new StoreHouse();
  6. // 创建线程类对象并启动
  7. ProduceThread t1 = new ProduceThread(storeHouse);
  8. ConsumerThread t2 = new ConsumerThread(storeHouse);
  9. t1.start();
  10. t2.start();
  11. }
  12. }

十一、创建线程的第三种方式

(1)实现Callable接口

从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
image.png

(2)FutureTask类

java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实
现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用
后的返回结果。
常用的方法如下:
image.png

  1. package com.lagou.task18;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class ThreadCallableTest implements Callable {
  6. @Override
  7. public Object call() throws Exception {
  8. // 计算1 ~ 10000之间的累加和并打印返回
  9. int sum = 0;
  10. for (int i = 1; i <= 10000; i++) {
  11. sum +=i;
  12. }
  13. System.out.println("计算的累加和是:" + sum); // 50005000
  14. return sum;
  15. }
  16. public static void main(String[] args) {
  17. ThreadCallableTest tct = new ThreadCallableTest();
  18. FutureTask ft = new FutureTask(tct);
  19. Thread t1 = new Thread(ft);
  20. t1.start();
  21. Object obj = null;
  22. try {
  23. obj = ft.get();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. } catch (ExecutionException e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println("线程处理方法的返回值是:" + obj); // 50005000
  30. }
  31. }

十二、线程池

(1)线程池的由来
在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束
时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性
能。
(2)概念和原理
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就
从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池
中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务
后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程
池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
(3)相关类和方法
从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和
java.util.concurrent.ExecutorService接口
image.png
image.png

  1. package com.lagou.task18;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class ThreadPoolTest {
  5. public static void main(String[] args) {
  6. // 1.创建一个线程池
  7. ExecutorService executorService = Executors.newFixedThreadPool(10);
  8. // 2.向线程池中布置任务
  9. executorService.submit(new ThreadCallableTest());
  10. // 3.关闭线程池
  11. executorService.shutdown();
  12. }
  13. }