一、创建线程
-
实现 Runnable 接口
- 首先通过RunnableThread 类实现 Runnable 接口
- 然后重写了 run() 方法
- 之后只要把这个实现了 run() 方法的实例传到Thread 类中去就可以实现多线程
public class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println("用实现Runnable接口实现线程");
}
}
-
继承 Thread 类
没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法
public class ExtendsThread extends Thread {
@Override
public void run() {
System.out.println(“用Thread类实现线程");
}
}
-
线程池创建线程
- 默认是采用DefaultThreadFactory
- 它会给我们线程池创建的线程设置一些默认的值,比如它的名字,它是不是守护线程,以及它的优先级
-
有返回值的 Callable
实现了callable接口,并且给它的泛型设置成 integer,然后它会返回一个随机数回来
class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());
-
定时器Timer
TimerTask的实现了Runnable接口,Timer内部有个TimerThread继承自Thread因此绕回来还是Thread +
class TimerThread extends Thread {
//具体实现
}
-
匿名内部类
它不是传入一个已经实例好的runnable对象,而是直接在这边去用一个匿名内部类的方式把需要传入的runnable给实现出来
/**
*描述:匿名内部类创建线程
*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
总结:透过现象看本质,创建线程只有一种方式: 构造Thread类
- 实现Runnable接口的run 方式,并把Runnable实例作为target对象,传给Thread类,最终调用target.run()
- 继承Thread类,重写Thread的run方法(),Thread.start()会执行run()
- 实现runnable接口 要比继承thread类要 更好:
- 可以把不同的内容进行解耦,权责分明
- 某些情况下可以提升性能,减小开销
- 继承 Thread 类相当于限制了代码未来的可扩展性
二、停止线程
-
原理介绍
通常情况下我们不会手动停止一个线程,而是允许线程运行到整个进程结束,然后让它自然停止,但依然会有许多特殊的情况 需要我们提前停止线程。比如:用户突然关闭程序或程序运行出错重启
这种情况下,即将停止的线程在很多业务场景下仍然很有价值,但是Java 并没有提供简单易用,能够直接安全停止线程的能力 -
为什么不强制停止?而是通知、协作
对 Java 而言,最正确的停止线程的方式是使用 interrupt,但 interrupt 仅仅起到通知被停止线程的作用。
对于被停止的线程而言,它拥有完全的自主权,既可以选择立即停止,也可以选择一段时间后停止。
那么为什么 Java 不提供强制停止线程的能力呢?
Java 希望程序间能够相互通知、相互协作的管理线程
比如:
线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据错乱,不管是中断命令发起者,还是接收者都不希望数据出现问题。 -
如何用 interrupt 停止线程
- 首先通过 Thread.currentThread().isInterrupt() 判断线程是否被中断
- 然后判断 count 是否小于 1000
- 这个线程的工作内容很简单,就是打印 0~999 的数字,每打印一个数字 count 加 1,可以看到,线程会在每次循环开始之前,检查是否被中断了
- 接下来在 main 函数中会启动线程
- 然后休眠五毫秒后立刻中断线程,该线程会检测到中断信号,于是在还没打印完1000个数的时候就会停下来
- 这种情况就属于通过 interrupt 实现了线程的正确停止
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() &&
count < 1000) {
System.out.println("count = " + count++);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
-
sleep 期间能否感受到中断
- 当在一个被阻塞的线程(调用sleep 或wait等会让线程阻塞的方法 ) 上调用interrupt 方法时,阻塞调用将会被Interrupted Exception 异常中断,将中段标记位设置成 false
- 如果我们负责编写的方法需要调用 sleep 或者 wait 时,就要求在方法中使用 try/catch或在方法签名中声明 throws InterruptedException
- 在 catch 语句块中调用 Thread.currentThread().Interrupt() 函数
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
三、为什么用 volatile 标记位的停止方法是错误的
public class VolatileStopThread implements Runnable {
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (!canceled && num <= 1000000) {
if(num % 10 == 0) {
System.out.print(num + "是10的倍数");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
VolatileStopThread r = new VolatileStopThread();
Thread thread = new Thread(r)
thread.start();
Thread.sleep(3000);
r.canceled = true;
}
}
- 声明一个叫作 VolatileStopThread 的类, 它实现了 Runnable 接口
- 然后在 run() 中进行 while 循环,在循环体中又进行了两层判断
- 首先判断 cancled 变量的值
cancled 变量是一个被 volatile 修饰的初始值为 false 的布尔值,当该值变为 true 时,while 跳出循环 - while 的第二个判断条件是 num 值小于1000000(一百万)
- 首先判断 cancled 变量的值
- 在while 循环体里,只要是 10 的倍数就打印出来,然后 num++
- 启动线程,然后经过 3 秒钟的时间,把用 volatile 修饰的布尔值的标记位设置成 true
- 这样,正在运行的线程就会在下一次 while 循环中判断出cancled 的值已经变成 true 了
- 这样就不再满足 while 的判断条件,跳出整个 while 循环,线程就停止了
- 这种情况是演示 volatile 修饰的标记位可以正常工作的情况,但是如果我们说某个方法是正确的,那么它应该不仅仅是在一种情况下适用,而在其他情况下也应该是适用的
看一下反例
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) { this.storage = storage; }
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 50 == 0) {
storage.put(num);
System.out.println(num + "是50的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) { this.storage = storage; }
public boolean needMoreNums() {
if (Math.random() > 0.97) {
return false;
}
return true;
}
}
public class VolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(500);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
// 一旦消费不需要更多数据了,我们应该让生产者也停下来
producer.canceled = true;
System.out.println(producer.canceled);
}
}
- 这是一个生产者,消费者模式
- 对于生产者而言,希望通过 volatile 标记位的方式来停止线程
- BlockingQueue 是生产者消费者用来通讯的存储器
- 当BlockingQueue 达到阀值8的时候,生产者会进行阻塞
- 一旦生产者进入
storage.put(num);
线程就阻塞了,那么此时无论消费者怎么改变canceled
的值,都无法使生产者重新进入while进行判断,那么使用 volatile 来进行停止线程就失败了。
网友评论