0%

多线程基础-2

多线程基础

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。

  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    • 常见的守护线程:垃圾回收机制。
    • 可以通过 线程.setDaemon(true); 来将该线程设置为守护线程。
    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
    package com.f.chapter17.daemonthread;

    /**
    * @author fzy
    * @date 2023/7/2 22:10
    */
    public class DaemonThread {
    public static void main(String[] args) {
    MyDaemonThread myDaemonThread = new MyDaemonThread();
    //如果希望当main线程结束后,子线程myDaemonThread可以自动退出。
    // 则只需将子线程myDaemonThread设置为守护线程即可。
    myDaemonThread.setDaemon(true);
    myDaemonThread.start();
    for (int i = 0; i < 10; i++) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println("主线程say hi... " + i);
    }
    }
    }

    class MyDaemonThread extends Thread {

    private int cnt = 1;

    @Override
    public void run() {
    while (true) { //无限循环
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println("子线程say hello " + (cnt++));
    }
    }
    }

线程的生命周期

  • JavaThread 源码来看,线程共有六种状态:

    1
    2
    3
    4
    5
    6
    7
    8
    public enum State {
    NEW, //尚未启动的线程处于此状态。
    RUNNABLE, //在Java虚拟机中执行的线程处于此状态。
    BLOCKED, //被阻塞等待监视器锁定的线程处于此状态。
    WAITING, //正在等待另一个线程执行特定动作的线程处于此状态。
    TIMED_WAITING, //正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    TERMINATED; //已退出的线程处于此状态。
    }
  • 从线程的生命周期来看,线程共有七种状态(Runnable 状态又被细分为 ReadyRunning 状态):

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C17-4.jpg)

  • 用代码来显示线程的状态:

    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
    package com.f.chapter17.threadstate;

    /**
    * @author fzy
    * @date 2023/7/3 20:34
    */
    public class ThreadState {
    public static void main(String[] args) {
    T t = new T();
    System.out.println(t.getName() + " 的状态为:" + t.getState());
    t.start();

    while (t.getState() != Thread.State.TERMINATED) {
    System.out.println(t.getName() + " 的状态为:" + t.getState());
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }

    System.out.println(t.getName() + " 的状态为:" + t.getState());

    }
    }

    class T extends Thread {
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println("hi " + i);
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    }
    }

    输出结果为:

    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
    Thread-0 的状态为:NEW
    Thread-0 的状态为:RUNNABLE
    hi 0
    Thread-0 的状态为:TIMED_WAITING
    hi 1
    Thread-0 的状态为:RUNNABLE
    Thread-0 的状态为:TIMED_WAITING
    hi 2
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 3
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 4
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 5
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 6
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 7
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 8
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    hi 9
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TIMED_WAITING
    Thread-0 的状态为:TERMINATED

★★★线程同步机制

  • 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性

    也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

