在学习的时候,一定要注意学习顺序,使用3W1H的方式进行学习:

What:是什么? Who/When/Where : 应用场景 什么时候使用 在哪里使用 Why : 为什么要使用它 How: 内部原理

线程属性详解

属性名称 用途
编号(ID) 每个线程有自己的ID,用于标识不同的线程.被后续创建的线程使用,唯一性不能保证,不允许被修改.
名称(Name) 作用让用户或程序员在开发、调试或运行过程中,更容易区分每个不同的线程、定位问题等.
是否是守护线程(isDaemon) true代表该线程是[守护线程],false代表线程是非守护线程[用户线程].守护线程是服务用户线程的,而JVM退出是看用户线程的,如果用户线程都运行完毕了守护线程还有没有运行完毕,这时候JVM退出不会管守护线程是否运行完毕,直接强制退出.
优先级(Priority) 优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行、哪些少运行.默认和父线程的优先级相等,共有10个等级,默认值是5.在实际编码过程中,不推荐修改优先级.

思考

  • 什么时候需要设置守护线程
  • 如何应用线程优先级来帮助程序运行?有哪些禁忌
  • 不同的操作系统如何处理优先级问题?

线程id

每个线程都有自己的一个id,这个id是不能修改的.main函数就是第一个线程,id是自增的.直接上代码

  1. /**
  2. * ID从1开始,JVM运行起来后,自己创建的线程的ID早已不是0
  3. */
  4. public class Id {
  5. public static void main(String[] args) {
  6. Thread thread = new Thread();
  7. //主线程ID:1 为什么不是0呢 先++ 变成1了
  8. System.out.println("主线程ID"+Thread.currentThread().getId());
  9. //?? 子线程ID9 为什么到了ID9了呢?
  10. System.out.println("子线程ID"+thread.getId());
  11. }
  12. }

运行结果: 不知道大家有没有发现,主线的ID不是从0开始的,而且子线程的ID竟然变成了12 不是说好的ID是自增的吗?
image.png
不要慌,我们深入源码看一下,看到下面的代码,你肯定明白ID为什么不是从0开始的,因为它是先加1啊,所以主线程的ID就是1.

  1. /* For generating thread ID */
  2. private static long threadSeqNumber;
  3. private static synchronized long nextThreadID() {
  4. return ++threadSeqNumber;
  5. }

子线程ID为什么不是2,而是变成了其他的值呢? 我们DEBUG来看一下, 可以看到在启动程序的时候JVM早已经有多个thread初始化了.
image.png
有兴趣的可以执行查询了解一下

  • “Signal Dispatcher” 负责把操作系统的信号 发给适当的程序的
  • “Reference Handler” 和GC引用相关的线程
  • “Finalizer” 负责执行对象finalized的方法

也就是说当我们运行main()方法的时候,JVM不止创建了主线程还创建了很多辅助线程,这也就是说明我们创建的子线程的ID为什么变成了12,而不是从2开始.

线程名字

线程是如何命名的呢? 其实从Thread的构造方法中就可以看出来,我们打印出来的线程名称都是“Thread-XXX”对吧,其实还可以通过Thread的构造方法自定义线程的名称或者通过setName()方法自定义线程的名称.

  1. public Thread() {
  2. init(null, null, "Thread-" + nextThreadNum(), 0);
  3. }
  4. /* For autonumbering anonymous threads. */
  5. private static int threadInitNumber;
  6. private static synchronized int nextThreadNum() {
  7. return threadInitNumber++;
  8. }
  9. public final synchronized void setName(String name) {
  10. checkAccess();
  11. if (name == null) {
  12. throw new NullPointerException("name cannot be null");
  13. }
  14. this.name = name;
  15. if (threadStatus != 0) {
  16. setNativeName(name);
  17. }
  18. }

守护线程

守护线程是干什么的? 守护线程就是给用户线程提供服务. 三个特性: 线程类型默认继承自父线程 被谁启动,通常所有的守护线程是由JVM启动 不影响JVM退出,JVM退出只看用户线程,不看守护线程.

守护线程和普通线程的区别

  • 整体无区别
  • 唯一区别在于JVM的离开
  • 守护线程是服务普通线程的[用户线程]

用户线程会影响JVM的退出,而守护线程不会影响JVM退出.JVM退出的时候不会关心守护线程是否关闭了,JVM退出的时候会强制关闭守护线程
一般在开发中是不需要把自己的线程设置为守护线程.

