线程
创建线程
方法一:从Thread
派生一个自定义类,然后覆写run()
方法:
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 启动新线程
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
方法二:创建Thread
实例时,传入一个Runnable
实例:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动新线程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
方法三:创建Thread
实例时,传入一个Runnable
实例,但是这个Runnable
实例由FutureTask
包装Callable
得来:
public class Demo implements Callable<String>{
public String call() throws Exception {
System.out.println("正在执行新建线程任务");
Thread.sleep(2000);
return "结果";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Demo d = new Demo();//准备Callable
FutureTask<String> task = new FutureTask<>(d);//包装Callable
Thread t = new Thread(task);//传入Runnable
t.start();
//获取任务执行后返回的结果
String result = task.get();
}
}
方法四:使用线程池
public class Demo {
public static void main(String[] args) {
Executor threadPool = Executors.newFixedThreadPool(5);
for(int i = 0 ;i < 10 ; i++) {
threadPool.execute(new Runnable() {
public void run() {
//todo
}
});
}
}
}
线程的状态
NEW
新创建,尚未运行;
public class Main {
public static void main(String[] args) {
//我的线程
Thread me = new Thread(() -> {
doSleep(1000);
});
System.out.println(me.getState());//NEW
me.start();
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
RUNNABLE
运行中,正在执行run()
方法;
public class Main {
public static void main(String[] args) {
//我的线程
Thread me = new Thread(() -> {
doSleep(1000);
});
System.out.println(me.getState());//NEW
me.start();
System.out.println(me.getState());//RUNNABLE
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BLOCKED
运行中,阻塞等待 Monitor 锁,如synchronized
;
public class Main {
public static void main(String[] args) {
//Monitor锁
Object obj = new Object();
//另一个线程
Thread another = new Thread(() -> {
synchronized (obj) {
System.out.println("another enter synchronized");
doSleep(1000);
}
});
//我的线程
Thread me = new Thread(() -> {
synchronized (obj) {
System.out.println("me enter synchronized");
doSleep(1000);
}
});
another.start();
doSleep(100);
System.out.println(me.getState());//NEW
me.start();
doSleep(100);
System.out.println(me.getState());//BLOCKED
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WAITING
运行中,等待另一个线程执行一个特定动作,如Object.wait()
,Thread.join()
,LockSupport.park()
。
例子1:如果当前线程调用 obj.wait(),则它在等待另一个线程调用 obj.notify() 或 obj.notifyAll()。
public class Main {
public static void main(String[] args) {
//Monitor锁
Object obj = new Object();
//我的线程
Thread me = new Thread(() -> {
synchronized (obj) {
println("me enter synchronized");//①
try {
obj.wait();//释放 Monitor锁,等待唤醒
} catch (InterruptedException e) {
println("catch exception");
}
println("me wake up");//⑤
}
}, "me");
//另一个线程
Thread another = new Thread(() -> {
synchronized (obj) {
println("another enter synchronized");//②
println("me state:" + me.getState());//③WAITING
obj.notifyAll();//notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。
doSleep(100);//暂停一会me也不会先唤醒
println("another exit synchronized");//④
}
}, "another");
me.start();
doSleep(100);//让me有时间获取obj锁
another.start();
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void println(String msg) {
System.out.printf("[%s] %s\n", Thread.currentThread().getName(), msg);
}
}
输出:
[me] me enter synchronized
[another] another enter synchronized
[another] me state:WAITING
[another] another exit synchronized
[me] me wake up
例子2:如果当前线程调用 thread.join,则它在等待 thread 终止。
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread me = new Thread(() -> {
doSleep(1000);
});
Thread main_thread = Thread.currentThread();
Thread another = new Thread(() -> {
doSleep(100);//让 main_thread 有时间调用 me.join()
System.out.println(main_thread.getState());//WAITING
});
me.start();
another.start();
me.join();
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TIMED_WAITING
运行中,等待另一个线程执行一个特定动作,但有超时时间,如Thread.sleep(long)
,Object.wait(long)
,Thread.join(long)
,LockSupport.parkNanos(long)
,LockSupport.parkUntil(long)
。
例子:Thread.sleep(long)
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread me = new Thread(() -> {
doSleep(1000);
});
me.start();
doSleep(100);//让me有时间执行sleep
System.out.println(me.getState());//TIMED_WAITING
}
private static void doSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
例子:Object.wait(long)
和例子:Thread.join(long)
,只要在WAITTING例子
基础上加上超时时间即可。
TERMINATED
已终止。
线程终止的原因有:
- 线程正常终止:
run()
方法执行到return
语句返回; - 线程意外终止:
run()
方法因为未捕获的异常导致线程终止; - 对某个线程的
Thread
实例调用stop()
方法强制终止(强烈不推荐使用)。
中断线程
方法1:Thread.interrupt()
(1)如果线程处于阻塞状态(如WAITING、TIMED_WAITING,但是LockSupport.park()除外)或者试图执行一个阻塞操作,此时中断该线程,将会抛出一个InterruptedException异常,同时中断状态会被复位(由中断状态改为非中断状态)。如果先中断线程,再执行线程sleep也会抛出异常。
(2)如果线程处于非阻塞状态,此时中断该线程不会得到任何响应。(但设置了中断状态)
(3)线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它就保存等待,即使调用中断线程的方法,也不会生效。
这样设计线程:
class HelloThread extends Thread {
public void run() {
int n = 0;
//关键:不能是while(true);否则如果在(1)处线程被中断,则n++会再被执行一次,接着才会进入catch块。
//而isInterrupted()会返回true,可以提前知晓线程已经被中断,从而退出while循环。
while (!isInterrupted()) {
n++;
System.out.println(n + " hello!");
//关键:sleep时被中断,如果不捕获异常,则会被抛出去
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//关键:如果这里不跳出循环,则while()会一直返回true,死循环
//因为它的“中断状态”会被清除
break;
}//(1)
}
}
}
方法2:设置标志位
public class Main {
public static void main(String[] args) throws InterruptedException {
HelloThread t = new HelloThread();
t.start();
Thread.sleep(1);
t.running = false; // 标志位置为false
}
}
class HelloThread extends Thread {
public volatile boolean running = true;
public void run() {
int n = 0;
while (running) {
n ++;
System.out.println(n + " hello!");
}
System.out.println("end!");
}
}
注意到HelloThread
的标志位boolean running
是一个线程间共享的变量。线程间共享变量需要使用volatile
关键字标记,确保每个线程都能读取到更新后的变量值。
如果我们去掉volatile
关键字,运行上述程序,发现效果和带volatile
差不多,这是因为在x86的架构下,JVM回写主内存的速度非常快,但是,换成ARM的架构,就会有显著的延迟。
判断线程中断位
Thread.interrupted()
,返回当前线程的中断位,并清除中断位。
mythread.isInterrupted()
,返回mythread线程的中断位,不会清除中断位。
守护线程
如果有一个线程没有退出,JVM进程就不会退出。
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
如何创建守护线程呢?方法和普通线程一样,只是在调用start()
方法前,调用setDaemon(true)
把该线程标记为守护线程:
Thread t = new MyThread();
t.setDaemon(true);
t.start();
线程同步
synchronized
原子操作
JVM规范定义了几种原子操作:
- 基本类型赋值(
long
和double
除外),例如:int n = m
; - 引用类型赋值,例如:
List list = anotherList
。
long
和double
是64位数据,JVM没有明确规定64位赋值操作是不是一个原子操作,不过在x64平台的JVM是把long
和double
的赋值作为原子操作实现的。
常用并发集合
java.util.concurrent
包也提供了对应的并发集合类:
接口 | 非线程安全 | 线程安全 |
---|---|---|
List | ArrayList | CopyOnWriteArrayList |
Map | HashMap | ConcurrentHashMap |
Set | HashSet/TreeSet | CopyOnWriteArraySet |
Queue | ArrayDeque/LinkedList | ArrayBlockingQueue/LinkedBlockingQueue |
Deque | ArrayDeque/LinkedList | LinkedBlockingQueue |
java.util.Collections
工具类还提供了一个旧的线程安全集合转换器,可以这么用:
Map unsafeMap = new HashMap();
Map threadSafeMap = Collections.synchronizedMap(unsafeMap);
原子操作封装类
java.util.concurrent.atomic
包下封装了诸如AtomicInteger
类。
普通线程池
接口:ExecutorService
。
实现类:
- FixedThreadPool:线程数固定的线程池。
- CachedThreadPool:线程数根据任务动态调整的线程池。
- SingleThreadExecutor:仅单线程执行的线程池。
- ScheduledThreadPool:定期反复执行任务。
使用例子:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
es.submit(new Task("" + i));
}
// 关闭线程池,已经执行的任务会继续,未执行的任务不再执行。本方法不阻塞
es.shutdown();
}
}
class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("start task " + name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("end task " + name);
}
}
输出:
start task 0
start task 1
end task 1
end task 0
start task 2
end task 2
Future
(1)Runnable
接口有个问题,它的方法没有返回值。如果任务需要一个返回结果,那么只能保存到变量,还要提供额外的方法读取,非常不便。所以,Java标准库还提供了一个Callable
接口,和Runnable
接口比,它多了一个返回值:
class Task implements Callable<String> {
public String call() throws Exception {
return longTimeCalculation();
}
}
(2)现在的问题是,如何获得异步执行的结果?
如果仔细看ExecutorService.submit()
方法,可以看到,它返回了一个Future
类型,一个Future
类型的实例代表一个未来能获取结果的对象:
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 定义任务:
Callable<String> task = () -> {
Thread.sleep(1000);
return "hello";
};
// 提交任务并获得Future:
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果:
String result = future.get();// 可能阻塞
// 关闭线程池,否则程序会阻塞
executor.shutdown();
(3)一个Future
接口表示一个未来可能会返回的结果,它定义的方法有:
get()
:获取结果(可能会等待)get(long timeout, TimeUnit unit)
:获取结果,但只等待指定的时间;cancel(boolean mayInterruptIfRunning)
:取消当前任务;isDone()
:判断任务是否已完成。
(4)另一个使用Future
的例子,FutureTask
实现了Future
和Runnable
。
//定义任务
Callable<String> callable = ()->{
Thread.sleep(1000);
return "ok";
};
//创建线程
FutureTask<String> futureTask = new FutureTask<>(callable);
//启动线程
new Thread(futureTask).start();
//阻塞获取线程返回结果
try {
String result = futureTask.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
异步回调CompletableFuture
使用Future
获得异步执行结果时,要么调用阻塞方法get()
,要么轮询看isDone()
是否为true
,这两种方法都不是很好,因为主线程也会被迫等待。
从Java 8开始引入了CompletableFuture
,它针对Future
做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
我们以获取股票价格为例,看看如何使用CompletableFuture
:
public class Main {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}
static Double fetchPrice() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}
未深入研究,待完善。
Fork/Join线程池
Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。
我们来看如何使用Fork/Join对大数据进行并行求和:
import java.util.Random;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
// 创建2000个随机数组成的数组:
long[] array = new long[2000];
long expectedSum = 0;
for (int i = 0; i < array.length; i++) {
array[i] = random();
expectedSum += array[i];
}
System.out.println("Expected sum: " + expectedSum);
// fork/join:
ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
long startTime = System.currentTimeMillis();
Long result = ForkJoinPool.commonPool().invoke(task);
long endTime = System.currentTimeMillis();
System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
static Random random = new Random(0);
static long random() {
return random.nextInt(10000);
}
}
class SumTask extends RecursiveTask<Long> {
static final int THRESHOLD = 500;
long[] array;
int start;
int end;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 如果任务足够小,直接计算:
long sum = 0;
for (int i = start; i < end; i++) {
sum += this.array[i];
// 故意放慢计算速度:
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
return sum;
}
// 任务太大,一分为二:
int middle = (end + start) / 2;
System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
// “分裂”子任务:
SumTask subtask1 = new SumTask(this.array, start, middle);
SumTask subtask2 = new SumTask(this.array, middle, end);
// invokeAll会并行运行两个子任务:
invokeAll(subtask1, subtask2);
// 获得子任务的结果:
Long subresult1 = subtask1.join();
Long subresult2 = subtask2.join();
// 汇总结果:
Long result = subresult1 + subresult2;
System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
return result;
}
}
输出:
Expected sum: 9788366
split 0~2000 ==> 0~1000, 1000~2000
split 0~1000 ==> 0~500, 500~1000
split 1000~2000 ==> 1000~1500, 1500~2000
result = 2391591 + 2419573 ==> 4811164
result = 2485485 + 2491717 ==> 4977202
result = 4811164 + 4977202 ==> 9788366
Fork/join sum: 9788366 in 1248 ms.
ThreadLocal
Java标准库提供了一个特殊的ThreadLocal
,它可以在一个线程中传递同一个对象。
ThreadLocal
适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)。
每个Thread
有多个ThreadLocal
,用于存储不同数据。
每个线程都有一个ThreadLocalMap
变量:threadLocals
,键是ThreadLocal
,值是Object
。
例子:
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
process("Alice");
});
t1.start();
// Thread t2 = new Thread(() -> {
// process("Bob");
// });
// t2.start();
}
// 每个 Thread 有多个 ThreadLocal,用于存储不同数据
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static void process(String name) {
msg("enter process");
threadLocal.set(name);
f1();
threadLocal.remove();
msg("exit process");
}
static void f1() {
msg("enter f1()");
String name = threadLocal.get();
msg("name=" + name);
f2();
msg("exit f1()");
}
static void f2() {
msg("enter f2()");
String name = threadLocal.get();
msg("name=" + name);
msg("exit f2()");
}
static void msg(String s) {
System.out.printf("[%s] %s\n", Thread.currentThread().getName(), s);
}
}
输出:
[Thread-0] enter process
[Thread-0] enter f1()
[Thread-0] name=Alice
[Thread-0] enter f2()
[Thread-0] name=Alice
[Thread-0] exit f2()
[Thread-0] exit f1()
[Thread-0] exit process