多线程基础常用方法介绍
Posted 烟锁迷城
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程基础常用方法介绍相关的知识,希望对你有一定的参考价值。
1、Thread.join
join()
函数的作用是保证线程执行结果的可见性,即保证线程A的结果对线程B可见。
在示例代码中,线程thread的结果对主线程可见,是因为thread让主线程阻塞,直到thread线程执行完毕才唤醒主线程,这样就让thread的结果对主线程可见。
private static int a = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
a = 100;
});
thread.start();
thread.join();
System.out.println("a:"+a);
}
join函数源码可以看到,join有一个对象级别的锁保证单个线程的执行顺序,然后又调用了wait方法,这里的wait方法是让当前线程进入等待状态,直到线程不再存活。
也就是说,主函数线程启动子线程后,子线程调用join方法,主函数线程进入等待状态,子线程执行完毕,主线程继续执行,这样主线程才能获取到子线程内执行完成的数据结果,这样多线程就变为串行执行。
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
在join源码中没有与wait对应的notify方法,因为代码在JVM中,子线程结束后会调用notifyall,唤醒等待的主线程。
2、Thread.sleep
sleep()
函数使线程暂停执行一段时间,直到等待的时间结束才恢复执行,或者在这段时间内被中断,它的执行步骤如下:
- 挂起线程并修改其运行状态
- 用sleep()提供的参数来设置一个定时器
- 当时间结束,定时器会触发,内核受到中断后修改线程的运行状态。
public class ThreadDemo extends Thread{
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread show");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
System.out.println("main show");
}
}
在操作系统中,CPU竞争有很多种策略,Unix系统使用时间片算法,如果时间片到了,当前线程就会被挂起,交给其他线程执行。windows属于抢占式算法,如果一个线程抢占到CPU,就会一直使用直到执行结束。
因此对于sleep()函数的等待时间而言,就算是时间到了,也不会准确的唤醒该线程,因为它可能正在等待其他线程放开资源,或是和其他线程一起抢占资源,它的优先级并不会提高,也就是说,sleep(1000)
不会真的等待1秒之后唤醒,而是表示1秒之后该线程加入资源抢占,就像sleep(0)
的作用是让线程重新参与资源抢占,本身是具有意义的。
3、wait与notify
两个线程想要互相感知对方变化,就需要使用wait线程等待,notify线程唤醒。
最典型的案例,就是生产者与消费者模式
public class Consumer implements Runnable{
private Queue<String> msg;
private int maxSize;
public Consumer(Queue<String> msg, int maxSize) {
this.msg = msg;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true){
synchronized (msg){
while (msg.isEmpty()){
System.out.println("消费者停止消费");
try {
msg.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费产品:"+msg.remove());;
msg.notify();
}
}
}
}
public class Producter implements Runnable{
private Queue<String> msg;
private int maxSize;
public Producter(Queue<String> msg, int maxSize) {
this.msg = msg;
this.maxSize = maxSize;
}
@Override
public void run() {
int i = 0;
while (true){
synchronized (msg){
i++;
while (msg.size() == maxSize){
System.out.println("生产者停止生产");
try {
msg.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产产品:"+"产品"+i);
msg.add("产品"+i);
msg.notify();
}
}
}
}
从刚刚的案例来看,wait/notify本质上是一种条件竞争,也就是说,wait与notify是互斥存在的,所以需要增加synchronized来实现互斥。
wait和notify是用于实现多线程之间的通信,而通信就需要一个通信载体,synchronized就是一个载体,它们需要在同一个锁的范围内。
4、Thread.interrupted和Thread.interrupt
4.1、线程终止
使用函数stop()可以将线程强制终止。
public class StopDemo extends Thread{
@Override
public void run() {
while (true) {
try {
System.out.println("thread show");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread thread = new StopDemo();
System.out.println("main show");
thread.stop();
}
}
使用stop()终止线程是有风险的。在java程序本身没有线程,它需要借助jvm调用操作系统的线程,所以java程序强制终止线程时,实际上终止的是操作系统的线程,这会带来未知的风险,因此终止线程还是建议使用中断标志。
4.2、中断标志
使用线程的中断标志,可以安全的中断线程。但实际上中断标志是逻辑中断,并不会真的终止线程,所以当线程处于类似死循环的状态时,中断标志无法中断线程。
public class Demo implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("thread show");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("processor stop");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Demo());
System.out.println("main show");
thread.start();
thread.interrupt();
}
}
中断标志Thread.currentThread().isInterrupted()的默认值是false,当执行thread.isInterrupted()方法后,中断标志变为true,这样就可以从线程外对线程逻辑进行控制。
4.3、线程复位
对于已经执行interrupt()
方法的线程,可以手动复位,使用静态方法Thread.interrupted();
让标志位恢复为false。
假如在上一个例子中的主程序增加一行代码,sleep(1000),会发生什么呢?
public class Demo implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("thread show");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("processor stop");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Demo());
System.out.println("main show");
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
执行结果为:
从执行结果中可以发现,线程没有被中断,而是在抛出异常后继续执行,这意味着,线程的中断标志被恢复了,线程继续运行。
发生这个结果的原因是线程内部sleep(2000)这个语句开始生效,因为之前的中断标志在线程还未进入TIME_WAITING状态就已经被结束了,在设置延时1秒中断之后,线程成功进入超时等待状态,此刻线程中断标志变更,正在等待的线程被唤醒,抛出异常,但是没有任何响应,所以线程被复位,中断标志恢复成false,线程继续执行。
假如在异常处理中,再次执行线程中断呢?
public class Demo implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("thread show");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
System.out.println("processor stop");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Demo());
System.out.println("main show");
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
执行结果为:
此时可以看到,线程被成功中断。
这里推测,在interrupt()方法中,会做两件事情
- 将一个共享变量设置为true
- 将进入等待或超市等待状态下的线程唤醒
可以发现在所有的等待或是超时等待函数中,都要求必须被捕捉异常,这就是因为线程进入等待状态后用中断标志进行线程中断时,需要进行对应的执行操作,线程可以选择是继续执行,还是就此中断,这就将线程的执行权力交给当前线程而不是外部操作,这样的执行方式更加友好。
以上是关于多线程基础常用方法介绍的主要内容,如果未能解决你的问题,请参考以下文章