线程优先级

10个级别,默认是5. :::tips 注意:程序设计不应依赖于优先级,因为不同的操作系统不一样,高度依赖操作系统.优先级会被操作系统改变.在实际编码过程中不推荐修改优先级. :::

  1. /**
  2. * The minimum priority that a thread can have.
  3. */
  4. public final static int MIN_PRIORITY = 1;
  5. /**
  6. * The default priority that is assigned to a thread.
  7. */
  8. public final static int NORM_PRIORITY = 5;
  9. /**
  10. * The maximum priority that a thread can have.
  11. */
  12. public final static int MAX_PRIORITY = 10;

未捕获异常如何处理

思考?

  • Java异常体系图
  • 在实际工作中,如何全局处理异常?为什么要全局处理?不处理会怎么样?

Java异常体系图 如下:
image.png

UncaugheException异常通过UncaughtExceptionHandler处理.
为什么需要UncaughtExceptionHandler?

  • 主线程可以轻松发现异常,子线程却不行,即便子线程抛出异常,但是主线程还是在正常运行,无法发现子线程出现异常

通过下面代码演示:

  1. /**
  2. * 单线程:抛出 处理 有异常堆栈
  3. * 多线程:子线程发生异常 会有什么不同
  4. */
  5. public class ExceptionInChildThread implements Runnable{
  6. public static void main(String[] args) {
  7. new Thread(new ExceptionInChildThread()).start();
  8. //对主线程而言 没有任何影响 子线程可以正常打印出异常
  9. for (int i = 0; i < 1000; i++) {
  10. System.out.println(i);
  11. }
  12. }
  13. @Override
  14. public void run() {
  15. //线程抛出异常
  16. throw new RuntimeException();
  17. }
  18. }

image.png

  • 子线程异常无法用传统的方法捕获

如下代码,通过try/catch包裹线程代码,无法捕捉到异常.

  1. package com.prim.threadcoreknowledge.uncaughtexception;
  2. /**
  3. * 1. 不加try catch抛出4个异常,都带线程名字
  4. * 2. 加了try catch 期望捕获到第一个线程的异常,线程234不应该运行,希望看到打印出Caught Exception
  5. * 3. 执行结果发现:根本没有Caught Exception 线程234依然运行并且抛出异常
  6. * <p>
  7. * 说明线程的异常不能用传统方法捕获
  8. */
  9. public class CantCatchDirectly implements Runnable {
  10. public static void main(String[] args) throws InterruptedException {
  11. try {
  12. new Thread(new CantCatchDirectly(), "MyThread-1").start();
  13. Thread.sleep(300);
  14. new Thread(new CantCatchDirectly(), "MyThread-2").start();
  15. Thread.sleep(300);
  16. new Thread(new CantCatchDirectly(), "MyThread-3").start();
  17. Thread.sleep(300);
  18. new Thread(new CantCatchDirectly(), "MyThread-4").start();
  19. } catch (RuntimeException e) {
  20. System.out.println("Caught Exception");
  21. }
  22. }
  23. @Override
  24. public void run() {
  25. throw new RuntimeException();
  26. }
  27. }

image.png

  • 不能直接捕获的后果、提高健壮性

那么到底要如何捕捉子线程异常呢?

两种解决方案

  • (不推荐):手动在每个run方法里进行try/catch
  • (推荐):利用UncaughtExceptionHandler

如下UncaughtExceptionHandler是一个接口 通过uncaughtException() 方法可以拿到线程和异常.

  1. @FunctionalInterface
  2. public interface UncaughtExceptionHandler {
  3. /**
  4. * Method invoked when the given thread terminates due to the
  5. * given uncaught exception.
  6. * <p>Any exception thrown by this method will be ignored by the
  7. * Java Virtual Machine.
  8. * @param t the thread
  9. * @param e the exception
  10. */
  11. void uncaughtException(Thread t, Throwable e);
  12. }

