您的位置:首页 > 编程语言 > Java开发

Java线程相关,join,interrupt,wait等方法总结

2020-03-01 05:02 239 查看

线程的启动

线程的启动方式有以下三种:

1、X extends Thread;,然后X.run

2、X implements  Runnable;然后交给Thread运行

3、X implements  Callable;然后交给Thread运行

先看代码:

[code]public class NewThread {
/*扩展自Thread类*/
private static class UseThread extends Thread{
@Override
public void run() {
super.run();
//do my work
System.out.println("I am extends Thread");
}
}

/*实现Runnable接口*/
private static class UseRun implements Runnable{

@Override
public void run() {
System.out.println("I am implements Runnable");
}
}

/*实现Callable接口,允许有返回值*/
private static class UseCall implements Callable<String>{

@Override
public String call() throws Exception {
System.out.println("I am implements Callable -"+System.currentTimeMillis());
Thread.sleep(2000);
return "CallResult";
}
}

public static void main(String[] args)
throws InterruptedException, ExecutionException {

UseThread useThread = new UseThread();
useThread.start();

UseRun useRun = new UseRun();
new Thread(useRun).start();

UseCall useCall = new UseCall();
FutureTask<String> futureTask = new FutureTask<>(useCall);
new Thread(futureTask).start();
System.out.println("do my things...");
//do my work
//FutureTask的get方法是阻塞的,调用此方法之后,main线程会在此阻塞
System.out.println(futureTask.get()+"-"+System.currentTimeMillis());
System.out.printf("end of main...");
}
}

运行结果如下:

do my things...
I am extends Thread
I am implements Runnable
I am implements Callable-1581254728499
CallResult-1581254730499
end of main...

可以看到1581254730499-1581254728499=2000,正好是sleep的时间,说明FutureTask的get方法是阻塞的,要等到call方法执行完之后才会返回,end of main也是在get方法之后执行的,也能够佐证get方法是阻塞的。

说明:

1,Callable必须要借助FutureTask才能在线程中执行,Callable是无法直接作为new Thread的参数,创建Thread对象的

2,直接用Thread和Runnable这两种方式有个缺陷,就是在线程执行完毕之后,无法获取线程执行的结果

3,创建了Thread对象之后,也就是new Thread()之后,这个仅仅是new出了一个Thread对象,Thread对象并没有跟系统线程有任何关联,知道调用了Thread的start方法,才实现了真正意义上的线程启动,start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用

4,run方法只是Thread类和Runnable类的一个成员方法,跟其他类的成员方法没有任何区别,也可以声明Thread和Runnable的对象,然后直接调用run方法,而不必一定要通过Thread的start方法调起run方法,但是这两者有个区别:

  1. 通过Thread的start方法调起run方法,这个时候run方法是运行在通过start方法的调用,系统分配的线程中;
  2. 通过声明Thread和Runnable对象,直接调用,不通过start方法调起的,run方法是运行在当前线程的。

针对第3,4点,这个做个简单例子,如下:

[code]public class StartAndRun {
public static class ThreadRun extends Thread{

@Override
public void run() {
int i = 3;
while(i>0){
SleepTools.ms(1000);
System.out.println("I am "+Thread.currentThread().getName()
+" and now the i="+i--);
}
}
}

public static void main(String[] args) {
ThreadRun beCalled = new ThreadRun();
beCalled.setName("beCalled");
beCalled.run();

}
}

运行结果如下:

通过运行结果可以看出,如果不通过Thread的start方法调用,那么run方法是运行在名为main的这个线程中的;

修改上面main函数如下:

[code]public static void main(String[] args) {
ThreadRun beCalled = new ThreadRun();
beCalled.setName("beCalled");
//beCalled.run();

//调用Thread的start方法,让系统分配cpu资源,启动线程,调起run方法
beCalled.start();
}

运行结果如下:

通过运行结果可以看到,run方法是运行在名为beCalled的这个线程中的。

这里,这个例子主要是为了对run方法和start方法有个清晰的认知和了解。

以上就是线程的创建和执行。

线程Thread的相关方法

  • join方法

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B,所以就可以用join方法控制线程顺序执行。

t.join();      //使调用线程 t 在此之前执行完毕。
t.join(2000);  //等待 t 线程,等待时间是2000毫秒,即便线程t的执行时间会超过过2000ms,也只会等待2000ms

