4种线程创建方式,3种线程安全问题。

image.png

基本概念:程序、进程(资源分配单位)、线程(最小调度和执行单位)

image.png

从JVM看 “进程 、线程”

每个线程都有独立的虚拟机栈和程序计数器(最小调度与执行), 而每个进程都独立的方法区和堆(资源分配),且进程的资源被其内部的所有线程所共享(共享也导致了安全隐患)。
image.png

CPU单核多核: Java.exe至少有3个线程、并行与并发

image.png

多线程优点:不管单多核, 都可以同时(并发\并行)做更多事、提高资源利用率。

image.png
image.png

何时需要多线程:进程需要子任务、IO等待频繁、后台运行。

(仅目前)两种实现多线程方式:1、继承Thread类,2、实现Runable接口(优先选择:没有类的单继承局限、多线程间方便共享数据)

image.png

方式一:继承Thread类:重写run方法,再调用实例的start方法。

  1. new Thread(){
  2. @Override
  3. public void run(){ ... } // 1.继承并重写Thread类的run方法
  4. }.start(); // 2.调用该实例的start方法

该实例的start方法有两个作用:1启动一个新进程 2. jvm会用这个新进程去执行该实例的run() 。
问题1:不能直接调用该实例的run(),只是单纯方法的调用,并没有启用新线程。
问题2: 不能重复调用实例的start(), 因为每个实例只能启动一个新线程(内部实现有个计数器)。 不过,重新创建实例即可。

方式二:实现Runable接口: 实现run() ,然后new Thread(p).start()

image.png

  1. public class Hello {
  2. public static void main(String[] args) {
  3. Runnable p = new Runnable() {
  4. private int total = 10;
  5. @Override
  6. public void run() {
  7. while (total > 0) {
  8. System.out.println(Thread.currentThread().getName() + ":" + total--);
  9. }
  10. }
  11. };
  12. new Thread(p).start();
  13. new Thread(p).start();
  14. }
  15. }

Thread类:常用方法、线程优先级(概率,而非次序)

image.png
yield() 主动从运行状态回到就绪状态。比如爬虫,爬数据的进程已经爬到一节数据了,就可以yield(),以便让其他进程取走这部分数据。
join(): 比如刷微博,当一直向下刷时,已经没有内容展示了,那么“内容展示线程”主动调用“加载数据线程”的join方法, 此时“内容展示线程”阻塞,直到加载数据的线程执行完,“内容展示线程”就绪。 简而言之:join()就像有特定目的的阻塞一样,当达到目的就冲阻塞回到就绪状态。

补充1:线程通信的三个方法定义在Object类中:wait() ,notigy() ,notifyAll()
补充2: 线程分为守护线程和用户线程,守护线程依赖用户线程而存活。

  • Thread类线程优先级: 优先级不是次序,而是概率。

image.png

线程的生命周期(重要)

image.png

线程安全问题: 由共享数据带来的安全问题、线程同步机制(三种:同步代码块、同步方法、Lock)

解决方案一:同步代码块

(缺点1:效率低,同步代码块内部是相当于单线程。缺点2:不方便,共用一把锁)

  1. 需要被同步的代码块:操作共享数据的代码块。
  2. 同步监视器:俗称“锁”,任何类的实例都可以充当锁,比如new Object()。 但要求,多个线程共用同一把锁。

下面是两个窗口售票的例子:

  1. public class Hello {
  2. public static void main(String[] args) {
  3. Runnable p = new Runnable() {
  4. private int total = 100;
  5. Object key = new Object(); // 共用一把锁
  6. @Override
  7. public void run() {
  8. while (true) {
  9. // 下面代码块操作了共享数据total,故它就是需要被同步的代码块。
  10. // 特别地,有时候可以使用this本身来作为锁
  11. // synchronized (key) {
  12. // 更方便地,类本身就是唯一的对象,可以作为锁。
  13. // synchronized (this) {
  14. synchronized (Hello.class) {
  15. if (total < 0)
  16. break;
  17. System.out.println();
  18. try {
  19. Thread.sleep(10);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread().getName() + ":" + total--);
  24. }
  25. }
  26. }
  27. };
  28. new Thread(p).start();
  29. new Thread(p).start();
  30. }
  31. }

特别注意:对一个实例并发时,可以使用this本身来作为锁。或者,使用类名.class来作为锁,因为在java中类本身也是对象,且它是唯一的。

强调:类本身也是对象。

