@xtccc
2018-05-20T10:54:09.000000Z
字数 8927
阅读 2725
Java
目录
JVM允许一个Java app有若干个并发执行的线程。一个线程可以通过方法Thread#setDaemon(true)
被标记为daemon。
每个thread都有优先级,优先级越高的线程,越优先执行。当一个线程t1创建了新的新的线程t2时,t2有着与t1相同的优先级。并且,当且仅当t1是daemon thread时,t2才是daemon thread。
当JVM启动时,通常会有一个non-daemon thread(它会调用某个指定class的main方法)。JVM会一直运行直到以下事件发生:
Runtime#exit
方法被调用,并且security manager允许退出run
方法返回,或者抛出一个越过run
方法的异常)创建新的线程有两种方法:
方法1:创建一个java.io.Thread
的subclass,在其中实现run
方法,然后调用该subclass的start
方法
下面先创建一个名为ThreadClzA
的类:
public class ThreadClzA extends Thread {
private String title;
public ThreadClzA(String s) {
title = s;
System.out.println("创建ThreadClzA线程: title = " + title);
}
@Override
public void run(){
System.out.println("ThreadClzA线程 - " + title + " 进入`run`");
Random rand = new Random(System.currentTimeMillis());
for (int i=0; i < 5; i++) {
int seconds = rand.nextInt(10);
System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +
"我将沉睡 " + seconds + "秒 ... ");
try {
sleep(seconds*1000);
} catch (InterruptedException ex) {
System.out.println("ThreadClzA线程 " + title + " 发生异常");
}
}
System.out.println("ThreadClzA线程 - " + title + " 退出`run`");
}
}
然后,调用这个类实例的start
方法
System.out.println("进入main");
ThreadClzA t = new ThreadClzA("堂吉诃德");
t.start();
System.out.println("离开main");
运行输出为:
可见:
ThreadClzA#start
方法后,ThreadClzA#run
方法会立即运行;与此同时,创建该线程的non-daemon线程(即ThreadExamples#main
方法所属的线程)不会阻塞,这两个线程并发地运行ThreadExamples#main
方法退出),如果被创建的daemon线程还未运行完(ThreadClzA#run
方法还在运行),则JVM的主进程不会退出,而是会等待所有的non-daemon线程都运行完了才会退出。方法2:创建一个class,令其implements java.lang.Runnable
,并实现其中的run
方法,然后将该class的一个实例作为参数构造一个Thread
实例,并调用Thread#start
方法。
public class ThreadClzB implements Runnable {
private String title;
public ThreadClzB(String s) {
title = s;
System.out.println("创建ThreadClzB线程: title = " + title);
}
@Override
public void run() {
System.out.println("ThreadClzB线程 - " + title + " 进入`run`");
Random rand = new Random(System.currentTimeMillis());
for (int i=0; i < 5; i++) {
int seconds = rand.nextInt(10);
System.out.println("ThreadClzB线程 - " + title + ",第 " + i + " 次循环," +
"我将沉睡 " + seconds + "秒 ... ");
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException ex) {
System.out.println("ThreadClzB线程 " + title + " 发生异常");
}
}
System.out.println("ThreadClzB线程 - " + title + " 退出`run`");
}
}
启动这个线程时,用如下方法:
ThreadClzB t = new ThreadClzB("奥赛罗");
new Thread(t).start();
方法1与方法2的结构是非常相似的,daemon threads的行为也是相同的。实际上,应优先使用方法2,因为实现一个接口,比扩展一个基类的目的更加明确,也更加明了。
java.lang.Thread
也是基于 java.lang.Runnable
的:
public class Thread implements Runnable {
}
如果non-daemon线程调用了System.exit
或者Runtime#getRuntime#exit
,则它创建的所有daemon线程会立即终止:
// 这是main方法
System.out.println("进入main");
ThreadClzA t = new ThreadClzA("堂吉诃德");
t.start();
System.out.println("离开main");
System.exit();
运行结果如下:
通过方法Thread#setDaemon
,可以将一个thread设置为user thread或者daemon thread,user thread又称为non-daemon thread。
如果所有的user threads,都运行完毕了,那么JVM就会退出,即使仍然存在daemon threads在运行。
如果daemon threads抛出了异常,也可以在运行的界面上看到输出的异常信息。
System.out.println("进入main");
ThreadClzA t = new ThreadClzA("堂吉诃德");
t.setDaemon(true);
t.start();
Thread.sleep(15000);
System.out.println("离开main");
运行结果:
如果被创建的daemon thread抛出了异常,则它会立即结束,但是不会影响到创建它的主线程,主线程依然会正常运行到结束。
public class ThreadClzA extends Thread {
private String title;
public ThreadClzA(String s) {
title = s;
System.out.println("创建ThreadClzA线程: title = " + title);
}
@Override
public void run(){
System.out.println("ThreadClzA线程 - " + title + " 进入`run`");
Random rand = new Random(System.currentTimeMillis());
for (int i=0; i < 5; i++) {
/** 制造异常 */
if (3 ==i)
System.out.println(101/0);
int seconds = rand.nextInt(10);
System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +
"我将沉睡 " + seconds + "秒 ... ");
try {
sleep(seconds*1000);
} catch (InterruptedException ex) {
System.out.println("ThreadClzA线程 " + title + " 发生异常");
}
}
System.out.println("ThreadClzA线程 - " + title + " 退出`run`");
}
}
// main方法
System.out.println("进入main");
ThreadClzA t = new ThreadClzA("哈姆雷特");
t.start();
/** 等待40秒 */
for (int i=0; i < 5; i++) {
System.out.println("主线程 - 正在等待我创建的daemon thread, i = " + i);
}
System.out.println("离开main");
运行结果为
如果创建daemon threads的主线程抛出了异常,那么主线程会立即结束,但是被主线程创建的daemon thread仍然会正常运行到结束。
public class ThreadClzA extends Thread {
private String title;
public ThreadClzA(String s) {
title = s;
System.out.println("创建ThreadClzA线程: title = " + title);
}
@Override
public void run(){
System.out.println("ThreadClzA线程 - " + title + " 进入`run`");
Random rand = new Random(System.currentTimeMillis());
for (int i=0; i < 5; i++) {
int seconds = rand.nextInt(10);
System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +
"我将沉睡 " + seconds + "秒 ... ");
try {
sleep(seconds*1000);
} catch (InterruptedException ex) {
System.out.println("ThreadClzA线程 " + title + " 发生异常");
}
}
System.out.println("ThreadClzA线程 - " + title + " 退出`run`");
}
}
// main方法
System.out.println("进入main");
ThreadClzA t = new ThreadClzA("堂吉诃德");
t.start();
// 主线程抛出异常
System.out.println(101/0);
System.out.println("离开main");
运行结果为
主线程创建了线程t1,t1又创建了线程t2,t2抛出了异常,t2会终止运行,但是t1和主线程的运行不受影响。
当新建线程抛出异常且该异常未被捕获时,我们可以为该线程提供一个方法,它可以被uncaught exception触发。设置的方式是 Thread#setUncaughtExceptionHandler
,如下:
public class ThreadClzE extends Thread {
private String title;
public ThreadClzE(String s) {
title = s;
System.out.println("创建ThreadClzE线程: title = " + title);
}
@Override
public void run() {
System.out.println("ThreadClzE线程 - " + title + " 进入`run`");
Random rand = new Random(System.currentTimeMillis());
for (int i = 0; i < 8; i++) {
/** 制造一个 divide by zero 异常 */
if (3 == i) {
int x = 102;
int y = 0;
System.out.println(x / y);
}
int seconds = rand.nextInt(5);
System.out.println("ThreadClzE线程 - " + title + ",第 " + i + " 次循环," +
"我将沉睡 " + seconds + "秒 ... ");
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException ex) {
System.out.println("ThreadClzE线程 " + title + " sleep发生异常");
}
}
System.out.println("ThreadClzE线程 - " + title + " 退出`run`");
}
}
System.out.println("进入main");
ThreadClzE t = new ThreadClzE("夏洛克");
/** 提供一个异常处理函数,当被创建的线程抛出未被捕获的异常时,
* 该方法将被调用一次,然后线程退出
* */
t.setUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("这是我们自定义的异常处理函数");
}
}
);
t.start();
System.out.println("即将退出main");
运行结果为:
通过ExecutorService
可以创建并使用线程池。
public class BasicUsage {
public static void main(String[] args) throws InterruptedException {
TestExecute();
}
/**
*
* */
public static void TestExecute() throws InterruptedException {
/**
* 创建一个大小固定的线程池(容纳3个active thread)
* 加入5个线程,其中3个线程会立即调度运行,剩下的2个线程
* 将等待,直到线程池中由空余的slot可以让它们运行
* */
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i=1; i<= 5; i++) {
Runnable worker = new Worker("worker" + i);
executor.execute(worker); // 也可以是executor.submit(worker);
}
/**
* 调用`shutdown()`,会等待所有的线程运行完毕,而不会立即结束正在运行的线程
* 如果希望让executor中所有正在运行的线程立即结束,应该调用`shutdownNow()`
* 在调用`shutdown()`之后,不能再向executor中加入新的线程了,否则会报异常
* */
executor.shutdown();
/**
* 如果不调用`shutdown()`,那么即使所有的线程都运行完毕了,
* `isTerminated()`也仍然会返回false
* */
while (!executor.isTerminated()) {
Thread.sleep(3*1000);
System.out.println("***** 等待所有的线程结束 *****");
}
System.out.println("\nAll threads stopped .");
}
/**
* 线程实例
* */
public static class Worker implements Runnable {
public String title;
public Worker(String title) {
this.title = title;
}
@Override
public void run() {
System.out.println(title + " 开始运行 ");
long seconds = new Random(System.currentTimeMillis()).nextInt(4)*1000;
try {
Thread.sleep(seconds);
System.out.println(title + " 运行结束 ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果为:
如果某个线程出现了异常未被捕获,这个线程会立即终止,但不影响线程池中其他线程的运行。
public class HandleExceptions {
public static void main(String[] args) throws InterruptedException {
TestThreadExceptions();
}
public static void TestThreadExceptions() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new Bean("beanA"));
executor.execute(new Bean("beanB"));
executor.execute(new Bean("beanC"));
executor.execute(new Bean("beanD"));
executor.shutdown();
while (!executor.isTerminated()) {
Thread.sleep(2*1000);
System.out.println("***** 等待所有的线程结束 *****");
}
System.out.println("\nAll threads stopped .");
}
public static class Bean implements Runnable {
public String title;
public Bean(String title) {
this.title = title;
}
@Override
public void run() {
System.out.println(title + " 开始运行 ");
long seconds = new Random(System.currentTimeMillis()).nextInt(8)*1000;
try {
Thread.sleep(seconds);
/** 只让一个线程抛出异常,其他线程正常运行
* */
if (title.equals("beanA"))
System.out.println(100/0);
System.out.println(title + " 运行结束 ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果为:
Java线程的终止——interrupt
没有完美的方案,我目前的实现是在关键点抛出异常。
volatile关键字,简而言之有以下几个作用:
解决多线程并发时的 Variable Visibility Problem
当一个线程修改了一个volatile variable后(实际上是在CPU cache中修改的),该变量的最新值会被立即写回到main memory,这样它的最新值对其他线程是可见的。
提供Full volatile Visibility Guarantee
If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.
If Thread A reads a volatile variable, then all variables visible to Thread A when reading the volatile variable will also be re-read from main memory.
实现 Happens-Before Guarantee
这与JVM和CPU的自动优化语句执行顺序有关