Synchronized
  1. 同步代码块:

    1
    2
    3
    synchronized (对象) {	//得到对象的锁,才能操作同步代码
    //需要被同步的代码;
    }
  2. synchronized 还可以放在方法声明中,表示整个方法为同步方法。

    1
    2
    3
    public synchronized void m (String name) {
    //需要被同步的代码;
    }
  • 对于一个售票示例代码:

    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
    package com.f.chapter17.sellticket;

    /**
    * @author fzy
    * @date 2023/7/2 10:41
    */
    public class SellTicket02 {
    public static void main(String[] args) {
    TicketWindow02 ticketWindow = new TicketWindow02();
    Thread thread1 = new Thread(ticketWindow);
    Thread thread2 = new Thread(ticketWindow);
    Thread thread3 = new Thread(ticketWindow);
    System.out.println("目前一共有 " + ticketWindow.getTicketNum() + " 张票。");
    thread1.start();
    thread2.start();
    thread3.start();
    }
    }

    class TicketWindow02 implements Runnable {
    private int ticketNum = 100;

    public int getTicketNum() {
    return ticketNum;
    }

    @Override
    public void run() {
    while (true) {
    if (ticketNum <= 0) {
    break;
    }

    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }

    System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
    }
    }
    }
    • 由于没有进行线程同步,所以会发生“超卖现象”,即输出结果为:

      1
      2
      3
      4
      5
      6
      ......
      窗口Thread-0售出一张票,还剩 1 张票。
      窗口Thread-1售出一张票,还剩 2 张票。
      窗口Thread-1售出一张票,还剩 0 张票。
      窗口Thread-0售出一张票,还剩 -1 张票。
      窗口Thread-2售出一张票,还剩 -2 张票。
  • 为了保证售票正常,需要使用线程同步技术:

    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    package com.f.chapter17.sync;

    /**
    * @author fzy
    * @date 2023/7/2 10:41
    */
    public class SellTicket02 {
    public static void main(String[] args) {
    TicketWindow02 ticketWindow = new TicketWindow02();
    Thread thread1 = new Thread(ticketWindow);
    Thread thread2 = new Thread(ticketWindow);
    Thread thread3 = new Thread(ticketWindow);
    System.out.println("目前一共有 " + ticketWindow.getTicketNum() + " 张票。");
    thread1.start();
    thread2.start();
    thread3.start();
    }
    }

    //使用 synchronized 实现线程同步
    class TicketWindow02 implements Runnable {
    private static int ticketNum = 100;

    public static int getTicketNum() {
    return ticketNum;
    }

    public synchronized void sell() { //同步方法:在同一个时刻,只能有一个线程来执行 sell 方法。这时锁在this对象上
    /*
    //也可以用:
    //synchronized (对象) { //得到对象的锁,才能操作同步代码
    // //需要被同步的代码;
    //}
    //来实现线程同步

    synchronized (this) {
    if (ticketNum <= 0) {
    System.out.println("售票结束...");
    return;
    }

    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }

    System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
    }
    */

    if (ticketNum <= 0) {
    System.out.println("售票结束...");
    return;
    }


    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }

    System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
    }

    @Override
    public void run() {
    while (true) {
    sell();
    if (ticketNum <= 0) {
    break;
    }
    }
    }
    }
    • 输出结果就正常了:

      1
      2
      3
      4
      5
      6
      窗口Thread-2售出一张票,还剩 3 张票。
      窗口Thread-2售出一张票,还剩 2 张票。
      窗口Thread-0售出一张票,还剩 1 张票。
      窗口Thread-2售出一张票,还剩 0 张票。
      售票结束...
      售票结束...
同步原理
  • 以上面的售票例子为例,分析同步原理。

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C17-5.jpg)

    1. 首先 run 方法是一个 synchronized 方法,意味着该方法采用线程同步机制。
    2. thread1、thread2、thread3 这三个线程都以“抢夺式”的方式来争抢执行 run 方法。
    3. 假设 thread1 争抢成功,则 thread1 会取得该 ticketWindow 对象的锁,然后开始执行相应的代码内容,此时由于 thread2、thread3 并没有该对象的锁,所以无法执行。
    4. 等到 thread1 执行完代码后,其将锁放回该对象中,然后和 thread2、thread3 一起,继续争抢执行 run 方法,如此循环往复。
    • 注意:该锁为对象锁,即该锁是在 ticketWindow 对象上,而不是在 run 方法上。

      除了对象锁以外,还有类锁,类锁使用在类的静态方法中。

★互斥锁
  1. Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
  2. 每个对象都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  3. 关键字 synchronized 用来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
  4. 同步的局限性:导致程序的执行效率要降低。
  5. 同步方法(非静态的)的锁可以是 this,也可以是其他对象要求是同一个对象)。
  6. 同步方法(静态的)的锁为当前类本身。
  • 注意:
    1. 同步方法如果没有使用 static 修饰:默认锁对象为 this
    2. 如果方法使用 static 修饰,默认锁对象为 当前类.class
    3. 实现的落地步骤:
      • 需要先分析要上锁的代码。
      • 选择同步代码块或同步方法(优先选择同步代码块,因为采用同步机制的代码越少,代码执行的效率越高
      • ★★★要求多个线程的锁对象为同一个! -> 很重要!
线程死锁
  • 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程上是一定要避免死锁的发生。

    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.f.chapter17.sync;

    /**
    * @author fzy
    * @date 2023/7/4 14:14
    * 模拟线程死锁
    */
    public class DeadLock {
    public static void main(String[] args) {
    //这段代码形成死锁
    DeadLockDemo deadLockDemo1 = new DeadLockDemo(true);
    DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
    deadLockDemo1.start();
    deadLockDemo2.start();
    //分析:
    // 1.首先deadLockDemo1获得了对象o1的对象锁,然后开始休眠1秒
    // 2.接着deadLockDemo2获得了对象o2的对象锁
    // 3.此时deadLockDemo1仍在休眠,而deadLockDemo2希望得到对象o1的对象锁
    // 4.但是对象o1的对象锁在deadLockDemo1,因此deadLockDemo2无法继续向下执行
    // 5.然后deadLockDemo1休眠结束,若deadLockDemo1要继续向下执行,就要得到对象o2的对象锁
    // 6.但是对象o2的对象锁在deadLockDemo2,因此deadLockDemo1无法继续向下执行
    // 7.这就造成一个情况:若deadLockDemo1要继续向下执行,就要得到对象o2的对象锁,否则其无法释放对象o1的对象锁,
    // 若deadLockDemo2要继续向下执行,就要得到对象o1的对象锁,否则其无法释放对象o2的对象锁,由此形成死锁。
    }
    }

    class DeadLockDemo extends Thread{
    static Object o1 = new Object(); //设置为static,保证多个线程的对象锁为同一个
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
    this.flag = flag;
    }

    @Override
    public void run() {
    if (flag) {
    synchronized (o1) { //对象互斥锁,下面就是同步代码
    System.out.println(Thread.currentThread().getName() + " 进入1");
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName() + " 进入2");
    }
    }
    } else {
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName() + " 进入3");
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName() + " 进入4");
    }
    }
    }
    }
    }