解决方案二:同步方法: 共享数据被一个方法所全部包含,则该方法可以声明为同步方法。(同步方法其实等价于同步代码块,只是不过同步监视器由JVM隐式为this 或者 类本身)(缺点:如同步的代码块的缺点,即效率低,互斥内部依然为“单线程”)、(非)静态同步方法。

  • 非静态同步方法:同步监视器为this,仅处理一个实例并发情况。
  • 静态同步方法和静态共享变量:同步监视器为类,处理该类的并发情况。 ```java // 非静态同步方法:同步监视器为this,仅处理一个实例并发情况。 class Sale implements Runnable { private int total = 100;

    @Override public void run() {

    1. while (true) {
    2. sales();
    3. }

    }

    private synchronized void sales() {

    1. if (total < 0)
    2. return;
    3. System.out.println();
    4. try {
    5. Thread.sleep(10);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. System.out.println(Thread.currentThread().getName() + ":" + total--);

    }

    public static void main(String[] args) {

    1. Runnable p = new Sale();
    2. new Thread(p).start();
    3. new Thread(p).start();

    }

}

// 静态同步方法:同步监视器为类,处理该类的并发情况。 class Sale implements Runnable { private static int total = 100; // 1. 共享变量static

  1. @Override
  2. public void run() {
  3. while (true) {
  4. sales();
  5. }
  6. }
  7. private static synchronized void sales() { // 同步方法static
  8. if (total < 0)
  9. return;
  10. System.out.println();
  11. try {
  12. Thread.sleep(10);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName() + ":" + total--);
  17. }
  18. public static void main(String[] args) {
  19. Runnable p1 = new Sale();
  20. Runnable p2 = new Sale();
  21. new Thread(p1).start();
  22. new Thread(p2).start();
  23. }

}

  1. <a name="U9nUW"></a>
  2. ### 多线程线程安全——单例模式
  3. ```java
  4. public class Instance {
  5. public static Instance instance = null;
  6. public static Instance getInstance() {
  7. // synchronized (Instance.class) {
  8. // // 方式一:效率低:所有之后的线程都要进行排队检查是否为null
  9. // if (instance == null) {
  10. // instance = new Instance();
  11. // }
  12. // return instance;
  13. // }
  14. if (instance == null) {
  15. // 方式二:效率高,稍之后的线程都要不再需要为检查null而排队。
  16. synchronized (Instance.class) {
  17. if (instance == null) {
  18. instance = new Instance();
  19. }
  20. }
  21. }
  22. return instance;
  23. }
  24. }

死锁

image.png

线程安全方式三:Lock锁 — jdk5.0以上、new ReentrantLock()、lock()、unlock()

