创建线程的两种方式:
- 继承Thread类;
- 实现Runnable接口。
- 继承Thread类:
public class MyThread extends Thread {
public void run() {
System.out.println("My thread.");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
- 当一个类已经继承了其他类时,就只能实现Runnable接口:
public class MyThread extends OtherClass implements Runnable {
@Override
public void run() {
System.out.println("my thread");
}
public static void main(String[] args) {
// 先创建一个Thread实例,再传入自己的MyThread实例
MyThread myThread = new MyThread();
new Thread(myThread).start();
}
}
Thread类实现了Runnable接口。
public class Thread implements Runnable {
private volatile String name;
private int priority;
...
}
当线程有返回值时,必须实现Callable接口,执行Callable任务后返回Future对象,调用Future对象的get()方法获取返回值:
public class MyThread2 implements Callable {
private int i;
MyThread2(int i) {
this.i = i;
}
@Override
public Object call() throws Exception {
return i * i * i;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
int taskSize = 10;
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
List<Future> list = new ArrayList<>();
for (int i = 0; i < taskSize; i++) {
Callable callable = new MyThread2(i);
Future future = pool.submit(callable);
list.add(future);
}
pool.shutdown();
for (Future future : list) {
System.out.println(future.get().toString());
}
}
}
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
参见 >> Java 四种线程池的用法分析
Java提供的4种线程池:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSchuduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool
public class MyThread4 {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
final int index = i;
try {
Thread.sleep(i * 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.execute(() -> System.out.println(Thread.currentThread().getName() + " " + index));
}
}
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-1 3
pool-1-thread-1 4
pool-1-thread-1 5
pool-1-thread-1 6
pool-1-thread-1 7
pool-1-thread-1 8
pool-1-thread-1 9
newFixedThreadPool
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int x = i;
pool.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + x);
});
}
任务数超出最大线程数,超出的线程会等待:
pool-1-thread-2 1
pool-1-thread-3 2
pool-1-thread-1 0
pool-1-thread-2 3
pool-1-thread-1 5
pool-1-thread-3 4
pool-1-thread-2 6
pool-1-thread-1 7
pool-1-thread-3 8
pool-1-thread-2 9
newSchuduledThreadPool
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// 延迟 1 秒后每 3 秒执行一次
pool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()), 1, 3, TimeUnit.SECONDS);
newSingleThreadPool
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
pool.execute(() -> System.out.println(Thread.currentThread().getName() + " " + index));
}
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-1 3
pool-1-thread-1 4
pool-1-thread-1 5
pool-1-thread-1 6
pool-1-thread-1 7
pool-1-thread-1 8
pool-1-thread-1 9
线程的生命周期:
新建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标志位:
public class MyRunnable implements Runnable { public volatile boolean flag = true; @Override 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
0 创建 2 创建 1 创建 ------- 2 终止 0 终止 1 终止
使用interrupt方式
线程处于阻塞状态时:
如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过break 来跳出循环,才能正常结束 run 方法。
public class MyThread5 extends Thread { @Override 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,和使用自定义的标志来控制循环是一样的道理。public 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:线程的礼让指的是先将线程的资源让出去,让别的线程先执行。
public class ThreadMethod extends Thread {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int x = 0; x < 10; x++) {
if (x % 3 == 0) {
Thread.yield(); //线程礼让
System.out.println("玩耍线程礼让执行 ***********************");
}
System.out.println(Thread.currentThread().getName() + "、 x = " + x);
}
}, "玩耍的线程");
thread.start();
for (int x = 0; x < 10; x++) {
System.out.println("【霸道的main主线程】 x = " + x);
}
}
}
output
玩耍线程礼让执行 ***********************
【霸道的main主线程】 x = 0
【霸道的main主线程】 x = 1
【霸道的main主线程】 x = 2
【霸道的main主线程】 x = 3
【霸道的main主线程】 x = 4
【霸道的main主线程】 x = 5
【霸道的main主线程】 x = 6
【霸道的main主线程】 x = 7
【霸道的main主线程】 x = 8
【霸道的main主线程】 x = 9
玩耍的线程、 x = 0
玩耍的线程、 x = 1
玩耍的线程、 x = 2
玩耍线程礼让执行 ***********************
玩耍的线程、 x = 3
玩耍的线程、 x = 4
玩耍的线程、 x = 5
玩耍线程礼让执行 ***********************
玩耍的线程、 x = 6
玩耍的线程、 x = 7
玩耍的线程、 x = 8
玩耍线程礼让执行 ***********************
玩耍的线程、 x = 9
join: 线程强制执行,指的当满足某些条件之后,某一个线程对象将一直可以独占资源,一直到该线程的程序执行结束。
public class ThreadMethod extends Thread {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread(); //获得主线程
Thread thread = new Thread(() -> {
for (int x = 0; x < 10; x++) {
if (x == 3) { //如果x == 3,就强制执行主线程
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "、 x = " + x);
}
}, "玩耍的线程");
thread.start();
for (int x = 0; x < 10; x++) {
Thread.sleep(100L);
System.out.println("【霸道的main主线程】 x = " + x);
}
}
}
output
玩耍的线程、 x = 0
玩耍的线程、 x = 1
玩耍的线程、 x = 2
【霸道的main主线程】 x = 0
【霸道的main主线程】 x = 1
【霸道的main主线程】 x = 2
【霸道的main主线程】 x = 3
【霸道的main主线程】 x = 4
【霸道的main主线程】 x = 5
【霸道的main主线程】 x = 6
【霸道的main主线程】 x = 7
【霸道的main主线程】 x = 8
【霸道的main主线程】 x = 9
玩耍的线程、 x = 3
玩耍的线程、 x = 4
玩耍的线程、 x = 5
玩耍的线程、 x = 6
玩耍的线程、 x = 7
玩耍的线程、 x = 8
玩耍的线程、 x = 9
sleep: 休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在有多个线程对象,休眠也是有多个先后顺序的。
wait和notify:
- synchronized关键词用于互斥访问。
- 为了让一个方法是同步的,简单的添加synchronized关键词到它们的声明上。那么同一个对象上没有两个同步方法的调用可以相互交错。
- 同步语句必须指定提供内在锁的对象。当使用synchronized(this)时候,必须避免使用同步其他对象方法的调用。
- wait()告诉调用的线程放弃监视器和进入休眠状态,直到其他线程进入相同的监视器和调用notify()。
- notify()唤醒在相同对象上第一个调用wait()的线程。
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();
b.start();
synchronized (b) {
try {
System.out.println("Waiting for b to complete...");
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Total is: " + b.total);
}
}
}
class ThreadB extends Thread {
int total;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
total += i;
}
notify();
}
}
}