看示例代码:

[code]public class JoinTest implements Runnable{

public static int a = 0;

public void run() {
for (int k = 0; k < 5; k++) {
a = a + 1;
try {
Thread.sleep(100);
System.out.println("a = "+a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) throws Exception {
Runnable r = new JoinTest();
Thread t = new Thread(r);
t.start();
//t.join();t.join();
System.out.println("end of main...");
}
}

运行结果如下:

可以看到,没有调用join方法的时候,由于线程中有sleep的耗时操作,那么主线程中的语句是先执行的。

下面修改代码如下:

[code]public static void main(String[] args) throws Exception {
Runnable r = new JoinTest();
Thread t = new Thread(r);
t.start();
//调用join方法之后,就是把t这个线程,插入到当前线程之中,当前线程必须在此处等待线程t执行完毕之后,才能继续往后执行
t.join();
System.out.println("end of main...");
}

执行结果如下:

我们看到main线程在等待线程t执行完之后才继续往后执行,这就是join方法的作用,把join方法所属的线程插入到当前线程执行,待线程t执行完毕之后,当前线程才能继续往后执行,join方法是阻塞的。

那么思考一下,如何利用join方法实现线程的顺序执行呢?来看一个稍微复杂一些的例子,将上面代码修改为如下:

[code]public class JoinTest implements Runnable{

public static int a = 0;

public void run() {
Thread.currentThread().setName("JoinTest");
System.out.println(Thread.currentThread().getName()+" start...");
for (int k = 0; k < 5; k++) {
a = a + 1;
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" a = "+a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" end...");
}

static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
System.out.println(Thread.currentThread().getName()+" start...");
try {
//当前线程会在此处阻塞,直到线程mT执行完毕,才能继续往后
mT.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end...");
}
}

public static void main(String[] args) throws Exception {
Runnable r = new JoinTest();
Thread t = new Thread(r);
JoinTest1 t1 = new JoinTest1(t);
t1.start();
t.start();
t1.join();
System.out.println("end of main...");
}
}

运行结果如下:

从结果可以清晰的看到,在线程JoinTest1中,会等待线程mT(也就是JoinTest)执行完毕之后才继续往后执行,在main方法中同样,main线程等待t1(也就是JoinTest1)执行完毕之后才能继续往后。

关于join(n),需要注意:

在调用t.join(n)的时候,当前线程必须要拿到t的对象锁,如果其他线程持有该对象的锁并未释放,那么当前线程等待的时间可就不是n了。

看例子如下

[code]public class JoinTest implements Runnable{

public static int a = 0;

public void run() {
Thread.currentThread().setName("JoinTest");
System.out.println(Thread.currentThread().getName()
+" start time = "+System.currentTimeMillis());
//该for循环执行完毕,最少需要5*1000 = 5000ms
for (int k = 0; k < 5; k++) {
a = a + 1;
try {
Thread.sleep(1000);
//System.out.println(Thread.currentThread().getName()+" a = "+a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+" end time = "+System.currentTimeMillis());
}

static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
synchronized(mT){
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
try {
//mT.join(1000);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"releaseLock end time = "+System.currentTimeMillis());
}
}
}

public static void main(String[] args) throws Exception {
System.out.println("begin of main time = "+System.currentTimeMillis());
Runnable r = new JoinTest();
Thread t = new Thread(r);
JoinTest1 t1 = new JoinTest1(t);
t.start();
t1.start();
//线程t整个执行完,需要5000ms,这里join参数为1000,那么main线程只会等待1000ms,就会继续往后执行,但是这并不会阻断线程t,线程t还是会继续执行,直到结束
t.join(1000);
System.out.println("end of main time = "+System.currentTimeMillis());
}
}

运行结果:

分析一下运行结果:

1,即便join的参数的时长比线程t本身需要执行的时间短,但是join之后,线程t仍然会继续执行,知道结束,也就是说,join并不会对线程t本身造成任何影响。

2,end of main time = 1581296332472 - begin of main time = 1581296330458 这两个时间差大概是2000ms,并不是join方法传入的参数1000ms,这是因为在执行t.join时发现,发现线程t1已经持有对象t的锁,此时join会同时开始计时;这里思考个问题,main线程等待的时间其实是t1持有锁的时间,并不是join参数的时间和t1持有锁时间之和,而是线程t1持有锁的时间和join参数的较大者(看有些博客里面可能会讲main线程等待的时间是join和t1持有对象锁时间之和,注意这个说法是错误的)。

修改代码验证一下上面第二点:

[code]public static void main(String[] args) throws Exception {
System.out.println("begin of main time = "+System.currentTimeMillis());
Runnable r = new JoinTest();
Thread t = new Thread(r);
JoinTest1 t1 = new JoinTest1(t);
t.start();
t1.start();
t.join(3000);//此处之前为1000,小于t1持有锁时间,现在修改为3000,大于t持有锁时间
System.out.println("end of main time = "+System.currentTimeMillis());
}

运行结果:

从结果可得知,end of main time = 1581298192393 - begin of main time = 1581298189389,这个时间差大概是3000ms,是join的参数时间,也正好验证了上面第二点中提到的问题,说明只要调用了join方法,就开始计时,main线程等待的时间是join方法的参数和t1持有对象锁的时间的较大者。

  • wait、notify、notifyall方法

wait方法其实就是等待/通知机制,这里要说明一下,wait方法是Object的方法,并不是线程Thread独有的。

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

notify():

通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。

notifyAll():

通知所有等待在该对象上的线程

wait()

调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁

wait(long)

超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回

wait (long,int)

对于超时时间更细粒度的控制,可以达到纳秒

看一个调用wait方法的例子:

[code]public class JoinTest implements Runnable{

public static int a = 0;

public static Object waitObj = new Object();

public void run() {
Thread.currentThread().setName("JoinTest");
try {
synchronized (waitObj){
System.out.println(Thread.currentThread().getName()
+" start time = "+System.currentTimeMillis());
//在此处调用wait方法,当前线程会阻塞,等待通知
waitObj.wait();
System.out.println(Thread.currentThread().getName()
+" end time = "+System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();

synchronized (waitObj){
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
try {
//在此处调用notify方法之后,线程JoinTest才能继续往后执行
waitObj.notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+" releaseLock end time = "+System.currentTimeMillis());
}
}
}

public static void main(String[] args) throws Exception {
System.out.println("begin of main time = "+System.currentTimeMillis());
Runnable r = new JoinTest();
Thread t = new Thread(r);
JoinTest1 t1 = new JoinTest1(t);
t.start();
t1.start();
//t.join(3000);
System.out.println("end of main time = "+System.currentTimeMillis());
}
}

运行结果如下:

分析执行结果:

1,在调用wait方法之后,线程会释放对象锁,此处就是waitObj这个对象;理由:线程t中调用wait方法之后,线程t已经处于阻塞状态,线程t并没有执行完,如果调用wait方法没有释放资源的话,在线程t1里面的log是不可能打印出来的,因为锁定的是同一个对象。

2,在调用Object的notify方法之后,线程t收到通知,解除阻塞状态,继续往后执行。

3,wait(n)方法,就是等待n毫秒的时间之后,解除阻塞状态,如果在这n毫秒的时间内收到了notify或者notifyall通知,那么立刻解除阻塞状态。

wait和notify的标准范式:

等待方遵循如下原则。

1)获取对象的锁。

2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

3)条件满足则执行对应的逻辑。

通知方遵循如下原则。

1)获得对象的锁。

2)改变条件。

3)通知所有等待在对象上的线程。

  • interrupt相关方法

