Java 线程基础

线程状态

操作系统中线程的状态

在操作系统中,线程被视为轻量级的进程,所以线程状态其实和进程状态是一致的。

image

操作系统的线程主要有以下三个状态:

  • 就绪状态(ready):线程正在等待使用 CPU,经调度程序调用之后进入 running 状态。

  • 执行状态(running):线程正在使用 CPU。

  • 等待状态(waiting):线程经过等待事件的调用或者正在等待其他资源(如 I/O)。

Java 中的线程状态

// Thread.State
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

线程状态:

image

NEW

New 状态:处于 NEW 状态的线程此时尚未启动。

RUNNABLE

RUNNABLE (可运行)状态:表示当前线程处于可运行状态。处于 RUNNABLE 状态的线程在 Java 虚拟机中运行,也有可能在等待 CPU 分配资源。包含了操作系统线程状态中的 Running 和 Ready。

BLOCKED

BLOCKED(阻塞)状态:等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

WAITING

WAITING(等待)状态:等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入 WAITING 方法 退出 WAITING 方法
不带参数的 Object.wait() Object.notify()、Object.notifyAll()
不带参数的 Thread.join() 被调用的线程执行完毕
LockSupport.park() -

TIMED_WAITING

TIMED_WAITING(超时等待)状态:无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。

调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。

而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

进入 TIMED_WAITING 方法 退出 TIMED_WAITING 方法
Thread.sleep(long millis) 时间结束
Object.wait(long timeout) 时间结束、Object.notify()、Object.notifyAll()
Thread.join(long millis) 时间结束、被调用的线程执行完毕
LockSupport.parkNanos(long nanos) -
LockSupport.parkUntil(long deadline) -

TERMINATED

TERMINATED(终止)状态:线程结束任务之后自己终止,或者产生了异常而终止。

Java 中创建线程的方式

Java 中创建线程有四种方式,分别是:

继承 Thread 类

  • 首先定义一个类来继承 Thread 类,重写 run 方法。

  • 然后创建这个子类对象,并调用 start 方法启动线程。

示例:

public class ThreadDemo1 extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("thread demo1 is running");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试代码:

public class MainThreadDemo {
    public static void main(String[] args) {
        new ThreadDemo1().start();
    }
}

实现 Runnable 接口

  • 首先定义一个类实现 Runnable 接口,并实现 run 方法。

  • 然后创建 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中

  • 最后调用 start 方法启动线程。

示例:

public class ThreadDemo2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("thread demo2 is running");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试代码:

public class MainThreadDemo {
    public static void main(String[] args) {
        new Thread(new ThreadDemo2()).start();
    }
}

实现 Callable 接口,并结合 Future 实现

  • 首先定义一个 Callable 的实现类,并实现 call 方法。call 方法是带返回值的。

  • 然后通过 FutureTask 的构造方法,把这个 Callable 实现类传进去。

  • 把 FutureTask 作为 Thread 类的 target ,创建 Thread 线程对象。

  • 通过 FutureTask 的 get 方法获取线程的执行结果。

import java.util.concurrent.Callable;

public class ThreadDemo3 implements Callable<String> {
    @Override
    public String call() {
        for (int i = 0; i < 10; i++) {
            System.out.println("thread demo3 is running");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return "success";
    }
}

测试代码:

public class MainThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new ThreadDemo3());
        new Thread(task).start();
        String result = task.get();
        System.out.println(result);
    }
}

通过线程池创建线程

方式一:Runnable

  • 首先,定一个 Runnable 的实现类,重写 run 方法。

  • 然后创建一个拥有固定线程数的线程池。

  • 最后通过 ExecutorService 对象的 execute 方法传入线程对象。
    示例:

public class ThreadDemo4 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("[" + Thread.currentThread().getName() + "]:thread demo3 is running, " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

通过线程池创建线程:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class MainThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService executorService = new ThreadPoolExecutor(
                8, 16, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy()
        );

        for (int i = 0; i < 3; i++) {
            executorService.execute(new ThreadDemo4());
        }
        executorService.shutdown();
    }
}

这种方法有一个缺点,无法对执行线程的任务传参,也没办法获取线程执行后的返回值,因此,实际使用的时候,更多的场景是使用 CompletableFuture 类去创建线程任务。