★释放锁
  • 下面的操作会释放锁

    1. 当前线程的同步代码块、同步方法执行结束。
    2. 当前线程在同步代码块、同步方法中遇到 breakreturn
    3. 当前线程在同步代码块、同步方法中出现了未处理的 ErrorException,导致异常结束。
    4. 当前线程在同步代码块、同步方法中执行了有锁的对象的 wait() 方法,导致当前线程暂停,并释放锁。
      • 注意:在调用 wait() 方法使得线程暂停并释放锁后,后续还要调用 notify()notifyAll() 方法来将线程唤醒,使得线程能继续执行。
  • 下面的操作不会释放锁

    1. 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()Thread.yield()方法暂停当前线程的执行,不会释放锁
    2. 线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁。
    • 注意:应尽量避免使用 suspend()resume() 来控制线程,方法不再推荐使用。
★wait、notify和notifyAll
  • waitnotifynotifyAll 方法用于允许线程通过访问公共对象来相互通信,或者换句话说,可以将 Object 视为通过这些方法进行线程间通信的媒介。这些方法需要从同步上下文中调用,否则会抛出 java.lang.IllegalMonitorStateException 异常。

    • wait():当您在对象上调用 wait 方法时,它会告诉线程释放该对象的锁并进入睡眠状态,除非直到某个其他线程进入同一监视器并在其上调用 notifynotifyAll 方法。
    • notify():当您在对象上调用 notify 方法时,它会唤醒等待该对象的锁的线程之一。因此,如果多个线程正在等待同一个对象锁,它将唤醒其中一个。现在你一定想知道它会唤醒哪一个。它实际上取决于操作系统的实现。
    • notifyAll()notifyAll 将唤醒等待该对象的锁的所有线程,不像 notify 只唤醒其中一个。哪个线程首先被唤醒取决于线程优先级和操作系统实现。
  • 通过 wait、notify、notifyAll 方法来解决“线程死锁”小节的死锁问题:

    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package com.f.chapter17.sync;

    /**
    * @author fzy
    * @date 2023/7/4 21:20
    */
    public class WaitExample {
    public static void main(String[] args) {
    SolveDeadLockDemo solveDeadLockDemo1 = new SolveDeadLockDemo(true);
    SolveDeadLockDemo solveDeadLockDemo2 = new SolveDeadLockDemo(false);
    solveDeadLockDemo1.start();
    solveDeadLockDemo2.start();
    }
    }

    class SolveDeadLockDemo extends Thread {
    static Object o1 = new Object(); //设置为static,保证多个线程的对象锁为同一个
    static Object o2 = new Object();
    boolean flag;

    public SolveDeadLockDemo(boolean flag) {
    this.flag = flag;
    }

    @Override
    public void run() {
    if (flag) {
    synchronized (o1) { //对象互斥锁,下面就是同步代码
    System.out.println(Thread.currentThread().getName() + " 进入1");
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    //调用wait方法释放o1对象锁,注意wait方法一定是在synchronized中调用
    try {
    o1.wait();
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName() + " 进入2");
    }
    }
    } else {
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName() + " 进入3");
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName() + " 进入4");
    //将释放o1对象锁的线程唤醒,使其继续执行
    o1.notify();
    }
    }
    }
    }
    }
---------------The End---------------