多线程

1.进程

进行中的应用程序,只有一个应用程序处于运行状态,才能被称之为进程。 应用程序的执行实例,有独立的内存空间和系统资源。

2. 线程

CPU执行的最小单位,包含在进程之中。 CPU调度和分派的基本单位,应用程序运算是最小单位。

3. 进程和线程的关系

进程和线程的关系,就像车身和车轮的关系,不是越多越好,要根据实际的硬件环境,选择最合适的数量,才是最优方案。

4. CPU和线程的执行

单核心CPU下,多线程是轮流交替执行。每个任务最多执行20ms,每个任务准备好以后,会向CPU发出指令:准备好了 真正是否能够被执行 不确定 因为同时可能有很多线程都准备好了

5. 并发和并行

并发是指同时发生,轮流交替来执行 并行是真正意义 上的同时执行

6. 线程的创建

6.1 继承Thread类

线程的创建方式1:继承Thread类 重写run方法

  1. package com.qfedu.test2;
  2. /**
  3. * 线程的创建方式1:继承Thread类 重写run方法
  4. * @author WHD
  5. *
  6. */
  7. public class T1 extends Thread{
  8. @Override
  9. public void run() {
  10. System.out.println(Thread.currentThread().getName());
  11. }
  12. public static void main(String[] args) {
  13. T1 t1 = new T1();
  14. t1.setName("线程A");
  15. t1.start(); // 调用start方法才会开启新的线程
  16. // t1.run(); 调用run方法不会开启新的线程 依然使用main线程调用run方法
  17. }
  18. }

6.2 实现Runnable接口

创建线程方式2 :实现Runnable接口 重写run方法

  1. package com.qfedu.test2;
  2. /**
  3. * 创建线程方式2 :实现Runnable接口 重写run方法
  4. * @author WHD
  5. *
  6. */
  7. public class T2 implements Runnable{
  8. @Override
  9. public void run() {
  10. System.out.println(Thread.currentThread().getName());
  11. }
  12. public static void main(String[] args) {
  13. T2 t2 = new T2();
  14. Thread thread = new Thread(t2, "线程A");
  15. thread.start();
  16. }
  17. }

6.3 两种方式对比

继承Thread类 编写简单,可直接操作线程 适用于单继承 实现Runnable接口 避免单继承局限性 便于共享资源 推荐使用实现Runnable接口方式创建线程

7. 线程的状态

线程的状态 创建—就绪—运行—阻塞—死亡

  1. package com.qfedu.test3;
  2. /**
  3. * 线程的状态
  4. * 创建--就绪--运行--阻塞--死亡
  5. * @author WHD
  6. *
  7. */
  8. public class T1 extends Thread{
  9. @Override
  10. public void run() { // 运行
  11. try {
  12. Thread.sleep(2000); // 阻塞
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName());
  17. }
  18. // 死亡
  19. public static void main(String[] args) {
  20. T1 t1 = new T1(); // 创建
  21. t1.setName("线程A");
  22. t1.start(); // 就绪
  23. }
  24. }

8. 线程的优先级

线程的优先级 默认为5 最低为1 最高为10 优先级高代表获取CPU的概率较大 并不能保证一定会优先获得CPU资源 MAX_PRIORITY 最高10 MIN_PRIORITY 最低1 NORM_PRIORITY 默认5

  1. package com.qfedu.test4;
  2. /**
  3. * 线程的优先级 默认为5 最低为1 最高为10
  4. * 优先级高代表获取CPU的概率较大 并不能保证一定会优先获得CPU资源
  5. * @author WHD
  6. *
  7. */
  8. public class T1 extends Thread{
  9. @Override
  10. public void run() {
  11. for (int i = 0; i < 10; i++) {
  12. System.out.println(Thread.currentThread().getName() + i);
  13. }
  14. }
  15. public static void main(String[] args) {
  16. T1 thread1 = new T1();
  17. thread1.setName("赵四");
  18. T1 thread2 = new T1();
  19. thread2.setName("广坤");
  20. System.out.println(thread1.getPriority());
  21. System.out.println(thread2.getPriority());
  22. thread1.setPriority(Thread.MAX_PRIORITY); // 设置优先级为最高 10
  23. thread2.setPriority(1);// 设置优先级为最低 1
  24. thread1.start();
  25. thread2.start();
  26. }
  27. }

9. 线程的休眠