下面看线程异常处理的调用策略 ThreadGroup

  1. public
  2. class ThreadGroup implements Thread.UncaughtExceptionHandler {
  3. public void uncaughtException(Thread t, Throwable e) {
  4. //默认情况下parent是null
  5. if (parent != null) {
  6. parent.uncaughtException(t, e);
  7. } else {
  8. //调用Thread.setDefaultUncaughtExceptionHandler() 方法设置的
  9. //全局handler进行处理
  10. Thread.UncaughtExceptionHandler ueh =
  11. Thread.getDefaultUncaughtExceptionHandler();
  12. //如果取到的不为null 就是使用我们实现的接口方法调用
  13. if (ueh != null) {
  14. ueh.uncaughtException(t, e);
  15. } else if (!(e instanceof ThreadDeath)) {
  16. //全局 handler也不存在就输出异常栈
  17. System.err.print("Exception in thread \""
  18. + t.getName() + "\" ");
  19. e.printStackTrace(System.err);
  20. }
  21. }
  22. }
  23. }

如何实现呢?

  • 给程序统一设置

通过调用Thread的静态方法setDefaultUncaughtExceptionHandler 可以给所有的线程进行设置异常捕捉

  1. public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
  2. private String name;
  3. public MyUncaughtExceptionHandler(String name) {
  4. this.name = name;
  5. }
  6. @Override
  7. public void uncaughtException(Thread t, Throwable e) {
  8. Logger anonymousLogger = Logger.getAnonymousLogger();
  9. anonymousLogger.log(Level.WARNING, "线程异常,终止了" + t.getName(), e);
  10. System.out.println(name + "捕获了异常" + t.getName() + "异常:" + e);
  11. }
  12. }
  13. public class UseUncaughtExceptionHandler implements Runnable {
  14. public static void main(String[] args) throws InterruptedException {
  15. Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));
  16. new Thread(new UseUncaughtExceptionHandler(),"MyThread-1").start();
  17. Thread.sleep(300);
  18. new Thread(new UseUncaughtExceptionHandler(),"MyThread-2").start();
  19. Thread.sleep(300);
  20. new Thread(new UseUncaughtExceptionHandler(),"MyThread-3").start();
  21. Thread.sleep(300);
  22. new Thread(new UseUncaughtExceptionHandler(),"MyThread-4").start();
  23. }
  24. @Override
  25. public void run() {
  26. throw new RuntimeException();
  27. }
  28. }

运行结果如下:捕获到了子线程的异常
image.png

UncaughtExceptionHandler 还可以给每个线程单独的设置以及给线程池设置.

  • 给每个线程单独设置
  • 给线程池设置

    多线程双刃剑:可能导致安全、性能问题

    思考:

  • 一共有哪几类线程安全问题(三种)?

  • 哪些场景需要额外注意线程安全问题?
  • 什么是多线程的上下文切换?

    线程安全

    线程安全是并发最重要的部分.什么是线程安全呢? 《Java 并发编程实战》Brian Goetz 对“线程安全”的一个描述: “当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的”. 也可以这样理解:“不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全”

什么情况下会出现线程安全问题,怎么避免?
主要有两个问题:

  1. 数据争用:数据读写由于同时写,会造成错误数据
  2. 竞争条件 执行顺序:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了
  • 运行结果错误:a++ 多线程下出现消失的请求现象

如下代码,两个线程同时执行a++的操作,那么理想得到的结果是20000.

  1. public class MultiThreadError8 implements Runnable {
  2. int index = 0;
  3. static MultiThreadError8 instance = new MultiThreadError8();
  4. public static void main(String[] args) throws InterruptedException {
  5. Thread thread1 = new Thread(instance);
  6. Thread thread2 = new Thread(instance);
  7. thread1.start();
  8. thread2.start();
  9. thread1.join();//等待线程1执行完毕
  10. thread2.join();//等待线程2执行完毕
  11. System.out.println(instance.index);
  12. }
  13. @Override
  14. public void run() {
  15. for (int i = 0; i < 10000; i++) {
  16. index++;
  17. }
  18. }
  19. }

但是得到的实际结果如下: 远远要小于20000,并且每次运行的结果还不一样,这是为什么呢?
image.png

i++ 在多个线程下多执行情况如下图分析: 两个线程去执行a++,我们期望的结果是3. 但是实际情况线程1
执行i=1 -> i+1 但是这时候并没有将结果赋值给i,线程2再去执行的时候拿到的结果还是i=1 -> i+1,这时候🈶️切换到线程1,此时线程1不知道线程2的步骤,执行i=2 将i=2写进去了,一旦切换到线程2,而线程2也不知道线程1 的情况,线程2执行i=2.

  1. 线程1 线程2<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/375694/1587304853276-63b7748c-5e10-4a2e-b962-cd77a59fb80b.png#align=left&display=inline&height=342&margin=%5Bobject%20Object%5D&name=image.png&originHeight=684&originWidth=1166&size=43497&status=done&style=none&width=583)

