实现线程的方式:
第一种方式:
通过实现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
- 它会给我们线程池创建的线程设置一些默认的值,比如它的名字,他是不是守护线程,以及它的优先级
- 其本质还是通过
new Thread()
前面两种方式实现的
第四种方式:
有返回值的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 {
//具体实现
}
第六种方式:
匿名内部类
第七种方式:
Lambda表达式
new Thread(()->
System.out.println(Thread.currentThread().getName())).start();
)
最终实现的都是实现runnable接口或是继承Thread类
两个方式的最主要区别还是 run()方法 的内容来源
方式一
最终调用target.run()方式二
run()整个都被重写
@Override
pubilc void run() {
if(target != null) {
target.run();
}
}
创建线程只有一中方式: 构造 Thread 类
实现线程”运行内容”的两种方式:
方式一:
实现Runnable接口的run方式,并把Runnable实例作为target对象,传给Thread类,最终调用target.run()
方式二:
继承Thread类,重写Thread的run方法(),Thread.start() 会执行run()
==实现runnable接口比继承Thread类更好
可以把不同的内容进行解耦,权责分明
某些情况下可以提升性能,减少开销
继承Thread类相当于限制了代码未来的可扩展性
停止线程
对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。那么为什么 Java 不提供强制停止线程的能力呢?
事实上,Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。比如:线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。
阻塞调用将会被Interrupted Exception 异常中断,将中断标记位设置成false
即使线程在休眠,仍然能够响应中断,并抛出异常
如果我们负责编写的方法需要调用sleep或者wait时,就要求在方法中使用try/catch或在方法中声明throws InteruptedException
void subTask{
try{
sleep(delay);
}catch(InterruptedException e){}
//这里不处理该异常是非常不好的
}
如何用 interrupt 停止线程
while (!Thread.currentThread().islnterrupted() && more work to do) {
do more work
}
具体例子。
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 期间能否感受到中断
Runnable runnable = () -> {
int num = 0;
try {
while (!Thread.currentThread().isInterrupted() &&
num <= 1000) {
System.out.println(num);
num++;
Thread.sleep(1000000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
改写上面的代码,如果线程在执行任务期间有休眠需求,也就是每打印一个数字,就进入一次 sleep ,而此时将 Thread.sleep() 的休眠时间设置为 1000 秒钟。
public class StopDuringSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (!Thread.currentThread().isInterrupted() && num <= 1000) {
System.out.println(num);
num++;
Thread.sleep(1000000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
主线程休眠 5 毫秒后,通知子线程中断,此时子线程仍在执行 sleep 语句,处于休眠中。那么就需要考虑一点,在休眠中的线程是否能够感受到中断通知呢?是否需要等到休眠结束后才能中断线程呢?如果是这样,就会带来严重的问题,因为响应中断太不及时了。正因为如此,Java 设计者在设计之初就考虑到了这一点。
如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。
两种最佳处理方式:
方法一:
**方法签名异常,run()强制try/catch**
void subTask() throws InterruptedException {
sleep(delay);
}
- 用throws InterruptedException标记方法,不采用try语句块捕获异常,以便于该异常可以传递到顶层,让run()方法可以捕获异常
- 由于run方法内无法抛出checked Exception(只能用try catch),所以在方法上抛出异常的好处是,顶层方法必须处理该异常,不能够漏掉或者被吞掉
方法二:
再次中断
private void reInterrupt(){
trt{
Thread.sleep(2000);
}catch(InterruptedException e){
Thread.currentThread().interrup();
e.printStackTrace();
}
}
- 在catch语句块中调用Thread.currentThread().Interrupt()函数
- 因为如果线程在休眠期间被中断,那么会自动清除中断信号
- 如果这时手动添加中断信号,中断信号依然可以捕捉到
- 这样后续执行的方法依然可以检测到这里发生过中断,可以做出相应的处理,整个线程可以正常退出
生产者消费者模式
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("生产者运行结束");
}
}
}
首先,声明了一个生产者 Producer,通过 volatile 标记的初始值为 false 的布尔值 canceled 来停止线程。而在 run() 方法中,while 的判断语句是 num 是否小于 100000 及 canceled 是否被标记。while 循环体中判断 num 如果是 50 的倍数就放到 storage 仓库中,storage 是生产者与消费者之间进行通信的存储器,当 num 大于 100000 或被通知停止时,会跳出 while 循环并执行 finally 语句块,告诉大家“生产者结束运行”。
class Consumer {
BlockingQueue storage;
public Consumer (BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.97) {
return false;
}
return true;
}
}
而对于消费者 Consumer,它与生产者共用同一个仓库 storage,并且在方法内通过 needMoreNums() 方法判断是否需要继续使用更多的数字,刚才生产者生产了一些 50 的倍数供消费者使用,消费者是否继续使用数字的判断条件是产生一个随机数并与 0.97 进行比较,大于 0.97 就不再继续使用数字。
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);
}
下面来看下 main 函数,首先创建了生产者/消费者共用的仓库 BlockingQueue storage,仓库容量是 8,并且建立生产者并将生产者放入线程后启动线程,启动后进行 500 毫秒的休眠,休眠时间保障生产者有足够的时间把仓库塞满,而仓库达到容量后就不会再继续往里塞,这时生产者会阻塞,500 毫秒后消费者也被创建出来,并判断是否需要使用更多的数字,然后每次消费后休眠 100 毫秒,这样的业务逻辑是有可能出现在实际生产中的。
当消费者不再需要数据,就会将 canceled 的标记位设置为 true,理论上此时生产者会跳出 while 循环,并打印输出“生产者运行结束”。
然而结果却不是我们想象的那样,尽管已经把 canceled 设置成 true,但生产者仍然没有停止,这是因为在这种情况下,生产者在执行 storage.put(num) 时发生阻塞,在它被叫醒之前是没有办法进入下一次循环判断 canceled 的值的,所以在这种情况下用 volatile 是没有办法让生产者停下来的,相反如果用 interrupt 语句来中断,即使生产者处于阻塞状态,仍然能够感受到中断信号,并做响应处理。