线程的概念
进程是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。真正占用 CPU 的是线程,线程是 CPU 分配的基本单位。
Java 经常以 Main 函数作为程序执行的入口,Main 函数所在的线程就是进程中的一个线程,一般称为主线程。在 Java 中多个线程共享进程的堆和方法区,但是每个线程有自己的程序计数器和栈。
线程创建的方式
继承 Thread 方式
public class ThreadStudy extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread.getName());
}
}
// 启动方式
Thread threadStudy = new ThreadStudy();
threadStudy.start();实现 Runnable 接口方式
public class RunnableTaskStudy implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentTread.getName());
}
}
// 启动方式
RunnableTaskStudy taskStudy = new RunnableTaskStudy();
Thread thread1 = new Thread(taskStudy);
Thread thread2 = new Thread(taskStudy);
thread1.start();
thread2.start();实现 Callable 接口,该方式可以获取返回值
public class CallableTaskStudy implements Callable<String> {
@Override
public void call() throws Exception {
return "hello";
}
}
// 启动
FutureTask<String> futureTask = new FutureTask<>(new CallableStudy());
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
LOGGER.info("Result is " + result);
} catch (InterruptedException | ExecutionException e) {
LOGGER.info(e.getMessage());
throw e;
}
线程通知与等待
当一个线程调用一个共享变量的 wait() 方法后,该线程被阻塞挂起,直到下面几件事情发生返回:
其它线程调用了该共享变量的 notify(), notifyAll() 方法。 其它线程调用了该线程的 interrupt() 方法,该线程抛出 InterruptedException 异常后返回。
调用共享变量的 wait() 方法前,必须先获取该共享变量的监视器锁。
一个线程调用了共享变量的 notify() 方法时后,会唤醒一个在该共享变量上调用 wait() 系列方法后被挂起的线程
被唤醒的线程不能马上从 wait() 方法返回并继续执行,它必须在获取了共享变量的监视器锁之后才可以返回。
代码实现
代码实现需要注意的地方是在判断是否满足执行条件时需要使用 while 循环来防止虚假唤醒
static class PurchaseThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(PurchaseThread.class.toString());
private final Resource resource;
public PurchaseThread(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
try {
resource.purchase();
} catch (InterruptedException exp) {
LOGGER.severe(exp.getMessage());
}
}
}
static class SaleThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(SaleThread.class.toString());
private final Resource resource;
public SaleThread(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
try {
resource.sale();
} catch (InterruptedException exp) {
LOGGER.severe(exp.getMessage());
}
}
}
static class Resource {
private static final Logger LOGGER = Logger.getLogger(Resource.class.toString());
private static final int MAX_SIZE = 10;
private final Deque<String> products;
public Resource(Deque<String> products) {
this.products = products;
}
public void purchase() throws InterruptedException {
synchronized (products) {
while (products.size() == MAX_SIZE) {
products.wait();
}
while (products.size() < MAX_SIZE) {
products.addLast(UUID.randomUUID().toString());
}
products.notifyAll();
}
}
public void sale() throws InterruptedException {
synchronized (products) {
// 这里需要通过while循环来防止虚假唤醒,如果这里使用if作为判断,当线程醒来之后就直接执行后面的逻辑了
while (products.size() == 0) {
products.wait();
}
LOGGER.info(products.removeLast());
products.notifyAll();
}
}
}
线程中断
interrupt() 方法
A 线程可以调用 B 线程的 interrupt() 方法来将 B 线程的中断标志位设置为 true 并返回,这里并不会影响 B 线程的执行。如果 B 调用了 wait() 系列函数,join() 方法,sleep() 方法而被挂起,这个时候 A 调用 B 线程的 interrupt() 方法,线程 B 会在调用方法出抛出 InterruptedException 异常并返回。
isInterrupted()
检测当前线程是否被中断
interrupted()
检测当前线程被中断,如果当前线程被中断,则清除中断标志位。
代码实现
static class PrintThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(PrintThread.class.toString());
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
LOGGER.info("Print Yes");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException exp) {
// 当休眠时被中断会抛出中断异常
LOGGER.severe(exp.getMessage());
break;
}
}
}
}
PrintThread thread = new PrintThread();
thread.start();
TimeUnit.SECONDS.sleep(1);
// 这里中断线程
thread.interrupt();
LOGGER.info("Main thread is over.");