专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

并发编程之线程基础

线程简介

  • 进程与线程

    进程是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。而线程是进程的组成部分,它代表了一条顺序的执行流。

  • 线程的状态

    1. NEW 线程刚被创建
    2. RUNNABLE 可运行状态。包括正在运行中和等待CPU分配时间片段
    3. BLOCKED 阻塞状态。正在等待获取synchronized监视器
    4. WAITING 等待状态。进入此状态的方法有:wait(),join(),park()
    5. TIMED_WAITING 有超时时间限制的等待。sleep(),wait(long),join(long),LockSupport.parkNanos(long),LockSupport.parkUntil(long)
    6. TERMINATED 线程执行结束,被终止
  • 创建线程
    1. 继承Thread类,重写run()方法。
    2. 实现Runnable接口,重写run()方法。
  • 需要注意的问题
    1. 线程并不是越多越好,线程的切换是需要消耗资源的,这是上下文切换的成本。
    2. 死锁。线程之间互相等待对方释放锁,会出现死锁的情况。
    3. 多线程并发。
  • 线程生命周期

33_1.png

  • 解决并发问题的常见锁
    1. synchronized
    2. ReentrantLock
    3. CountDownLatch

具体的比较分析,其他章节再做讨论。

线程通信

线程通信的方式有以下几种:

1、 JDK中提供的wait(),notify(),notifyAll()
2、 ReentrantLock中使用的Condition
3、 LockSupport

  • JDK中提供的wait(),notify(),notifyAll()
    1. wait():使线程进入WAITING状态,只有等待另一个线程 通知或者被中断才返回。
    2. notify():唤醒该对象上等待的一个线程,从wait()方法返回。如果有多个线程在该对象上等待,唤醒哪一个由操作系统来决定的。
    3. notifyAll():唤醒该对象上等待的所有线程。

    需要特别注意的是:能够调用这些方法的对象必须是锁对象,否则会抛出异常,所以,通常在synchronized中使用这些方法。其次,调用wait方法后,该线程会释放锁,调用notify和notifyAll后不会释放锁,继续向下执行任务。

public class SyncDemo {
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    public static void main(String[] args) {
        Runnable r = () -> method1();
        Thread thread = new Thread(r);
        thread.start();
    }
    private static void method1() {
        //o1是锁对象
        synchronized (o1) {
            try {
                System.out.println("开始WAITING");
                // o2并不是锁对象
                o2.wait();
            } catch (InterruptedException e) {
//                e.printStackTrace();
            }
        }
    }

结果如下,抛出了异常。

开始WAITING
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.mooc.Thread.SyncDemo.method1(SyncDemo.java:16)
    at com.mooc.Thread.SyncDemo.lambda$main$0(SyncDemo.java:8)
    at java.lang.Thread.run(Thread.java:748)

关于锁的释放时机:

public class SyncDemo {
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    public static void main(String[] args) {
        Runnable r1 = SyncDemo::method1;
        Runnable r2 = SyncDemo::method2;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
    }
    private static void method1() {
        synchronized (o1) {
            try {
                System.out.println("线程:" + Thread.currentThread().getName() + " 获取到了锁");
                System.out.println("线程:" + Thread.currentThread().getName() + " 等待");
                o1.wait();
            } catch (InterruptedException e) {
//                e.printStackTrace();
            }
        }
    }
    private static void method2() {
        synchronized (o1) {
            System.out.println("线程:" + Thread.currentThread().getName() + " 获取到了锁");
            for (; ; ) {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程:" + Thread.currentThread().getName() + " 在运行");
                } catch (InterruptedException e) {
//                    e.printStackTrace();
                }
            }
        }
    }
}

结果如下,两个线程是同一个对象监视器,当Thread-0进入等待后,Thread-1仍然还可以执行,说明Thread-0已经释放了锁。

线程:Thread-0 获取到了锁
线程:Thread-0 等待
线程:Thread-1 获取到了锁
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行

  • Condition 提供了几个方法:
    1. await()
    2. awaitUninterruptibly
    3. await(long time, TimeUnit unit)
    4. signal()
    5. signalAll() 方法的 作用见名知意,这里不做过多陈述。
  • LockSupport

LockSupport是一个线程工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒。

  • 核心方法: 1. park():阻塞线程 2. uppark(thread):唤醒一个指定的线程
  • 与wait/notify对比:
    1. wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程。
    2. notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark却可以唤醒一个指定的线程。
    3. 因为中断的时候 park不会抛出 InterruptedException异常,所以需要在 park之后自行判断中断状态,然后做额外的处理。
    4. park()与uppark(thread)执行的顺序不重要,先uppark(thread)然后再park()也没关系。如下示例:
  public class LockSupportDemo {
      public static void main(String[] args) {
          Thread thread = new Thread(new LockSupportDemoThread());
          thread.start();
          // 唤醒线程
          LockSupport.unpark(thread);
          System.out.println("唤醒完毕");
      }
      private static class LockSupportDemoThread implements Runnable{
          @Override
          public void run() {
              try {
                  System.out.println("线程:"+ Thread.currentThread().getName()+" 开始执行");
                  // 在此处阻塞5s,保证此时unpark()方法在park()方法之前已经执行了
                  Thread.sleep(5000);
                  // 让线程进入WAITING
                  System.out.println("线程:"+ Thread.currentThread().getName()+" 即将进入等待");
                  LockSupport.park();
              } catch (InterruptedException e) {
  //                e.printStackTrace();
              }
              System.out.println("线程:"+ Thread.currentThread().getName()+" 继续执行");
          }
      }
  }

结果如下所示,是先执行的unpark,然后才park的,但是最终线程还是被唤醒了,说明顺序并不重要。

  唤醒完毕
  线程:Thread-0 开始执行
  线程:Thread-0 即将进入等待
  线程:Thread-0 继续执行

线程中断

中断是一种协同机制,可以是线程自己中断,也可以被其他线程中断。线程中断并不意味着线程结束了

  • 中断标识:每个线程都维护一个线程标志位,用以表示线程是否中断。
  • 关于中断的几个重要方法:interrupt()、Thread.interrupted()、isInterrupted()
  • interrupt()
    1. 如果线程是阻塞在wait、join、sleep方法中,则会抛出InterruptedException,并重置中断标志位
    2. 如果线程是阻塞在I/O操作InterruptibleChannel中,通道会关闭,标志位被设置(不是重置)。
    3. 如果线程是阻塞NIO的Selector中,会立即返回,标志位被设置(不是重置)。
    4. 如果线程不是处于以上3中情况中,则只是会设置标志位(不是重置)。
  • Thread.interrupted()
  public static boolean interrupted() {
        return currentThread().isInterrupted(true);// true ,表示重置标志位
    }

返回当前中断标志,并且重置标志位

  • isInterrupted()

返回当前中断标志,不会重置标志位

public boolean isInterrupted() {
        return isInterrupted(false);// false,不会重置标志位
    }

Thread.interrupted()和isInterrupted()都是调用的native方法,只是入参不同。

private native boolean isInterrupted(boolean ClearInterrupted);

  • 线程中断使用注意事项
    1. 查看当前的中断标识,是否被清空
    2. 关注InterruptedException

本文使用 tech.souyunku.com 排版

文章永久链接:https://tech.souyunku.com/37453

未经允许不得转载:搜云库技术团队 » 并发编程之线程基础

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们