方式二:CompletableFuture

  • 使用 CompletableFuture 类去创建线程任务。

  • 然后创建一个拥有固定线程数的线程池。

  • 最后通过 CompletableFuture.supplyAsync 方法创建线程任务。

定义一个 Task 类:

public class ThreadDemo4 {

    public String count(int start, int end) {
        for (int i = start; i < end; i++) {
            System.out.println("[" + Thread.currentThread().getName() + "]: thread demo3 is running, " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return "success";
    }
}

创建线程池:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class MainThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService executorService = new ThreadPoolExecutor(
                8, 16, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy()
        );
        List<CompletableFuture<String>> futureList = new ArrayList<>();
        List<String> results = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            CompletableFuture<String> future = CompletableFuture
                    .supplyAsync(() -> new ThreadDemo4().count(finalI, finalI + 10), executorService)
                    .whenComplete((result, exception) -> {
                        results.add(result);
                    });
            futureList.add(future);
        }

        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(10, TimeUnit.MINUTES);
        executorService.shutdown();
        System.out.println(results);
    }
}

CompletableFuture 创建线程的时候,也是调用的ExecutorService.execute 方法创建线程任务,但是,CompletableFuture 会将线程封装为 AsyncSupply 对象,在执行结束后,保存线程的计算结果。

总结

在 JAVA 源码中,关于 Thread 类有一段注释:

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

   class PrimeThread extends Thread {
          long minPrime;
          PrimeThread(long minPrime) {
              this.minPrime = minPrime;
          }

          public void run() {
              // compute primes larger than minPrime
               . . .
          }
      }

The following code would then create a thread and start it running:

      PrimeThread p = new PrimeThread(143);
      p.start();

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:

      class PrimeRun implements Runnable {
          long minPrime;
          PrimeRun(long minPrime) {
              this.minPrime = minPrime;
          }

          public void run() {
              // compute primes larger than minPrime
               . . .
         }
      }

The following code would then create a thread and start it running:

      PrimeRun p = new PrimeRun(143);
      new Thread(p).start();

-- 摘自 Java Doc: Thread

即创建一个新的执行线程有两种方式:

  • 方式一:继承 Thread 类,并覆盖 run() 方法,然后,调用该子类对象的 start() 方法;

  • 方式二:实现 Runnable 接口,并实现 run() 方法,然后,构造一个 Thread 对象,并调用其 start() 方法。

可以发现,最终这两种方式,都会调用 Thread.start() 方法,而 Thread.start() 方法最终会调用 Thread.run() 方法,其源码实现如下:

public class Thread implements Runnable {

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    ...
}

由此,可以看出,两者的区别:

创建方式 区别
方式一 继承的方式,运行时,调用的子类的 run 方法
方式二 将实现类委托给 Thread,运行时,调用的是 Thread 类中的 run 方法

因此,结合前面的分析,在本质上,创建线程只有一种方式:就是构造一个 Thread 类的对象。

对于,Callable + FutureTask 的方式,也是需要通过 new Thread(task) 的方式构造一个 Thread 对象,然后通过 Thread.start 方法,调用 FutureTask.run 方法,最后执行 call 方法。

对于,线程池的方式,默认情况下,会使用线程池工厂类 DefaultThreadFactory 创建一个新的 Thread 对象。

// java.util.concurrent.ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService {

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
        ...
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    }
    ...
}

// java.util.concurrent.Executors
public class Executors {
    static class DefaultThreadFactory implements ThreadFactory {
        ...
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
    ...
}

有趣的例子

如果,我们在子类中,同时继承 Thread,并且由实现了 Runnable 接口,如下面的代码所示:

public class TestThread {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("runnable")) {
            @Override
            public void run() {
                System.out.println("Thread run");
            }
        }.start();
    }
}

代码应该输出什么呢?

通过分析,以上代码就会知道,这是一个继承了 Thread 父类的子类对象,重写了父类的 run 方法。然后,在构造函数 public Thread(Runnable target) 中传入了一个 Runnable 接口的对象。

而线程执行 start 方法时,会首先判断当前对象是否有 run 方法,存在则优先调用子类的 run 方法。

因此,上述代码的输出结果为:

Thread run

参考:

posted @ 2023-10-26 12:43  LARRY1024  阅读(16)  评论(0编辑  收藏  举报