疑问🤔️:下面代码只实例化了一个Runable实例,如果不同的Runable实例如何拿到同一个lock,并同步共享资源呢?

  1. class Sale implements Runnable {
  2. private int total = 100;
  3. // 1.同一个Lock实例
  4. private ReentrantLock lock = new ReentrantLock();
  5. @Override
  6. public void run() {
  7. while (true) {
  8. // 2. 加锁
  9. lock.lock();
  10. if (total < 0)
  11. return;
  12. System.out.println();
  13. try {
  14. Thread.sleep(10);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(Thread.currentThread().getName() + ":" + total--);
  19. // 3. 解锁
  20. lock.unlock();
  21. }
  22. }
  23. }
  24. public class Test {
  25. public static void main(String[] args) {
  26. Runnable p = new Sale();
  27. new Thread(p).start();
  28. new Thread(p).start();
  29. }
  30. }

线程安全建议顺序: Lock > 同步代码块 > 同步方法

image.png

线程通信:wait、notify、notifyAll : 1. 这3个方法只能在同步代码块或同步方法中(Lock中有其他方式) 且调用者必须其中的同步监视器。2. wait()阻塞并释放锁。3. 这三个方法定义在Object类中(因为任何类都可以充当同步监视器,故任何类都可能需要它)

image.png

  1. // 两个线程交互打印
  2. class Sale implements Runnable {
  3. private int total = 100;
  4. private Object obj = new Object(); // 1.同步监视器
  5. @Override
  6. public void run() {
  7. while (true) {
  8. synchronized (obj) {
  9. obj.notifyAll(); // 2.自己先进入,调用同步监视器来唤醒其他进程
  10. if (total < 0)
  11. break;
  12. System.out.println();
  13. try {
  14. Thread.sleep(10);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(Thread.currentThread().getName() + ":" + total--);
  19. try {
  20. obj.wait();// 办完事情,调用同步监视器来进入阻塞并释放锁。
  21. } catch (InterruptedException e) {
  22. // TODO Auto-generated catch block
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }
  28. }
  29. public class Test {
  30. public static void main(String[] args) {
  31. Runnable p = new Sale();
  32. Thread job1 = new Thread(p);
  33. Thread job2 = new Thread(p);
  34. job1.setName("job1");
  35. job2.setName("job2");
  36. job1.start();
  37. job2.start();
  38. }
  39. }

面试题:sleep()与wait()异同

image.png

线程通信:生产者消费者问题

image.png

// 下面代码: 同步监视器为Clerk.class, 当生产过剩时生产者主动wait(), 当物料缺乏时消费者主动wait()。 且一旦开始生产,则消费者被唤醒,一旦开始消费,则生产者被唤醒。

  1. public class Product {
  2. public static void main(String[] args) {
  3. Clerk clerk = new Clerk();
  4. Thread producter = new Thread(new Producter(clerk));
  5. Thread customer = new Thread(new Custormer(clerk));
  6. producter.setPriority(Thread.MAX_PRIORITY);
  7. customer.setPriority(Thread.MIN_PRIORITY);
  8. producter.setName("生产者1:");
  9. customer.setName("消费者1:");
  10. producter.start();
  11. customer.start();
  12. }
  13. }
  14. class Clerk {
  15. private int num = 0;
  16. public void addNum() {
  17. this.setNum(this.getNum() + 1);
  18. }
  19. public int getNum() {
  20. return num;
  21. }
  22. public void setNum(int num) {
  23. this.num = num;
  24. }
  25. public void subNum() {
  26. this.setNum(this.getNum() - 1);
  27. }
  28. }
  29. // 同步监视器为Clerk.class, 生产者生产过剩时wait(),消费者物料缺乏时wait()。 一旦开始生产,则消费者唤醒,一旦开始消费,则生产者唤醒。
  30. class Producter implements Runnable {
  31. private Clerk clerk = null;
  32. private int i = 50;
  33. Producter(Clerk clerk) {
  34. this.clerk = clerk;
  35. }
  36. @Override
  37. public void run() {
  38. while (i-- > 0) {
  39. synchronized (Clerk.class) {
  40. Clerk.class.notify();
  41. if (clerk.getNum() >= 10)
  42. try {
  43. Clerk.class.wait();
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. System.out.println(Thread.currentThread().getName() + "即将生产第" + clerk.getNum() + "个产品");
  48. try {
  49. Thread.sleep(50);
  50. } catch (InterruptedException e1) {
  51. e1.printStackTrace();
  52. }
  53. clerk.addNum();
  54. }
  55. }
  56. }
  57. }
  58. class Custormer implements Runnable {
  59. private Clerk clerk = null;
  60. private int i = 50;
  61. Custormer(Clerk clerk) {
  62. this.clerk = clerk;
  63. }
  64. @Override
  65. public void run() {
  66. while (i-- > 0) {
  67. synchronized (Clerk.class) {
  68. if (clerk.getNum() <= 0)
  69. try {
  70. Clerk.class.wait();
  71. } catch (InterruptedException e) {
  72. e.printStackTrace();
  73. }
  74. Clerk.class.notify();
  75. System.out.println(Thread.currentThread().getName() + "即将消费第" + clerk.getNum() + "个产品");
  76. try {
  77. Thread.sleep(50);
  78. } catch (InterruptedException e1) {
  79. // TODO Auto-generated catch block
  80. e1.printStackTrace();
  81. }
  82. clerk.subNum();
  83. }
  84. }
  85. }
  86. }

jdk5.0新增-多线程创建之:实现Callable的call接口(有返回值、抛异常、支持范型)-需借助FutureTask类: FutureTask实现了Runnable的接口

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.FutureTask;
  3. class MyCallable implements Callable {
  4. // 1.实现Callable的call接口的类
  5. private int[] nums;
  6. MyCallable(int[] nums) {
  7. this.nums = nums;
  8. }
  9. public int getSum() {
  10. int sum = 0;
  11. for (int i : nums) {
  12. sum += i;
  13. System.out.println(Thread.currentThread().getName() + ":" + i);
  14. }
  15. return sum;
  16. }
  17. @Override
  18. public Integer call() throws Exception {
  19. return getSum();
  20. }
  21. }
  22. public class CallableTest {
  23. public static void main(String[] args) {
  24. // 2.创建Callable的接口实现类的实例
  25. MyCallable MyCallable1 = new MyCallable(new int[] { 1, 2, 3, 4, 5, 6, 7 });
  26. MyCallable MyCallable2 = new MyCallable(new int[] { 10, 11, 12, 13, 14, 15 });
  27. // 3.使用2中实例来创建FutureTask实例。
  28. FutureTask<Integer> futureTask1 = new FutureTask<Integer>(MyCallable1);
  29. FutureTask<Integer> futureTask2 = new FutureTask<Integer>(MyCallable2);
  30. // 4.使用3中的实例来创建Thread实例。并运行。
  31. Thread job1 = new Thread(futureTask1);
  32. Thread job2 = new Thread(futureTask2);
  33. // 5. 启动多线程
  34. job1.setName("job1:");
  35. job2.setName("job2:");
  36. job1.start();
  37. job2.start();
  38. // 6.若有必要,FutureTask获取call方法中的返回值
  39. try {
  40. Integer sum1 = futureTask1.get();
  41. Integer sum2 = futureTask2.get();
  42. System.out.println("sum1:" + sum1 + " sum2:" + sum2);
  43. } catch (Exception e) {
  44. // TODO: handle exception
  45. e.printStackTrace();
  46. }
  47. }
  48. }

jdk5.0新增-使用线程池(‼️是开发中 实际使用的方式)优点: 响应速度提高,资源重用率提高,关于管理。

image.png

image.png

下面代码:
image.png

多线程回顾:创建多线程共4种方式:继承Thread、实现Runnable接口、实现Callable接口、ThreadPool 。 线程安全(基于同步机制)共3种方式:同步代码块、同步方法、Lock锁。