Java的线程和常见方法

本文最后更新于 2024年11月3日

在学习和使用Java的多线程前,需要了解一些关于计算机中进程,线程的基础知识。

1.线程,进程和管程

1.1 线程(Thread)

  • 定义:线程是操作系统中能够独立运行的最小单位,是进程的一个执行分支。一个进程可以包含多个线程,它们共享同一进程的资源(如内存和文件句柄)。
  • 特点
    • 线程之间的创建和销毁开销较小。
    • 线程间共享内存,通信较为高效,但也容易引发竞争条件和数据不一致问题。

1.2 进程(Process)

  • 定义:进程是程序在计算机上运行的实例,它拥有自己的内存空间和资源。进程之间是相互独立的,通常通过进程间通信(IPC)进行数据交换。
  • 特点
    • 进程有自己的地址空间,线程间不共享内存。
    • 进程的创建和销毁开销较大,但提供更好的隔离性和稳定性。

1.3 管程(Monitor)

  • 定义:管程是一种高层次的同步机制,用于控制对共享资源的访问。它将共享资源的访问和管理封装在一个对象中,并提供互斥访问。
  • 特点
    • 管程通常包括一个互斥锁和一些条件变量。
    • 通过管程,可以避免线程间的竞争条件,简化线程同步的复杂性。

2.串行、并行和并发

2.1 串行(Serial)

  • 定义:派发多个任务,所有任务都按照顺序先后执行。
  • 特点:顺序执行,执行总时长几乎等于每个任务执行的时间相加。

2.2 并行(Parallelism)

  • 定义:并行是指派发多个任务在同一时刻同时执行直到全部完成。通常是在多核处理器上,多个任务可以同时在不同的核心上运行。
  • 特点:同一时间分别执行,每个任务执行都不被打断,执行总时长约等于耗时最长的那个任务需要的时间。

2.3 并发(Concurrency)

  • 定义:派发多个任务在同一时间段内进行,不一定是同时执行的。任务可能在共享的时间片上交替运行。
  • 特点:同一时间交替的执行,任务有被其他任务抢走时间片后中断和抢占其他任务的时间片的可能,执行总时长可能小于以串行或并行来执行这些任务的总时长。

3.Java多线程的实现和常见方法

3.1 继承Thread类创建子线程

通过继承java.lang.Thread,重写其run()方法来创建自定义线程类,然后实例化该类并调用start()方法启动线程,jvm自动调用run()方法,run()方法运行的就是子线程。

1.可以通过重写带参(线程名)构造函数,或调用setName()设置线程名。

2.每个Thread只能执行一次run()方法否则会出现IllegalThreadStateException异常,如果我们自己直接运行线程的run()方法等同于对象调用方法,仍然是单线程。

3.通过Thread.currentThread()可以获取当前运行的线程。

class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("Thread is running.");
        System.out.println(Thread.currentThread().getName());
    }
}

MyThread thread = new MyThread("t1");
thread.start();
class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("Thread is running.");
        System.out.println(Thread.currentThread().getName());
    }
}

MyThread thread = new MyThread();
thread.setName("t1");
thread.start();

通过匿名内部类写法

new Thread("t1") {

   @Override
   public void run() {
      System.out.println("Thread is running.");
      System.out.println(Thread.currentThread().getName());
   }

}.start();

3.2 实现Runnable接口创建子线程

通过实现java.lang.Runnable接口并重写其run()方法实现一个Target对象,然后将该Target传递给Thread,同时可以选择指定一个线程名,再调用Thread的start()方法启动线程,jvm自动调用run()方法,在run()方法开启子线程。

实现Runnable接口创建子线程,既可以避免单继承的局限,又能使得代码更加清晰,把子线程的任务与执行任务的Thread对象分开,可以用一个任务创建出多个线程同时执行。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread is running.");
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

Thread thread2 = new Thread(new MyRunnable(), "t2");
thread2.start();

无论哪种方式创建线程,都不能自己手动调用Thread的run()方法,必须调用start()start()方法最终调用C++实现的native方法start0(),由JVM调用run()方法,所以自己调用run()方法无法实现多线程。
java.lang.Thread

public synchronized void start() {
  /**
  * This method is not invoked for the main method thread or "system"
  * group threads created/set up by the VM. Any new functionality added
  * to this method in the future may have to also be added to the VM.
  *
  * A zero status value corresponds to state "NEW".
  */
  if (threadStatus != 0)
     throw new IllegalThreadStateException();

  /* Notify the group that this thread is about to be started
  * so that it can be added to the group's list of threads
  * and the group's unstarted count can be decremented. */
  group.add(this);

  boolean started = false;
  try {
     start0();
     started = true;
  } finally {
     try {
           if (!started) {
              group.threadStartFailed(this);
           }
     } catch (Throwable ignore) {
           /* do nothing. If start0 threw a Throwable then
           it will be passed up the call stack */
     }
  }
}

private native void start0();

3.3 创建守护线程

JVM的线程分为用户线程和守护线程

用户线程:系统的工作线程,会完成这个程序需要完成的业务操作。

守护线程:服务线程,没有服务对象就没有必要继续运行下去了。

Java中,通过设置setDaemon(true)来实现一个守护线程,需要在调用start()方法之前设置,当主线程结束后,守护线程即使还有任务未完成,JVM进程也会退出。如果子线程没有设置为守护线程,即使主线程完成,子线程仍然继续执行未完成的任务,JVM不会退出。

public static void main(String[] args) {
   Thread daemon = new Thread(() -> {
      System.out.println(Thread.currentThread().getName());

      while (true) {

      }

   }, "daemon");

   daemon.setDaemon(true);
   daemon.start();

   System.out.println(Thread.currentThread().getName());
}

3.4 线程的暂停:sleep()

暂停执行(睡眠)多少毫秒

Thread.sleep(2000);

3.5 检查线程是否存活:isAlive()

通过Thread.sleep();使线程休眠,观测线程开启和结束后的状态。

public static void main(String[] args) {
   Thread t1 = new Thread(() -> {
      System.out.println(Thread.currentThread().getName());

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

   }, "t1");

   t1.start();

   System.out.println(t1.isAlive()); //true

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

   System.out.println(t1.isAlive()); //false
}

3.6 让出CPU:yield()

静态方法,当前线程让出CPU,给同级或更高优先级线程机会

Thread.yield();

还有一些线程的方法涉及到线程间的通信,见:Java线程间的通信机制


"如果文章对您有帮助,可以请作者喝杯咖啡吗?"

微信二维码

微信支付

支付宝二维码

支付宝


Java的线程和常见方法
https://blog.liuzijian.com/post/7e9cf814-2856-46b9-b30d-7a77f7090b04.html
作者
Liu Zijian
发布于
2022年5月1日
更新于
2024年11月3日
许可协议