更多互联网新鲜资讯、工作奇淫技巧 关注原创【飞鱼在浪屿】 (日更新)
JavaThread对象允许我们在单独的线程中运行代码。当应用程序启动时,JVM将创建名为的初始线程main。main方法在主线程上运行。在应用程序内部,我们可以创建新线程以与主线程并行执行其他任务。
Java使用本机操作系统线程。因此,一个操作系统线程映射了一个Java线程。
创建线程
Thread类的构造函数带有一个Runnable对象。可运行接口具有一个抽象run方法,该方法称为Thread#start()方法。它可以由lambda,匿名类或实现Runnable方法的类实例化。
通常,使用lambda更容易且更紧凑:
Thread thread = new Thread(() -> {
// content of run command
});
thread.start();
只要线程的运行挂钩方法没有返回,线程就一直存在。调度程序可以挂起并运行线程多次。为了使线程永远执行,它需要一个无限循环来防止其返回。
Join方法允许一个线程等待另一个线程的完成。这是屏障同步的一种简单形式。
Java线程类型:用户和守护程序线程
JVM启动时,它包含一个名为主线程的用户线程。用户线程和守护程序线程之间的主要区别是它们退出时会发生什么。
- 即使主线程退出,用户线程也会继续其生命周期。
- 但是,当所有用户线程退出时,所有守护进程线程都会终止。
- 当所有用户线程退出时,JVM本身退出。
线程类包含布尔daemon字段,用于指定线程是否为守护程序。可以在创建时通过构造函数或setter方法进行设置。
Thread thread = new Thread(getRunnable());
thread.setDaemon(true);
thread.start();
默认情况下,守护程序字段为false,因此我们生成的大多数线程都是用户线程。isDaemon如果未指定,则线程会复制父线程的状态。Java使用守护线程在一些场景,如ForkJoinPool和Timer。为了说明,看以下示例:
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// runDeamonThread();
runUserThread();
System.out.println(getCurrentThreadName() + " exits");
}
private static void runDeamonThread() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newWorkStealingPool(10);
executorService.execute(getRunnable());
}
private static void runUserThread() {
Thread thread = new Thread(getRunnable());
thread.start();
}
private static Runnable getRunnable() {
return () -> {
for (int i = 0; i <= 200; i++) {
System.out.print(".");
Thread.yield();
}
System.out.println(getCurrentThreadName() + " exits. isDeamon: " + isDaemon());
};
}
private static boolean isDaemon() {
return Thread.currentThread().isDaemon();
}
private static String getCurrentThreadName() {
return Thread.currentThread().getName();
}
}
- 当调用runUserThread方法时,它显示以下示例输出:
................................................
main exits
........................................................................................
Thread-0 exits. isDeamon: false
- 第二种情况是调用runDeamonThread,以ForkJoinPoolDaemon Threads为例。可以简单地使用setDaemon(true)方法。输出:
main exits
因此,当main方法退出时,所有用户线程都将终止,JVM退出并杀死所有守护程序线程,因此我们甚至没有机会看到守护程序线程的输出。
停止线程
与创建相比,停止线程是一件非常困难的事情。一旦线程开始运行,它就会与调用者分离开来,并且它具有自己的生命周期。它可以完成任务并退出,或者如果它长时间运行,则可以永远工作。Java没有为我们提供自愿停止线程的方法(不建议使用)。
- 简单的方法可能是使用停止标志:
volatile boolean isStopped = false;
public void test() {
new Thread(() -> {
while (!isStopped) {
System.out.print(".");
}
System.out.println("Child Exits");
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStopped = true;
System.out.println("Main exits");
}
该标志用volatile为了使两个线程都可以看到其最新值。但是,如果线程被阻塞sleep,wait,join或阻塞I / O操作,那么将失败。
2.停止的另一种方法是使用线的interrupt()方法。
为了使中断机制正常工作,被中断的线程必须支持其自己的中断机制。可以检查以下2种情况:
- 非阻塞和长时间运行的任务
在这种情况下,调用该thread.interrupt()方法将设置该线程的中断标志,但是如果任务本身不检查中断标志的状态,则不会有任何影响。例如:
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
while (true) {
System.out.print(".");
}
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
为了使线程捕获中断,它应该迭代检查中断标志的状态,以便它可以了解是否有任何待处理的中断请求并相应地处理该请求。
因此,可以在while循环中检查该标志,如果为true,则可以返回或中断该循环。在lambda表达式中,不可能引发异常,但是在适当的地方也可以引发异常InterruptedException。
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
while (true) {
if (Thread.interrupted()) {
break;
}
System.out.print(".");
}
System.out.println("Child exits");
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
请注意,该Thread.interrupted()方法返回标志的值,如果为true,则将其清除。因此,如果想将线程的状态保持为堆栈高层的中断,则可以使用Thread.currentThread().interrupt();
- 阻止任务
如果一个线程调用频繁的封锁方式,例如wait,join,sleep,blocking I/O。这些方法在内部检查中断状态,如果他们被中断,如果让他们自动抛出异常InterruptedException。应该在适当的上下文中捕获并处理此异常。以下示例使用中断来中断阻塞sleep操作中的循环:
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
try {
while (true) {
Thread.sleep(10000);
}
} catch (InterruptedException e) {
System.out.println("Thread interrupted: " + e.getMessage());
}
System.out.println("Child Exits");
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
有一些处理Java的模式InterruptedException:
- 一种方法是将异常传播给调用方,因此上层将负责。
- 在重新抛出之前,可以进行任务特定的清理。
- 如果不可能重新抛出,可以再次将中断状态设置为true,Thread.currentThread().interrupt()以保留更上层要检查它的证据。
因此,可以断定,如果要执行可取消的任务,则需要定期检查中断状态,并以线程退出的方式处理中断。
线程组
为了简化线程管理,可以使用java.lang.ThreadGroup对相关线程进行分组的对象来组织多个线程。每个线程组都需要有一个父组。在层次结构中,有一个Main组,它是程序中创建的其他组或线程的父级。可以通过使用父组和/或名称调用的构造函数来创建ThreadGroup。要将线程添加到组中,我需要在线程的构造函数中指定该组。
public void test() {
ThreadGroup tg1 = new ThreadGroup("Thread-group-1");
ThreadGroup tg2 = new ThreadGroup(tg1, "Thread-group-2");
Thread thread1 = new Thread(tg1,"thread-1");
Thread thread2 = new Thread(tg2,"thread-2");
Thread thread3 = new Thread(tg2,"thread-3");
thread1.start();
thread2.start();
thread3.start();
Thread[] threads = new Thread[tg2.activeCount()];
tg2.enumerate(threads);
Arrays.asList(threads).forEach(t -> System.out.println(t.getName()));
tg1.list();
}
可以通过调用enumerate方法来遍历线程,该方法用组的线程引用,填充tg2数组。
可以通过使用线程组来实现线程池:
public class ThreadPool {
// Create a thread group field
private final ThreadGroup group = new ThreadGroup("ThreadPoolGroup");
// Create a LinkedList field containing Runnable
private final List<Runnable> tasks = new LinkedList<>();
public ThreadPool(int poolSize) {
// create several Worker threads in the thread group
for (int i = 0; i < poolSize; i++) {
var worker = new Worker(group, "worker-" + i);
worker.start();
}
}
private Runnable take() throws InterruptedException {
synchronized (tasks) {
// if the LinkedList is empty, we wait
while (tasks.isEmpty()) tasks.wait();
// remove the first job from the LinkedList and return it
return tasks.remove(0);
}
}
public void submit(Runnable job) {
// Add the job to the LinkedList and notifyAll
synchronized (tasks) {
tasks.add(job);
tasks.notifyAll();
}
}
public int getRunQueueLength() {
// return the length of the LinkedList
// remember to also synchronize!
synchronized (tasks) {
return tasks.size();
}
}
public void shutdown() {
// this should stop all threads in the group
group.interrupt();
}
private class Worker extends Thread {
public Worker(ThreadGroup group, String name) {
super(group, name);
}
public void run() {
// we run in an infinite loop:
while(true) {
// remove the next job from the linked list using take()
// we then call the run() method on the job
try {
take().run();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
}
线程局部变量
JavaThreadLocal类可用于创建其值只能由同一线程访问的变量。因此,即使两个线程正在执行相同的代码,并且该代码具有对相同ThreadLocal变量的引用,两个线程也无法看到彼此的ThreadLocal变量。
public class Main {
public static class ThreadLocalStorage {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void setName(String name) {
threadLocal.set(name);
}
public static String getName() {
return threadLocal.get();
}
}
public static void main(String[] args) {
ThreadLocalStorage.setName("Main thread");
Runnable runnable = () -> {
ThreadLocalStorage.setName(getCurrentThreadName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread: [" + getCurrentThreadName() + "] " +
"- value: [" + ThreadLocalStorage.getName() + "]");
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
System.out.println("Main exits");
}
private static String getCurrentThreadName() {
return Thread.currentThread().getName();
}
}
如果运行代码,我们可以看到每个线程都有其自己的ThreadLocal对象副本。
Main exits
Thread: [Thread-0] - ThreadLocal value: [Thread-0]
Thread: [Thread-1] - ThreadLocal value: [Thread-1]
而不是每个线程在a内都有其自己的值ThreadLocal,而是InheritableThreadLocal授予对线程以及该线程创建的所有子线程的值的访问权限。