创建线程的两种方式:
- 继承Thread类;
- 实现Runnable接口。
- 继承Thread类:
1 | public class MyThread extends Thread { |
- 当一个类已经继承了其他类时,就只能实现Runnable接口:
1 | public class MyThread extends OtherClass implements Runnable { |
Thread类实现了Runnable接口。
1 | public class Thread implements Runnable { |
当线程有返回值时,必须实现Callable接口,执行Callable任务后返回Future对象,调用Future对象的get()方法获取返回值:
1 | public class MyThread2 implements Callable { |
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
参见 >> Java 四种线程池的用法分析
Java提供的4种线程池:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSchuduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool
1 | public class MyThread4 { |
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
1 | pool-1-thread-1 0 |
newFixedThreadPool
1 | ExecutorService pool = Executors.newFixedThreadPool(3); |
任务数超出最大线程数,超出的线程会等待:
1 | pool-1-thread-2 1 |
newSchuduledThreadPool
1 | ScheduledExecutorService pool = Executors.newScheduledThreadPool(3); |
newSingleThreadPool
1 | ExecutorService pool = Executors.newSingleThreadExecutor(); |
1 | pool-1-thread-1 0 |
线程的生命周期:
新建New:JVM分配内存,并初始化成员变量的值;
就绪Runnable:创建方法调用栈和程序计数器;
运行 Running:获得CPU,执行run()方法;
阻塞 Blocked:让出CPU时间片,停止运行;
死亡 Dead:正常结束,异常结束或调用stop()方法。
wait()和sleep()的关键的区别在于,sleep()方法属于Thread类,wait()方法属于Object类,wait()是用于线程间通信的,而sleep()是用于短时间暂停当前线程。更加明显的一个区别在于,当一个线程调用wait()方法的时候,会释放它锁持有的对象的管程和锁,但是调用sleep()方法的时候,不会释放他所持有的管程。
而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
终止线程的3种方式 > Java中如何优雅正确的终止线程
使用stop()方法
thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。
使用volatile标志位:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class MyRunnable implements Runnable {
public volatile boolean flag = true;
public void run() {
System.out.println(Thread.currentThread().getName() + "创建");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (flag) {
}
System.out.println(Thread.currentThread().getName() + "终止");
}
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(myRunnable, i + " ");
thread.start();
}
Thread.sleep(2000L);
System.out.println("-------");
myRunnable.flag = false;
}
}output
1
2
3
4
5
6
70 创建
2 创建
1 创建
-------
2 终止
0 终止
1 终止使用interrupt方式
线程处于阻塞状态时:
如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过break 来跳出循环,才能正常结束 run 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class MyThread5 extends Thread {
public void run() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThread5 thread5 = new MyThread5();
thread5.start();
boolean interrupted = thread5.isInterrupted();
if (!interrupted) {
thread5.interrupt();
}
}
}
output
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at ch4.MyThread5.run(MyThread5.java:12)线程未处于阻塞状态:
使用 isInterrupted()判断线程的中断标志来退出循环。当使用
interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class MyThread5 extends Thread {
public void run() {
super.run();
try {
System.out.println("线程开始。。。");
Thread.sleep(200000);
System.out.println("线程结束。");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:" + this.isInterrupted());
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThread5 thread5 = new MyThread5();
thread5.start();
thread5.interrupt();
}
}
output
线程开始。。。
在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:false
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at ch4.MyThread5.run(MyThread5.java:13)
线程基本方法:
yield:线程的礼让指的是先将线程的资源让出去,让别的线程先执行。
1 | public class ThreadMethod extends Thread { |
output
1 | 玩耍线程礼让执行 *********************** |
join: 线程强制执行,指的当满足某些条件之后,某一个线程对象将一直可以独占资源,一直到该线程的程序执行结束。
1 | public class ThreadMethod extends Thread { |
output
1 | 玩耍的线程、 x = 0 |
sleep: 休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在有多个线程对象,休眠也是有多个先后顺序的。
wait和notify:
- synchronized关键词用于互斥访问。
- 为了让一个方法是同步的,简单的添加synchronized关键词到它们的声明上。那么同一个对象上没有两个同步方法的调用可以相互交错。
- 同步语句必须指定提供内在锁的对象。当使用synchronized(this)时候,必须避免使用同步其他对象方法的调用。
- wait()告诉调用的线程放弃监视器和进入休眠状态,直到其他线程进入相同的监视器和调用notify()。
- notify()唤醒在相同对象上第一个调用wait()的线程。
1 | public class ThreadA { |