线程休眠的方法 sleep(long mills)

  1. package com.qfedu.test5;
  2. /**
  3. * 线程休眠的方法 sleep(long mills)
  4. *
  5. * @author WHD
  6. *
  7. */
  8. public class T1 extends Thread{
  9. @Override
  10. public void run() {
  11. try {
  12. Thread.sleep(3000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName());
  17. }
  18. public static void main(String[] args) {
  19. T1 t1 = new T1();
  20. t1.setName("线程A");
  21. t1.start();
  22. }
  23. }

10. 线程的插队

线程的插队 join() 等待插队线程执行完毕 再执行当前线程 join(long mills) 等待插队线程固定时间 时间结束就执行当前线程

  1. package com.qfedu.test5;
  2. /**
  3. * 线程的插队
  4. * join() 等待插队线程执行完毕 再执行当前线程
  5. * join(long mills) 等待插队线程固定时间 时间结束就执行当前线程
  6. * @author WHD
  7. *
  8. */
  9. public class T2 extends Thread{
  10. @Override
  11. public void run() {
  12. for (int i = 0; i < 50; i++) {
  13. try {
  14. Thread.sleep(100);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(Thread.currentThread().getName() + i);
  19. }
  20. }
  21. public static void main(String[] args) throws InterruptedException {
  22. T2 t2 = new T2();
  23. t2.setName("--------线程A--------");
  24. t2.start();
  25. for (int i = 0; i < 20; i++) {
  26. if(i == 10) {
  27. t2.join(1000);
  28. }
  29. System.out.println(Thread.currentThread().getName() + i);
  30. }
  31. }
  32. }

11. 线程的礼让

yield()方法 线程礼让 当前线程礼让其他线程 让其他线程先执行 只是提供一种可能 不一定会礼让

  1. package com.qfedu.test6;
  2. /**
  3. * yield()方法 线程礼让 当前线程礼让其他线程 让其他线程先执行
  4. * 只是提供一种可能 不一定会礼让
  5. * @author WHD
  6. *
  7. */
  8. public class T1 extends Thread{
  9. @Override
  10. public void run() {
  11. for (int i = 0; i < 20; i++) {
  12. try {
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. if(i == 5) {
  18. Thread.yield();
  19. System.out.println(Thread.currentThread().getName() + "礼让了------------------");
  20. }
  21. System.out.println(Thread.currentThread().getName() + i);
  22. }
  23. }
  24. public static void main(String[] args) {
  25. T1 thread1 = new T1();
  26. T1 thread2 = new T1();
  27. thread1.setName("线程A");
  28. thread2.setName("线程B");
  29. thread1.start();
  30. thread2.start();
  31. }
  32. }

12.线程的中断

interrupt() 中断线程方法 但不是立即中断 如果当前线程有未执行完的任务 将执行完毕再中断 interrupted() 查看当前线程是否可以被中断 true为是 false为否 stop() 为立即中断线程的方法

  1. package com.qfedu.test6;
  2. /**
  3. * interrupt() 中断线程方法 但不是立即中断 如果当前线程有未执行完的任务 将执行完毕再中断
  4. * interrupted() 查看当前线程是否可以被中断 true为是 false为否
  5. * stop() 为立即中断线程的方法
  6. * @author WHD
  7. *
  8. */
  9. public class T2 extends Thread{
  10. @Override
  11. public void run() {
  12. for (int i = 0; i < 20; i++) {
  13. if(i == 10) {
  14. System.out.println("执行了线程中断方法interrupt()");
  15. Thread.currentThread().interrupt();
  16. // boolean flag = Thread.interrupted();
  17. // System.out.println(flag);
  18. }
  19. System.out.println(Thread.currentThread().getName() + i);
  20. }
  21. }
  22. public static void main(String[] args) throws InterruptedException {
  23. T2 t2 = new T2();
  24. t2.setName("线程A");
  25. t2.start();
  26. Thread.sleep(2000);
  27. System.out.println(t2.isAlive()); // 查看t2线程是否存活
  28. }
  29. }

13.线程安全

多线程共享数据引发的问题

  1. package com.qfedu.test8;
  2. /**
  3. * 因为T1类中count是实例类型的 所以每new一个对象 就存在一份拷贝 有三个count = 10
  4. * 所以我们将count改为static修饰的
  5. * 修改完以后:
  6. * 1.同一张票重复卖出
  7. * 2.在run方法中添加线程休眠 发现票的数量有误
  8. * @author WHD
  9. *
  10. */
  11. public class T2 extends Thread{
  12. private static int count = 10;
  13. @Override
  14. public void run() {
  15. while(count > 0) {
  16. try {
  17. Thread.sleep(500);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. count--;
  22. System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - count)+" 张票,还剩余" + count + "张票");
  23. }
  24. }
  25. public static void main(String[] args) {
  26. T2 zhaosi = new T2();
  27. T2 guangkun = new T2();
  28. T2 dana = new T2();
  29. zhaosi.setName("赵四");
  30. guangkun.setName("广坤");
  31. dana.setName("大拿");
  32. zhaosi.start();
  33. guangkun.start();
  34. dana.start();
  35. }
  36. }

解决方案:使用同步代码块

  1. package com.qfedu.test8;
  2. /**
  3. *
  4. * @author WHD
  5. *
  6. */
  7. public class T4 implements Runnable{
  8. int count = 10;
  9. @Override
  10. public void run() {
  11. while(true ) {
  12. try {
  13. Thread.sleep(500);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. synchronized(this) {
  18. if(count == 0) {
  19. break;
  20. }
  21. count--;
  22. System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - count)+" 张票,还剩余" + count + "张票");
  23. }
  24. }
  25. // 后续代码
  26. }
  27. public static void main(String[] args) {
  28. T4 t3 = new T4();
  29. Thread zhaosi = new Thread(t3, "赵四");
  30. Thread guangkun = new Thread(t3, "广坤");
  31. Thread dana = new Thread(t3, "大拿");
  32. zhaosi.start();
  33. guangkun.start();
  34. dana.start();
  35. }
  36. }

使用同步方法实现

  1. package com.qfedu.test8;
  2. /**
  3. * synchronized修饰方法 表示此方法同一时间只能有一个线程访问
  4. * @author WHD
  5. *
  6. */
  7. public class T5 implements Runnable{
  8. int count = 10;
  9. @Override
  10. public synchronized void run() {
  11. while(count > 0) {
  12. try {
  13. Thread.sleep(500);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. count--;
  18. System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - count)+" 张票,还剩余" + count + "张票");
  19. }
  20. // 后续代码
  21. }
  22. public static void main(String[] args) {
  23. T5 t3 = new T5();
  24. Thread zhaosi = new Thread(t3, "赵四");
  25. Thread guangkun = new Thread(t3, "广坤");
  26. Thread dana = new Thread(t3, "大拿");
  27. zhaosi.start();
  28. guangkun.start();
  29. dana.start();
  30. }
  31. }

14.synchronized关键字规则

同一时刻只能有一个线程进入synchronized(this)同步代码块

当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定

当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码