ZB-029-01多线程初步

线程不安全的表现

  • 数据错误
  • 死锁
  • 著名的HashMap的死循环问题
  • 写⼀段代码来重现死锁
  • 预防死锁产⽣的原则:
    • 所有的线程都按照相同的顺序获得资源的锁
  • 死锁问题的排查
  • 多线程的经典问题:哲学家⽤餐

数据错误

  • i++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.io.demo;

    public class Main {
    public static long i =0;
    public static void main(String[] args) {
    for (int j = 0; j < 1000; j++) {

    new Thread(()-> {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(++i);
    }).start();
    }

    }
    }

    // 执行后不是 1000
  • if-then-do

    • HashMap 线程不安全容器
    • HashMap 死循环
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      package com.io.demo;

      import java.util.HashMap;
      import java.util.Map;
      import java.util.Random;

      public class Main {
      public static Map<Integer,Integer> map = new HashMap<>();
      public static void main(String[] args) {
      for (int j = 0; j < 1000; j++) {
      new Thread(Main::putIfAbsent).start();
      }
      }

      // 随机丢入 HashMap 一个1~10数字 如果它不存在就丢进去
      private static void putIfAbsent(){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      int r = new Random().nextInt(10);
      if(!map.containsKey(r)){
      map.put(r,r);
      System.out.println("put:" + r);
      }
      }
      }
      // 执行后会发生 一个数被 put 多次

      /*
      这里有一个共享的 map
      在某一时刻
      线程1 随机了一个数字 0 ,同时它的执行时间到了
      与此同时:一另一个 线程2 开始执行了, 它也生成了一个0 并成功把它 put 进入 map里
      此时 线程1 恢复执行 获得资源调度继续执行 于是。。。 出现了 put 2次 0 的情况
      */

为什么要多线程

  • 为了让程序跑的更快,但是你的程序跑的更快,如果结果不对跑的再快也没用。
  • 这就是线程不安全 最要命的问题 它会产生错误的数据

死锁

  • java里任何一个对象都可以当作
  • synchronized 关键字:就是同一时刻只能有一个线程拿着一把

死锁实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.io.demo;


public class Main {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public static void main(String[] args) {
new Thead1().start();
new Thead2().start();
}

static class Thead1 extends Thread{
@Override
public void run() {
synchronized (lock1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (lock2){
System.out.println("thread1");
}
}
}
}

static class Thead2 extends Thread{
@Override
public void run() {
synchronized (lock2){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (lock1){
System.out.println("thread2");
}
}
}
}
}

死锁问题的排查

如何排查线程死锁问题呢?

第一步:jps 找到程序的进程编号

  • 所有的 java 都跑在 jvm 里,而操作系统看来 一个jvm 就是一个普通进程
  • 首先要找到进程
    • windows 上 任务管理器
    • 类 linux 系统使用 ps aux | grep java 可以列出所有 java 进程
    • jps :java自带的命令,只要配置了 java_home 那个目录里有这个命令 显示所有java的进程

第二步:jstack 进程号

  • 打印所有 该 java进程中的 栈信息

线程安全

  • 实现线程安全的基本⼿段

    • 不可变类
    • Integer/String/….

      1
      2
      3
      4
      5
      // 字符串 "a" 地址为 201
      String s = "a";
      s = "b";
      // 201地址的字符串没变 实际变的是 引用
      // 字符串 "b" 的地址是 301
    • synchronized同步块

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      // 解决 多线程 共享数据问题
      package com.io.demo;

      public class Main2 {
      private static int i = 0;
      private static final Object lock = new Object();

      public static void main(String[] args) {
      for (int j = 0; j < 1000; j++) {
      new Thread(Main2::modifySharedVariable).start();
      }
      }

      public static void modifySharedVariable(){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      synchronized (lock){
      i+=1;
      }
      System.out.println(i);
      }
      }

      另一种方式,明显感觉比之前慢了 因为此时的锁是 这个 class

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      package com.io.demo;

      public class Main2 {
      private static int i = 0;
      private static final Object lock = new Object();

      public static void main(String[] args) {
      for (int j = 0; j < 1000; j++) {
      new Thread(Main2::modifySharedVariable).start();
      }
      }

      public synchronized static void modifySharedVariable(){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      i+=1;
      System.out.println(i);
      }
      }

      移除 static 关键字 后 谁是锁?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      package com.io.demo;

      public class Main2 {
      private static int i = 0;
      private static final Object lock = new Object();

      public static void main(String[] args) {
      Main2 main2 = new Main2();
      for (int j = 0; j < 1000; j++) {
      new Thread(main2::modifySharedVariable).start();
      }
      }

      public synchronized void modifySharedVariable(){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      i+=1;
      System.out.println(i);
      }

      // 等价于
      public void modifySharedVariable2(){
      synchronized(this){
      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      i+=1;
      System.out.println(i);
      }
      }
      }
    • 同步块同步了什么东⻄?

      • synchronized(⼀个对象) 把这个对象当成锁
      • static synchronized ⽅法 把Class对象当成锁
      • 实例的synchronnized⽅法把该实例当成锁
    • Collections.synchronized