1. 线程基本概念

程序

是为了完成特定的任务,用某种语言编写的一组指令的集合.

简单说 : 就是我们写的代码

进程

  1. 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为
    该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分
    配新的内存空间
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的
    产生、存在和消亡的过程

程序 —运行run—> 进程

线程

  1. 线程由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程

其他相关概念

  1. 单线程:同一个时刻,只允许执行一个线程
  2. 多线程:同一个时刻,可以执行多个线程,比如:一个QQ进程,可以同时打
    开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
  3. 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说
    单核cpu实现的多任务就是并发
  4. 并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。并发和并行,

2. 线程基本使用

创建线程的三种方式

在java中线程来使用有两种方法

  1. 继承 Thread类,重写run方法
  2. 实现 Runnable接口,重写run方法

第17章 多线程基础 - 图1

线程应用案例 1-继承 Thread 类

  1. 请编写程序开启一个线程,该线程毎隔1秒。在控制台输出“喵喵我是小猫咪

  2. 对上题改进:当输出80次喵喵,我是小猫咪,结束该线程

  3. 使用 JConsole 监控线程执行情况,并画岀程序示意图!
    第17章 多线程基础 - 图2

  4. 当一个类继承了 Thread 类, 该类就可以当做线程使用

  5. 我们会重写 run方法,写上自己的业务代码

  6. run Thread 类 实现了 Runnable 接口的run方法

  1. @Override
  2. public void run() {
  3. if (target != null) {
  4. target.run();
  5. }
  6. }
  1. public class Thread01 {
  2. public static void main(String[] args) throws InterruptedException {
  3. //创建Cat对象,可以当做线程使用
  4. Cat cat = new Cat();
  5. //老韩读源码
  6. /*
  7. (1)
  8. public synchronized void start() {
  9. start0();
  10. }
  11. (2)
  12. //start0() 是本地方法,是JVM调用, 底层是c/c++实现
  13. //真正实现多线程的效果, 是start0(), 而不是 run
  14. private native void start0();
  15. */
  16. cat.start();//启动线程-> 最终会执行cat的run方法
  17. //cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
  18. //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
  19. //这时 主线程和子线程是交替执行..
  20. System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
  21. for(int i = 0; i < 60; i++) {
  22. System.out.println("主线程 i=" + i);
  23. //让主线程休眠
  24. Thread.sleep(1000);
  25. }
  26. }
  27. }
  1. class Cat extends Thread {
  2. int times = 0;
  3. @Override
  4. public void run() {//重写run方法,写上自己的业务逻辑
  5. while (true) {
  6. //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
  7. System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
  8. //让该线程休眠1秒 ctrl+alt+t
  9. try {
  10. Thread.sleep(1000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. if(times == 80) {
  15. break;//当times 到80, 退出while, 这时线程也就退出..
  16. }
  17. }
  18. }
  19. }

start0() 是本地方法,是JVM调用, 底层是c/c++实现真正实现多线程的效果, 是start0(), 而不是 run private native void start0();

run() 方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行

第17章 多线程基础 - 图3

线程应用案例 2-实现 Runnable 接口

  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类这时在用继承 Thread 类方法来创建线程显然不可能了。
  2. java设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程
  1. public class Thread02 {
  2. public static void main(String[] args) {
  3. Dog dog = new Dog();
  4. //dog.start(); 这里不能调用start
  5. //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
  6. Thread thread = new Thread(dog);
  7. thread.start();
  8. // Tiger tiger = new Tiger();//实现了 Runnable
  9. // ThreadProxy threadProxy = new ThreadProxy(tiger);
  10. // threadProxy.start();
  11. }
  12. }
  13. class Animal {
  14. }
  15. class Tiger extends Animal implements Runnable {
  16. @Override
  17. public void run() {
  18. System.out.println("老虎嗷嗷叫....");
  19. }
  20. }
  21. //线程代理类 , 模拟了一个极简的Thread类
  22. class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
  23. private Runnable target = null;//属性,类型是 Runnable
  24. @Override
  25. public void run() {
  26. if (target != null) {
  27. target.run();//动态绑定(运行类型Tiger)
  28. }
  29. }
  30. public ThreadProxy(Runnable target) {
  31. this.target = target;
  32. }
  33. public void start() {
  34. start0();//这个方法时真正实现多线程方法
  35. }
  36. public void start0() {
  37. run();
  38. }
  39. }
  40. class Dog implements Runnable { //通过实现Runnable接口,开发线程
  41. int count = 0;
  42. @Override
  43. public void run() { //普通方法
  44. while (true) {
  45. System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
  46. //休眠1秒
  47. try {
  48. Thread.sleep(1000);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. if (count == 10) {
  53. break;
  54. }
  55. }
  56. }
  57. }

线程应用案例 3-实现 Callable和Future接口

    1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
    1. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
    1. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
    1. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
  1. public class CallableThreadTest implements Callable<Integer> {
  2. public static void main(String[] args)
  3. {
  4. CallableThreadTest ctt = new CallableThreadTest();
  5. FutureTask<Integer> ft = new FutureTask<>(ctt);
  6. for(int i = 0;i < 100;i++)
  7. {
  8. System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
  9. if(i==20)
  10. {
  11. new Thread(ft,"有返回值的线程").start();
  12. }
  13. }
  14. try
  15. {
  16. System.out.println("子线程的返回值:"+ft.get());
  17. } catch (InterruptedException e)
  18. {
  19. e.printStackTrace();
  20. } catch (ExecutionException e)
  21. {
  22. e.printStackTrace();
  23. }
  24. }
  25. @Override
  26. public Integer call() throws Exception
  27. {
  28. int i = 0;
  29. for(;i<100;i++)
  30. {
  31. System.out.println(Thread.currentThread().getName()+" "+i);
  32. }
  33. return i;
  34. }
  35. }

静态代理模式

  1. public class Thread02 {
  2. public static void main(String[] args) {
  3. Dog dog = new Dog();
  4. //dog.start(); 这里不能调用start
  5. //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
  6. Thread thread = new Thread(dog);
  7. thread.start();
  8. // Tiger tiger = new Tiger();//实现了 Runnable
  9. // ThreadProxy threadProxy = new ThreadProxy(tiger);
  10. // threadProxy.start();
  11. }
  12. }
  13. class Animal {
  14. }
  15. class Tiger extends Animal implements Runnable {
  16. @Override
  17. public void run() {
  18. System.out.println("老虎嗷嗷叫....");
  19. }
  20. }
  21. //线程代理类 , 模拟了一个极简的Thread类
  22. class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
  23. private Runnable target = null;//属性,类型是 Runnable
  24. @Override
  25. public void run() {
  26. if (target != null) {
  27. target.run();//动态绑定(运行类型Tiger)
  28. }
  29. }
  30. public ThreadProxy(Runnable target) {
  31. this.target = target;
  32. }
  33. public void start() {
  34. start0();//这个方法时真正实现多线程方法
  35. }
  36. public void start0() {
  37. run();
  38. }
  39. }
  40. class Dog implements Runnable { //通过实现Runnable接口,开发线程
  41. int count = 0;
  42. @Override
  43. public void run() { //普通方法
  44. while (true) {
  45. System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
  46. //休眠1秒
  47. try {
  48. Thread.sleep(1000);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. if (count == 10) {
  53. break;
  54. }
  55. }
  56. }
  57. }

线程使用应用案例-多线程执行

请编写一个程序创建两个线程一个线程每隔1秒输出“hell,word”:输出10次,退出,一个线程每隔1秒输出“hi”,输出5次退出

  1. public class Thread03 {
  2. public static void main(String[] args) {
  3. T1 t1 = new T1();
  4. T2 t2 = new T2();
  5. Thread thread1 = new Thread(t1);
  6. Thread thread2 = new Thread(t2);
  7. thread1.start();//启动第1个线程
  8. thread2.start();//启动第2个线程
  9. //...
  10. }
  11. }
  12. class T1 implements Runnable {
  13. int count = 0;
  14. @Override
  15. public void run() {
  16. while (true) {
  17. //每隔1秒输出 “hello,world”,输出10次
  18. System.out.println("hello,world " + (++count));
  19. try {
  20. Thread.sleep(1000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. if(count == 60) {
  25. break;
  26. }
  27. }
  28. }
  29. }
  30. class T2 implements Runnable {
  31. int count = 0;
  32. @Override
  33. public void run() {
  34. //每隔1秒输出 “hi”,输出5次
  35. while (true) {
  36. System.out.println("hi " + (++count));
  37. try {
  38. Thread.sleep(1000);
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. if(count == 50) {
  43. break;
  44. }
  45. }
  46. }
  47. }

3. 继承 Thread vs 实现 Runnable 的区别

  1. 从java的设计来看,通过继承 Thread 或者实现 Runnable 接口来创建线程本质上没有区别从jdk帮助文档我们可以看到 Thread 类本身就实现了 Runnable 接口 start() -> start0()
  2. 实现 Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用 Runnable
  3. [售票系统],编程模拟三个售票窗口售票100分别使用继承 Thread 和实现 Runnable 方式并分析有什么问题?
    1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
    1. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

4. 线程终止

基本说明

  1. 当线程完成任务后,会自动退出

  2. 还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式

应用案例

  1. public class ThreadExit_ {
  2. public static void main(String[] args) throws InterruptedException {
  3. T t1 = new T();
  4. t1.start();
  5. //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
  6. //让t1 退出run方法,从而终止 t1线程 -> 通知方式
  7. //让主线程休眠 10 秒,再通知 t1线程退出
  8. System.out.println("main线程休眠10s...");
  9. Thread.sleep(10 * 1000);
  10. t1.setLoop(false);
  11. }
  12. }
  13. class T extends Thread {
  14. private int count = 0;
  15. //设置一个控制变量
  16. private boolean loop = true;
  17. @Override
  18. public void run() {
  19. while (loop) {
  20. try {
  21. Thread.sleep(50);// 让当前线程休眠50ms
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println("T 运行中...." + (++count));
  26. }
  27. }
  28. public void setLoop(boolean loop) {
  29. this.loop = loop;
  30. }
  31. }

5. 线程常用方法

常用方法第一组

  1. setName 设置线程名称,使之与参数name相同

  2. getName 返回该线程的名称

  3. start 使该线程开始执行;Java虚拟机底层调用该线程的 start0方法

  4. run调用线程对象run方法

  5. setPriority 更改线程的优先级

  6. getPriority 获取线程的优先级

  7. sleep 在指定的亳秒数内让当前正在执行的线程休眠(暂停执行)

  8. interrupt 中断线程

注意事项和细节

  1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程

  2. 线程优先级的范围

  3. interrupt ,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程

  4. sleep : 线程的静态方法,使当前线程休眠

应用案例

  1. public class ThreadMethod01 {
  2. public static void main(String[] args) throws InterruptedException {
  3. //测试相关的方法
  4. T t = new T();
  5. t.setName("老韩");
  6. t.setPriority(Thread.MIN_PRIORITY);//1
  7. t.start();//启动子线程
  8. //主线程打印5 hi ,然后我就中断 子线程的休眠
  9. for(int i = 0; i < 5; i++) {
  10. Thread.sleep(1000);
  11. System.out.println("hi " + i);
  12. }
  13. System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
  14. t.interrupt();//当执行到这里,就会中断 t线程的休眠.
  15. }
  16. }
  17. class T extends Thread { //自定义的线程类
  18. @Override
  19. public void run() {
  20. while (true) {
  21. for (int i = 0; i < 100; i++) {
  22. //Thread.currentThread().getName() 获取当前线程的名称
  23. System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);
  24. }
  25. try {
  26. System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
  27. Thread.sleep(20000);//20秒
  28. } catch (InterruptedException e) {
  29. //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
  30. //InterruptedException 是捕获到一个中断异常.
  31. System.out.println(Thread.currentThread().getName() + "被 interrupt了");
  32. }
  33. }
  34. }
  35. }

常用方法第二组

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼
    让的时间不确定,所以也不一定礼让成功
  2. join:线程的插队。插队的线程一旦插队成功,则肯定
    先执行完插入的线程所有的任务
  1. public class ThreadMethod02 {
  2. public static void main(String[] args) throws InterruptedException {
  3. T2 t2 = new T2();
  4. t2.start();
  5. for(int i = 1; i <= 20; i++) {
  6. Thread.sleep(1000);
  7. System.out.println("主线程(小弟) 吃了 " + i + " 包子");
  8. if(i == 5) {
  9. System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
  10. //join, 线程插队
  11. //t2.join();// 这里相当于让t2 线程先执行完毕
  12. Thread.yield();//礼让,不一定成功..
  13. System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
  14. }
  15. }
  16. }
  17. }
  18. class T2 extends Thread {
  19. @Override
  20. public void run() {
  21. for (int i = 1; i <= 20; i++) {
  22. try {
  23. Thread.sleep(1000);//休眠1秒
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("子线程(老大) 吃了 " + i + " 包子");
  28. }
  29. }
  30. }

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束 myDaemonThread.setDaemon(true);
  3. 常见的守护线程:垃圾回收机制

应用案例

  1. public class ThreadMethod03 {
  2. public static void main(String[] args) throws InterruptedException {
  3. MyDaemonThread myDaemonThread = new MyDaemonThread();
  4. //如果我们希望当main线程结束后,子线程自动结束
  5. //,只需将子线程设为守护线程即可
  6. myDaemonThread.setDaemon(true);
  7. myDaemonThread.start();
  8. for( int i = 1; i <= 10; i++) {//main线程
  9. System.out.println("宝强在辛苦的工作...");
  10. Thread.sleep(1000);
  11. }
  12. }
  13. }
  14. class MyDaemonThread extends Thread {
  15. public void run() {
  16. for (; ; ) {//无限循环
  17. try {
  18. Thread.sleep(1000);//休眠1000毫秒
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
  23. }
  24. }
  25. }

6. 线程的生命周期

JDK 中用 Thread.State 枚举表示了线程的几种状态

第17章 多线程基础 - 图4

线程状态转换图

第17章 多线程基础 - 图5

查看线程状态

  1. public class ThreadState_ {
  2. public static void main(String[] args) throws InterruptedException {
  3. T t = new T();
  4. System.out.println(t.getName() + " 状态 " + t.getState());
  5. t.start();
  6. while (Thread.State.TERMINATED != t.getState()) {
  7. System.out.println(t.getName() + " 状态 " + t.getState());
  8. Thread.sleep(500);
  9. }
  10. System.out.println(t.getName() + " 状态 " + t.getState());
  11. }
  12. }
  13. class T extends Thread {
  14. @Override
  15. public void run() {
  16. while (true) {
  17. for (int i = 0; i < 10; i++) {
  18. System.out.println("hi " + i);
  19. try {
  20. Thread.sleep(1000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. break;
  26. }
  27. }
  28. }

7. 线程的同步

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

同步具体方法-Synchronized

  1. 同步代码块
    1. synchronized(对象){
    2. //得到对象的锁,才能操作同步代码
    3. //需要被同步代码;
    4. }
  1. synchronized还可以放在方法声明中,表示整个方法-为同步方法
  1. public synchronized void m(String name){
  2. //需要被同步的代码
  3. }
  1. 如何理解:
    就好像某小伙伴上厕所前先把门关上(上锁)完事后再出来
    解锁),那么其它小伙伴就可在使用厕所了

  2. 使用 synchronized解决售票问题

分析同步原理

第17章 多线程基础 - 图6

8. 互斥锁

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  3. 关键字 synchronized来与对象的互斥锁联系。当某个对象用 synchronized 修饰时 ,表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性 : 导致程序的执行效率要降低
  5. 同步方法 ( 非静态的 ) 的锁可以是this,也可以是其他对象 ( 要求是同一个对象 )
  6. 同步方法 ( 静态的 ) 的锁为当前类本身。

使用互斥锁来解决售票问题

  1. public class SellTicket {
  2. public static void main(String[] args) {
  3. //测试
  4. // SellTicket01 sellTicket01 = new SellTicket01();
  5. // SellTicket01 sellTicket02 = new SellTicket01();
  6. // SellTicket01 sellTicket03 = new SellTicket01();
  7. //
  8. // //这里我们会出现超卖..
  9. // sellTicket01.start();//启动售票线程
  10. // sellTicket02.start();//启动售票线程
  11. // sellTicket03.start();//启动售票线程
  12. // System.out.println("===使用实现接口方式来售票=====");
  13. // SellTicket02 sellTicket02 = new SellTicket02();
  14. //
  15. // new Thread(sellTicket02).start();//第1个线程-窗口
  16. // new Thread(sellTicket02).start();//第2个线程-窗口
  17. // new Thread(sellTicket02).start();//第3个线程-窗口
  18. //测试一把
  19. SellTicket03 sellTicket03 = new SellTicket03();
  20. new Thread(sellTicket03).start();//第1个线程-窗口
  21. new Thread(sellTicket03).start();//第2个线程-窗口
  22. new Thread(sellTicket03).start();//第3个线程-窗口
  23. }
  24. }
  25. //实现接口方式, 使用synchronized实现线程同步
  26. class SellTicket03 implements Runnable {
  27. private int ticketNum = 100;//让多个线程共享 ticketNum
  28. private boolean loop = true;//控制run方法变量
  29. Object object = new Object();
  30. //同步方法(静态的)的锁为当前类本身
  31. //老韩解读
  32. //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
  33. //2. 如果在静态方法中,实现一个同步代码块.
  34. /*
  35. synchronized (SellTicket03.class) {
  36. System.out.println("m2");
  37. }
  38. */
  39. public synchronized static void m1() {
  40. }
  41. public static void m2() {
  42. synchronized (SellTicket03.class) {
  43. System.out.println("m2");
  44. }
  45. }
  46. //老韩说明
  47. //1. public synchronized void sell() {} 就是一个同步方法
  48. //2. 这时锁在 this对象
  49. //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
  50. public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
  51. synchronized (/*this*/ object) {
  52. if (ticketNum <= 0) {
  53. System.out.println("售票结束...");
  54. loop = false;
  55. return;
  56. }
  57. //休眠50毫秒, 模拟
  58. try {
  59. Thread.sleep(50);
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
  64. + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
  65. }
  66. }
  67. @Override
  68. public void run() {
  69. while (loop) {
  70. sell();//sell方法是一共同步方法
  71. }
  72. }
  73. }
  74. //使用Thread方式
  75. // new SellTicket01().start()
  76. // new SellTicket01().start();
  77. class SellTicket01 extends Thread {
  78. private static int ticketNum = 100;//让多个线程共享 ticketNum
  79. // public void m1() {
  80. // synchronized (this) {
  81. // System.out.println("hello");
  82. // }
  83. // }
  84. @Override
  85. public void run() {
  86. while (true) {
  87. if (ticketNum <= 0) {
  88. System.out.println("售票结束...");
  89. break;
  90. }
  91. //休眠50毫秒, 模拟
  92. try {
  93. Thread.sleep(50);
  94. } catch (InterruptedException e) {
  95. e.printStackTrace();
  96. }
  97. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
  98. + " 剩余票数=" + (--ticketNum));
  99. }
  100. }
  101. }
  102. //实现接口方式
  103. class SellTicket02 implements Runnable {
  104. private int ticketNum = 100;//让多个线程共享 ticketNum
  105. @Override
  106. public void run() {
  107. while (true) {
  108. if (ticketNum <= 0) {
  109. System.out.println("售票结束...");
  110. break;
  111. }
  112. //休眠50毫秒, 模拟
  113. try {
  114. Thread.sleep(50);
  115. } catch (InterruptedException e) {
  116. e.printStackTrace();
  117. }
  118. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
  119. + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
  120. }
  121. }
  122. }

注意事项和细节

  1. 同步方法如果没有使用 static修饰:默认锁对象为this
  2. 如果方法使用 static修饰,默认锁对象当前类cass
  3. 实现的落地步骤:

    • 需要先分析上锁的代码
    • 选择同步代码块或同步方法
    • 要求多个线程的锁对象为同一个即可!

9. 线程的死锁

基本介绍

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁在编程是一定要避免死锁的发生

应用案例

  1. public class DeadLock_ {
  2. public static void main(String[] args) {
  3. //模拟死锁现象
  4. DeadLockDemo A = new DeadLockDemo(true);
  5. A.setName("A线程");
  6. DeadLockDemo B = new DeadLockDemo(false);
  7. B.setName("B线程");
  8. A.start();
  9. B.start();
  10. }
  11. }
  12. //线程
  13. class DeadLockDemo extends Thread {
  14. static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
  15. static Object o2 = new Object();
  16. boolean flag;
  17. public DeadLockDemo(boolean flag) {//构造器
  18. this.flag = flag;
  19. }
  20. @Override
  21. public void run() {
  22. //下面业务逻辑的分析
  23. //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
  24. //2. 如果线程A 得不到 o2 对象锁,就会Blocked
  25. //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
  26. //4. 如果线程B 得不到 o1 对象锁,就会Blocked
  27. if (flag) {
  28. synchronized (o1) {//对象互斥锁, 下面就是同步代码
  29. System.out.println(Thread.currentThread().getName() + " 进入1");
  30. synchronized (o2) { // 这里获得li对象的监视权
  31. System.out.println(Thread.currentThread().getName() + " 进入2");
  32. }
  33. }
  34. } else {
  35. synchronized (o2) {
  36. System.out.println(Thread.currentThread().getName() + " 进入3");
  37. synchronized (o1) { // 这里获得li对象的监视权
  38. System.out.println(Thread.currentThread().getName() + " 进入4");
  39. }
  40. }
  41. }
  42. }
  43. }

10. 释放锁

下面操作会释放锁

  1. 当前线程的同步方法、同步代码块执行结束
    案例:上厕所,完事出来
  2. 当前线程在同步代码块、同步方法中遇到 break、 return
    案例:没有正常的完事,经理叫他修改bug,不得已出来
  3. 当前线程在同步代码块、同步方法中出现了未处理的Eror或 Exception,导致异常结束
    案例:没有正常的完事,发现忘带纸,不得已出来
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait0方法,当前线程暂停,并释
    案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

下面操作不会释放锁

  1. 线程执行同步代码块或同步方法时,程序调用 Thread. sleep()、 Thread yield() 方法暂停当前线程的执行,不会释放锁
    案例:上厕所,太困了,在坑位上眯了一会
  2. 线程执行同步代码块时,其他线程调用了该线程的 suspend 方法将该线程挂起,该线程不会释放锁
    提示 : 应尽量避兔使用 suspend() 和 resume() 来控制线程,方法不再推荐使用

11. 作业

第17章 多线程基础 - 图7

  1. public class Homework01 {
  2. public static void main(String[] args) {
  3. A a = new A();
  4. B b = new B(a);//一定要注意.
  5. a.start();
  6. b.start();
  7. }
  8. }
  9. //创建A线程类
  10. class A extends Thread {
  11. private boolean loop = true;
  12. @Override
  13. public void run() {
  14. //输出1-100数字
  15. while (loop) {
  16. System.out.println((int)(Math.random() * 100 + 1));
  17. //休眠
  18. try {
  19. Thread.sleep(1000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. System.out.println("a线程退出...");
  25. }
  26. public void setLoop(boolean loop) {//可以修改loop变量
  27. this.loop = loop;
  28. }
  29. }
  30. //直到第2个线程从键盘读取了“Q”命令
  31. class B extends Thread {
  32. private A a;
  33. private Scanner scanner = new Scanner(System.in);
  34. public B(A a) {//构造器中,直接传入A类对象
  35. this.a = a;
  36. }
  37. @Override
  38. public void run() {
  39. while (true) {
  40. //接收到用户的输入
  41. System.out.println("请输入你指令(Q)表示退出:");
  42. char key = scanner.next().toUpperCase().charAt(0);
  43. if(key == 'Q') {
  44. //以通知的方式结束a线程
  45. a.setLoop(false);
  46. System.out.println("b线程退出.");
  47. break;
  48. }
  49. }
  50. }
  51. }

第17章 多线程基础 - 图8

  1. public class Homework02 {
  2. public static void main(String[] args) {
  3. T t = new T();
  4. Thread thread1 = new Thread(t);
  5. thread1.setName("t1");
  6. Thread thread2 = new Thread(t);
  7. thread2.setName("t2");
  8. thread1.start();
  9. thread2.start();
  10. }
  11. }
  12. //编程取款的线程
  13. //1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
  14. //2. 每次取出 1000
  15. class T implements Runnable {
  16. private int money = 10000;
  17. @Override
  18. public void run() {
  19. while (true) {
  20. //解读
  21. //1. 这里使用 synchronized 实现了线程同步
  22. //2. 当多个线程执行到这里时,就会去争夺 this对象锁
  23. //3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
  24. //4. 争夺不到this对象锁,就blocked ,准备继续争夺
  25. //5. this对象锁是非公平锁.
  26. synchronized (this) {//
  27. //判断余额是否够
  28. if (money < 1000) {
  29. System.out.println("余额不足");
  30. break;
  31. }
  32. money -= 1000;
  33. System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
  34. }
  35. //休眠1s
  36. try {
  37. Thread.sleep(1000);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }
  43. }