错误分析:
下面通过代码,来打印出错误的结果

  1. /**
  2. * 线程安全问题
  3. * 第一种:运行结果出错
  4. * 演示计数不准确(减少),找出具体出错的位置
  5. */
  6. public class MultiThreadsError implements Runnable {
  7. int index = 0;
  8. static MultiThreadsError instance = new MultiThreadsError();
  9. final boolean[] marked = new boolean[10000000];
  10. static AtomicInteger realIndex = new AtomicInteger();
  11. static AtomicInteger wrongCount = new AtomicInteger();
  12. //CyclicBarrier 让线程根据我们的需要在某个地方等待,直到所等待的人员都就绪了,在一起出发 parties :2 表示等待两个线程
  13. static volatile CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
  14. static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
  15. public static void main(String[] args) throws InterruptedException {
  16. Thread thread1 = new Thread(instance);
  17. Thread thread2 = new Thread(instance);
  18. thread1.start();
  19. thread2.start();
  20. thread1.join();//等待线程1执行完毕
  21. thread2.join();//等待线程2执行完毕
  22. System.out.println(instance.index);
  23. System.out.println("realIndex:" + realIndex.get());
  24. System.out.println("wrongCount:" + wrongCount.get());
  25. }
  26. @Override
  27. public void run() {
  28. marked[0] = true;
  29. for (int i = 0; i < 10000; i++) {
  30. try {
  31. cyclicBarrier2.reset();
  32. //标记等待点 当两个线程都运行到这个点之后 一起执行
  33. cyclicBarrier.await();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. } catch (BrokenBarrierException e) {
  37. e.printStackTrace();
  38. }
  39. index++;
  40. try {
  41. //等待两个线程都执行完了 index++操作 然后放行
  42. cyclicBarrier.reset();
  43. cyclicBarrier2.await();
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. } catch (BrokenBarrierException e) {
  47. e.printStackTrace();
  48. }
  49. realIndex.incrementAndGet();
  50. //但是存在问题 如果两个线程都执行完了index++ 操作这时候 synchronized只能有一个线程去执行
  51. //第一个线程执行 marked=true 而第二个线程index是一样的 执行的时候marked肯定为true 所以第二个线程肯定会打印
  52. //解决问题加入判断:marked[index - 1] 前一位是true
  53. //存在没有线程冲突的情况 很可能有没有被标记上。
  54. //比如 10 = true 11 = false 12 = true 正常的情况
  55. //错误情况 10 = true 11 = true 12
  56. synchronized (instance) {
  57. if (marked[index] && marked[index - 1]) {
  58. System.out.println("发生了错误:" + index);
  59. wrongCount.incrementAndGet();
  60. }
  61. marked[index] = true;
  62. }
  63. }
  64. }
  65. }

运行结果如下,可以揪出错误的位置,在哪里发生了错误,更有利于我们找到运行步骤和如何出错的.
image.png

  • 活跃性问题:死锁 活锁 饥饿