暂停、恢复和停止操作对应在线程Thread的API就是suspend()resume()stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,一、一般的阻塞方法,如sleep等本身就支持中断的检查,二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

Java中把进程启动和终止的动作交给程序员自己处理,interrupt相关的方法主要有以下几个:

1,isInterrupted(),判断当前线程的interrupt标志位是否为true

2,interrupt(),用来终端线程,即将Thread的interrupt标志位置为true

3,Thread.interrupted(),判断当前线程的interrupt标志位是否为true,并且同时将该标志位置为false

先来看下如何合理的终止一个正在执行的线程:

[code]public class JoinTest implements Runnable{

public static int a = 0;

public static Object waitObj = new Object();

public void run() {
Thread.currentThread().setName("JoinTest");
System.out.println(Thread.currentThread().getName()
+" start time = "+System.currentTimeMillis());

System.out.println(Thread.currentThread().getName()
+" end time = "+System.currentTimeMillis());
}

static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
int i=0;
while(!isInterrupted()){
//System.out.println(Thread.currentThread().getName()+" i="+i++);
/*try {
Thread.sleep(500);
} catch (InterruptedException e) {
interrupt();
e.printStackTrace();
}*/
}
System.out.println(Thread.currentThread().getName()
+" releaseLock end time = "+System.currentTimeMillis());
}
}

