线程状态的转换
新建(New)
创建后未启动。
可运行(Runable)
可能正在运行,也可能等待CPU时间片。
阻塞(Blocking)
等待获取一个排它锁,如果其他线程释放了锁,就会结束该状态。
无限期等待(Waiting)
等待其他线程显式的唤醒,否则不会被分配时间片。
限期等待(Timed Waiting)
无需等待其他线程显式地唤醒,在一定时间之后被系统自动唤醒。
死亡(Terminated)
可以是线程结束任务之后自己结束,或产生异常而结束。
线程使用方式
有三种使用线程的方法:
- 实现 Runnable 接口;
- 实现 Callable 接口;
- 继承 Thread 类。
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
1.实现 Runnable 接口
class MyRunable implements Runnable {
@Override
public void run() {
System.out.println("hello world!");
}
public static void main(String[] args) throws InterruptedException {
MyRunable myRunable = new MyRunable();
Thread thread = new Thread(myRunable);
thread.start();
thread.join();
}
}
2. 实现 Callable 接口
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello World!";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
3. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello World!");
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
myThread.join();
}
}
实现接口 VS 继承 Thread 类
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
基础线程机制
Executor
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
主要有三种 Executor:
- CachedThreadPool: 一个任务创建一个线程;
- FixedThreadPool: 所有任务只能使用固定大小的线程;
- SingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool。
public class Solution {
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
final int I = i;
newCachedThreadPool.execute(() -> {
System.out.println("hello world! "+I);
});
}
newCachedThreadPool.shutdown();
}
}
Daemon
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程。
public class Solution {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("hello world!");
});
thread.setDaemon(true);
}
}
sleep()
Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。
sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
public static void main(String[] args) throws InterruptedException {
Thread.sleep(3000);
System.out.println("hello world!");
}
yield()
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
public static void main(String[] args) throws InterruptedException {
Thread thread_01 = new Thread(() -> {
System.out.println(1);
Thread.yield();
System.out.println(3);
});
Thread thread_02 = new Thread(() -> {
System.out.println(2);
Thread.yield();
System.out.println(4);
});
thread_01.start();
thread_02.start();
thread_01.join();
thread_02.join();
}
线程中断
一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。
InterruptedException
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println("hello");
} catch (InterruptedException e) {
System.out.println("我被干掉了");
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();
thread.join();
}
interrupted()
如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。
但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
class MyThread extends Thread {
@Override
public void run() {
while (!interrupted()) {
System.out.println("没有被打断");
}
System.out.println("完蛋了!");
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
myThread.interrupt();
myThread.join();
}
}
Executor 的中断操作
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
public static void main(String[] args) throws InterruptedException {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(() -> {
try {
Thread.sleep(3000);
System.out.println("hello world");
} catch (InterruptedException e) {
System.out.println("被中断喽");
}
});
newCachedThreadPool.shutdownNow();
}
如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<Object> submit = executorService.submit(() -> {
for(int i=0;i<100000000;i++){
System.out.println(i);
}
return null;
});
submit.cancel(true);
System.out.println("hello");
}
线程互斥同步
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。
synchronized
1. 同步一个代码块
class Test {
public void func() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
Test test = new Test();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
test.func();
});
executorService.execute(() -> {
test.func();
});
executorService.shutdown();
}
}
它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步:
class Test {
public void func() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
Test test = new Test();
Test test2 = new Test();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
test.func();
});
executorService.execute(() -> {
test2.func();
});
executorService.shutdown();
}
}
2. 同步一个方法
class Test {
public synchronized void func() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
Test test = new Test();
Test test2 = new Test();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
test.func();
});
executorService.execute(() -> {
test2.func();
});
executorService.shutdown();
}
}
它和同步代码块一样,作用于同一个对象。
3. 同步一个类
class Test {
public void func() {
synchronized (Test.class) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
Test test = new Test();
Test test2 = new Test();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
test.func();
});
executorService.execute(() -> {
test2.func();
});
executorService.shutdown();
}
}
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
4. 同步一个静态方法
class Test {
public static synchronized void func() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
Test.func();
});
executorService.execute(() -> {
Test.func();
});
executorService.shutdown();
}
}
作用于整个类。
ReentrantLock
ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。
class Test {
private Lock lock;
{
lock = new ReentrantLock();
}
public void func() {
lock.lock();
try {
for(int i=0;i<10;i++){
System.out.println(i);
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Test test = new Test();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> test.func());
executorService.execute(() -> test.func());
executorService.shutdown();
}
}
比较
1. 锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
2. 性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
3. 等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
4. 公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
5. 锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
使用选择
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
线程之间的协作
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
(这里就不掩饰了,经常用的)
wait() notify() notifyAll()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
它们都属于 Object 的一部分,而不属于 Thread。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。
使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
class Test {
public synchronized void before() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
System.out.println("被打断lou");
}
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
public static void main(String[] args) {
Test test = new Test();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(() -> test.after());
newCachedThreadPool.execute(() -> test.before());
newCachedThreadPool.shutdown();
}
}
(以上代码有死锁的风险,如果是after函数先执行,那没有问题;如果是before先执行,就证明了after没有拿到锁,等before释放锁之后,after拿到锁,然后进入休眠状态,然后也没有人唤醒它)
wait() 和 sleep() 的区别
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法
- wait() 会释放锁,sleep() 不会
await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
class Test {
private Lock lock;
private Condition condition;
{
lock = new ReentrantLock();
condition = lock.newCondition();
}
public void before() {
lock.lock();
try {
Thread.sleep(1500);
System.out.println("before");
} catch (InterruptedException e) {
System.out.println("被打断lou");
}
finally{
condition.signalAll();
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
lock.unlock();
}
}
public static void main(String[] args) {
Test test = new Test();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(() -> test.after());
newCachedThreadPool.execute(() -> test.before());
newCachedThreadPool.shutdown();
}
}
(和上文一下,也是会有死锁的风险,虽然我没有测出来,但是理论上来说,是这样的)
2024.11.03
writeBy kaiven