如下代码,会造成死锁的问题synchronized中还有一个synchronized

  1. package com.prim.background;
  2. import java.util.Map;
  3. /**
  4. * 第二个线程安全问题:死锁
  5. */
  6. public class MultiThreadError implements Runnable {
  7. int flag = 1;
  8. static Object o1 = new Object();
  9. static Object o2 = new Object();
  10. public static void main(String[] args) {
  11. MultiThreadError r1 = new MultiThreadError();
  12. MultiThreadError r2 = new MultiThreadError();
  13. r1.flag = 1;
  14. r2.flag = 0;
  15. new Thread(r1).start();
  16. new Thread(r2).start();
  17. }
  18. @Override
  19. public void run() {
  20. System.out.println("flag=" + flag);
  21. if (flag == 1) {
  22. synchronized (o1) {
  23. try {
  24. Thread.sleep(500);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. synchronized (o2) {
  29. System.out.println("1");
  30. }
  31. }
  32. }
  33. if (flag == 0) {
  34. synchronized (o2) {
  35. try {
  36. Thread.sleep(500);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. synchronized (o1) {
  41. System.out.println("0");
  42. }
  43. }
  44. }
  45. }
  46. }
  • 对象发布和初始化的时候的安全问题
    1. 方法返回一个private对象(private本意是不然外部访问)
    2. 还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界:
      1. 在构造函数中未初始化完毕就this赋值
      2. 隐式逸出-注册监听事件
      3. 构造函数中运行线程
  1. 方法返回一个private对象(private本意是不然外部访问)
  1. /**
  2. * 发布:public return 等
  3. * 逸出:1. 方法返回一个private对象(private不然外部对象访问)
  4. * 2. 还未完成初始化(构造函数没有完全执行完毕)就把对象提供给外界
  5. * 在构造函数中未初始化完毕就this赋值
  6. * 隐式逸出 -- 注册监听事件
  7. * 构造函数中运行线程
  8. */
  9. public class MultiThreadError3 {
  10. private Map<String, Object> states;
  11. public MultiThreadError3() {
  12. states = new HashMap<>();
  13. states.put("1", "周一");
  14. states.put("2", "周二");
  15. states.put("3", "周三");
  16. states.put("4", "周四");
  17. }
  18. public Map<String, Object> getStates() {
  19. return states;
  20. }
  21. public static void main(String[] args) {
  22. MultiThreadError3 multiThreadError3 = new MultiThreadError3();
  23. Map<String, Object> states = multiThreadError3.getStates();
  24. System.out.println(states.get("1"));
  25. //篡改了states states本意是private 这样逸出导致了问题的发生
  26. states.remove("1");
  27. //如果多个线程调用states.get("1") 就会导致其他线程获取到的是null
  28. System.out.println(states.get("1"));//null
  29. }
  30. }

对象初始化导致的线程安全问题:
在构造函数中未初始化完毕就this赋值

  1. public class MultisThreadError4 {
  2. static Point point;
  3. public static void main(String[] args) throws InterruptedException {
  4. new PointMarker().start();
  5. //如下 根据线程时间休眠的不同导致结果不同 这也是线程的安全问题
  6. // Thread.sleep(10);//1 0
  7. // Thread.sleep(105);// 1 1
  8. if (point != null) {
  9. System.out.println(point);
  10. }
  11. }
  12. }
  13. class Point {
  14. private final int x, y;
  15. public Point(int x, int y) throws InterruptedException {
  16. this.x = x;
  17. MultisThreadError4.point = this;
  18. Thread.sleep(100);
  19. this.y = y;
  20. }
  21. @Override
  22. public String toString() {
  23. return "Point{" +
  24. "x=" + x +
  25. ", y=" + y +
  26. '}';
  27. }
  28. }
  29. class PointMarker extends Thread {
  30. @Override
  31. public void run() {
  32. try {
  33. new Point(1, 1);
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }p

隐式逸出-注册监听事件: 导致的线程安全问题,演示代码如下:

  1. /**
  2. * 观察者模式
  3. */
  4. public class MultiThreadError5 {
  5. int count;
  6. public MultiThreadError5(MySource mySource) {
  7. mySource.registerListener(new EventListener() {
  8. @Override
  9. public void onEvent(Event event) {
  10. System.out.println("我得到的数字是:" + count);
  11. }
  12. });
  13. //对象还没有初始化完毕 就调用了EventListener 导致获得的count为0
  14. for (int i = 0; i < 10000; i++) {
  15. System.out.print(i);
  16. }
  17. count = 100;
  18. }
  19. public static void main(String[] args) {
  20. MySource mySource = new MySource();
  21. new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. try {
  25. Thread.sleep(10);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. mySource.eventCome(new Event() {
  30. });
  31. }
  32. }).start();
  33. MultiThreadError5 multiThreadError5 = new MultiThreadError5(mySource);
  34. }
  35. static class MySource {
  36. private EventListener eventListener;
  37. void registerListener(EventListener eventListener) {
  38. this.eventListener = eventListener;
  39. }
  40. void eventCome(Event e) {
  41. if (eventListener != null) {
  42. eventListener.onEvent(e);
  43. } else {
  44. System.out.println("还未初始化完毕");
  45. }
  46. }
  47. }
  48. interface EventListener {
  49. void onEvent(Event event);
  50. }
  51. interface Event {
  52. }
  53. }

如下结果,构造方法还没有初始化完毕,就调用了监听,导致得到的count是0.这也是线程安全问题
image.png

构造函数中运行线程

  1. package com.prim.background;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class MultiThreadError6 {
  5. private Map<String, Object> states;
  6. public MultiThreadError6() {
  7. new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. try {
  11. Thread.sleep(100);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. states = new HashMap<>();
  16. states.put("1", "周一");
  17. states.put("2", "周二");
  18. states.put("3", "周三");
  19. states.put("4", "周四");
  20. }
  21. }).start();
  22. }
  23. public Map<String, Object> getStates() {
  24. return states;
  25. }
  26. public static void main(String[] args) {
  27. MultiThreadError6 multiThreadError6 = new MultiThreadError6();
  28. try {
  29. Thread.sleep(150);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println(multiThreadError6.getStates().get("1"));
  34. }
  35. }

如何解决逸出?

  • 返回”副本”

    1. public class MultiThreadError3 {
    2. private Map<String, Object> states;
    3. public MultiThreadError3() {
    4. states = new HashMap<>();
    5. states.put("1", "周一");
    6. states.put("2", "周二");
    7. states.put("3", "周三");
    8. states.put("4", "周四");
    9. }
    10. public Map<String, Object> getStates() {
    11. return states;
    12. }
    13. /**
    14. * 通过副本的方法解决 发布逸出
    15. *
    16. * @return
    17. */
    18. public Map<String, Object> getStatesImproved() {
    19. return new HashMap<>(states);
    20. }
    21. public static void main(String[] args) {
    22. MultiThreadError3 multiThreadError3 = new MultiThreadError3();
    23. System.out.println(multiThreadError3.getStatesImproved().get("1"));
    24. multiThreadError3.getStatesImproved().remove("1");
    25. System.out.println(multiThreadError3.getStatesImproved().get("1"));
    26. }
    27. }
  • 工厂模式 注册监听问题修复

将注册监听在初始化完毕后进行注册

  1. public class MultiThreadError7 {
  2. int count;
  3. public EventListener listener;
  4. public MultiThreadError7(MySource mySource) {
  5. listener = new EventListener() {
  6. @Override
  7. public void onEvent(Event event) {
  8. System.out.println();
  9. System.out.println("我得到的数字是:" + count);
  10. }
  11. };
  12. //对象还没有初始化完毕 就调用了EventListener 导致获得的count为0
  13. for (int i = 0; i < 10000; i++) {
  14. System.out.print(i);
  15. }
  16. count = 100;
  17. }
  18. public static MultiThreadError7 getInstance(MySource source) {
  19. //先进行初始化在注册监听
  20. MultiThreadError7 multiThreadError7 = new MultiThreadError7(source);
  21. source.registerListener(multiThreadError7.listener);
  22. return multiThreadError7;
  23. }
  24. public static void main(String[] args) {
  25. MySource mySource = new MySource();
  26. new Thread(new Runnable() {
  27. @Override
  28. public void run() {
  29. // try {
  30. // Thread.sleep(10);
  31. // } catch (InterruptedException e) {
  32. // e.printStackTrace();
  33. // }
  34. mySource.eventCome(new Event() {
  35. });
  36. }
  37. }).start();
  38. MultiThreadError7.getInstance(mySource);
  39. }
  40. static class MySource {
  41. private EventListener eventListener;
  42. void registerListener(EventListener eventListener) {
  43. this.eventListener = eventListener;
  44. }
  45. void eventCome(Event e) {
  46. if (eventListener != null) {
  47. eventListener.onEvent(e);
  48. } else {
  49. System.out.println("还未初始化完毕");
  50. }
  51. }
  52. }
  53. interface EventListener {
  54. void onEvent(Event event);
  55. }
  56. interface Event {
  57. }
  58. }

结果如下,这样就可以在构造函数还没有初始化完毕的时候,不去执行业务逻辑,保证执行的顺序
image.png

需要考虑线程安全的情况:

  • 访问共享的变量和资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
  • 所有具有依赖时序性的操作,即使每一步操作都是线程安全的,还存在并发问题:read-modfiy-write(先读取再修改) check-then-act(先检查再执行)
  • 不同的数据之间存在捆绑关系的时候(IP和端口号)
  • 注意使用其他类的时候,如果对方没有声明是线程安全的,那么大概率会存在并发问题(比如HashMap它不是线程安全的)

性能问题

说到性能问题,首先我们要清楚性能问题有哪些体现、什么性能问题,以及为什么多线程会带来性能问题.

  • 性能问题有哪些体现
  • 为什么多线程会带来性能问题
    • 调度上下文切换
      • 什么是上下文? : 保存现场
      • 缓存开销 : 缓存失效
      • 何时会导致密集的上下文切换:抢锁 、 IO
    • 协作:内存同步