public static void main(String[] args) throws Exception {
System.out.println("begin of main time = "+System.currentTimeMillis());
Runnable r = new JoinTest();
Thread t = new Thread(r);
JoinTest1 t1 = new JoinTest1(t);
//t.start();
t1.start();
Thread.sleep(300);
t1.interrupt();
//t.join(3000);
System.out.println("end of main time = "+System.currentTimeMillis());
}
}

运行结果如下:

可以看到,在300ms之后,线程t1判断中断标志位为true,推出while循环,结束线程,注意此时在t1中终端标志位为true。

下面来修改一下JoinTest1的run方法如下:

[code]static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
int i=0;
//while(!isInterrupted()){
//此处将isInterrupted(),修改为Thread.interrupted()
while(Thread.interrupted()){
//System.out.println(Thread.currentThread().getName()+" i="+i++);
/*try {
Thread.sleep(500);
} catch (InterruptedException e) {
interrupt();
e.printStackTrace();
}*/
}
System.out.println(Thread.currentThread().getName()
+" releaseLock end time = "+System.currentTimeMillis());
}
}

main方法不做修改,运行结果如下:

注意此时终端标志位值为false,这是因为Thread.interrupted()将中断标志位又重新置为false。

  • 当线程阻塞的时候,该如何中断线程

看代码:

[code]static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
int i=0;
//while(!isInterrupted()){
//此处将isInterrupted(),修改为Thread.interrupted()
while(Thread.interrupted()){
System.out.println(Thread.currentThread().getName()+" i="+i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//interrupt();
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+" releaseLock end time = "+System.currentTimeMillis());
}
}

运行结果如下:

线程阻塞的时候调用interrupt会抛出InterruptedException,但是线程并不会终止,因为抛出异常之后,会导致重置中断标志位失败,那么在这种情况下应该怎么终止线程呢?做如下修改:

[code]static class JoinTest1 extends Thread{
private Thread mT;

public JoinTest1(Thread t){
mT = t;
}

@Override
public void run() {
Thread.currentThread().setName("JoinTest1");
super.run();
System.out.println(Thread.currentThread().getName()
+" getLock start time = "+System.currentTimeMillis());
int i=0;
//while(!isInterrupted()){
//此处将isInterrupted(),修改为Thread.interrupted()
while(Thread.interrupted()){
System.out.println(Thread.currentThread().getName()+" i="+i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
interrupt();//之前此处是注释的,在catch到InterruptedException的时候,调用interrupt方法,就可以中断此线程
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+" releaseLock end time = "+System.currentTimeMillis());
}
}

在Catch中打开interrupt,运行结果如下:

可以看到线程被成功终止了。

到这里线程相关的内容就结束了,如还有其他的后续补充

总结要注意的点如下:

  1. 如果要获取线程执行的结果,可以使用Callable,但是要用FutureTask做包装,并且注意获取线程执行结果的方法,即FutureTask的get方法是阻塞的,会等待线程结束才能返回。
  2. 要使线程顺序执行,可使用join方法,join方法可以使当前线程等待目标线程执行完毕。
  3. wait方法和notify方法是成对出现的,wait方法会阻断当前线程,知道其他线程调用notify或者notifyall才能继续往后执行,调用wait方法时,会释放当前持有的对象锁(yield和sleep不会释放持有的锁),特别注意,用wait的时候,要先锁住对象,防止多个线程进入代码断,否则会抛出异常。
  4. interrupt方法,在遇到线程阻塞的时候会抛出InterruptedException,导致线程无法成功终止,需要在catch到InterruptedException的时候,调用interrupt方法才能中断线程。
  5. 注意isInterrupted方法和interrupted方法的区别,如果是实现的的Runnable,在Runnable的run方法中判断标志位可以写为:while(!Thread.currentThread.isInterrupted()){}。
  6. 要深入了解run和start方法,start方法在同一时间只能调用一次。

Over。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
鉴于往事,有资于治道 发布了9 篇原创文章 · 获赞 0 · 访问量 274 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