2.java并发编程:多线程api详解

109 阅读16分钟

多线程API列表

方法static功能说明注意
start()启动一个新线程,在新的线程运行 run 方法中的代码。start 方法只是让线程进入就绪,里面代码不一定立刻运,因为cpu的时间片还没分给它。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException。
run()新线程启动后会调用的方法如果在构造Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为。 public void run() { if (this.target != null) { this.target.run(); }}
join()等待线程运行结束
join(long n)等待线程运行结束,最多等待 n毫秒1.如果在n秒内没有运行结束,那么直接返回结果。 2.如果在n秒内运行结束,那么会提前返回。如join(10000),实际1秒执行完成,那么1秒后join就会结束。
getId()获取线程长整型的 id此线程id唯一
getName()获取线程名
setName(String)修改线程名
getPriority()获取线程优先级
setPriority(int)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率,默认是5
getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED
isInterrupted()调用该方法的线程。如果已经被打断返回true,否则返回false。不会重置清除打断标记,返回的是打断状态。
isAlive()线程是否存活(是否运行完毕)
interrupt()打断线程打断标记默认是false。如果被打断线程正在 sleep、wait、join 会导致被打断的线程抛出 InterruptedException异常,会重置打断标记为false。如果打断的正在运行的线程,则会设置打断标记为true;park 的线程被打断,也会重新设置打断标记为true
interrupted()static注意是当前线程是否已经中断,如果已经被打断返回true,否则返回false。静态方法,打断标记默认是false。会重置清除打断标记,返回的是打断状态。
currentThread()static获取当前正在执行的线程
sleep(long n)static让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程,不会释放锁
yield()static提示线程调度器让出当前线程对CPU的使用主要是为了测试和调试

★interrupt方法

打断标记

打断标记的原始默认值是false。

下面说的清除打断标记或者重置打断标记都是将打断标记重新恢复,设置为原始的默认值false

Interrupt打断说明

动词,打断某个线程的方法。

打断sleep|wait|join

抛出异常且打断标记fasle

打断标记的原始默认值是false。

如果被打断线程正在 sleep、wait、join 会导致被打断的线程抛出 InterruptedException异常。

抛出异常,也会重置打断标记为false。

interrupt()方法只是设置线程的中断标记,当对处于阻塞状态的线程调用interrupt方法时(处于阻塞状态的线程是调用sleep, wait, join 的线程),会抛出InterruptException异常,而这个异常会清除中断标记。

默认的打断标记是false,重置和清除打断标记就是设置打断状态为false

sleep、wait、join方法都会让线程进入阻塞状态,打断处于sleep wait join等状态的线程,会重置打断状态即置为false,以 sleep 为例:

package org.apache.rocketmq.example.thread;
​
public class Test {
​
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
​
    private static void test1() throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }, "t1");
​
        t1.start();
        System.out.println((" 打断状态1: " +  t1.isInterrupted()));
        Thread.sleep(500);
        t1.interrupt();
        System.out.println((" 打断状态2: " +  t1.isInterrupted()));
​
    }
}
​

输出:

 打断状态1: false
 打断状态2: false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at org.apache.rocketmq.example.thread.Test.lambda$test1$0(Test.java:12)
    at java.lang.Thread.run(Thread.java:745)
​
Process finished with exit code 0   

打断正常运行的线程

打断标记为true

如果打断的正在运行的线程,则会设置打断标记为true;

park 的线程被打断,也会重新设置打断标记为true。

说明曾经被打断过。

打断正常运行的线程, 不会重置打断状态,即为true

package org.apache.rocketmq.example.thread;
​
public class Test {
​
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
​
    private static void test1() throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                while(true){
​
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
​
        }, "t1");
​
        t1.start();
        System.out.println((" 打断状态1: " +  t1.isInterrupted()));
        Thread.sleep(500);
        t1.interrupt();
        System.out.println((" 打断状态2: " +  t1.isInterrupted()));
​
    }
}
​
 打断状态1: false
 打断状态2: true

打断park的线程-true

打断标记为true

打断park的线程, 不会重置打断状态即打断标记为true。

private static void test4() {
    Thread t1 = new Thread(() -> {
        logebug("park...");
        LockSupport.park();
        log.dug("打断状态:{}", Thread.interrupted());
    });
    t1.start();
    sleep(1);
    t1.interrt();
}
​
输出:
​
15:19:05.895 c.Test14 [Thread-0] - park...
15:19:06.894 c.Test14 [Thread-0] - 打断状态:true

如果打断状态已经是 true, 则 park 会失效,即park不会停止。

package org.apache.rocketmq.example.thread;
​
import java.util.concurrent.locks.LockSupport;
​
public class Test {
​
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
​
    private static void test4() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("打断状态:{}" +  Thread.currentThread().isInterrupted());//true
            for (int i = 0; i < 5; i++) {
                System.out.println("park...");
                LockSupport.park();
                //如果打断标记是true,park会失效。
                System.out.println("打断状态:{}" +  Thread.currentThread().isInterrupted());//true
                System.out.println("打断状态:{}" + Thread.currentThread().isInterrupted());//true
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}
 
打断状态:{}false
park...
打断状态:{}true
打断状态:{}true
park...
打断状态:{}true
打断状态:{}true
park...
打断状态:{}true
打断状态:{}true
park...
打断状态:{}true
打断状态:{}true
park...
打断状态:{}true
打断状态:{}true
​
Process finished with exit code 0

cleanInterruppted属性默认false

cleanInterruppted为true:代表需要清除历史打断标记,即重置打断标记为false

isInterrupted:false:不需要清除历史打断标记

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会清除线程的中断状态

20180629102624946.png

interrupted:true:需要清除历史打断标记

interrupted()是静态方法。

底层实现是调用的当前线程的isInterrupted() ,cleanInterruppted属性默认是true:会清除线程的中断状态。 20180629102603395.png interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会清除线程的中断状态

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会清除线程的中断状态

isInterrupted:正常方法&不会重置标记

打断阻塞线程

抛出异常,返回打断标记为false

package 打断;
​
import java.util.concurrent.BrokenBarrierException;
 
public class testisInterrupted1{
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1; ) {
                    try {
                        Thread.sleep(500000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
​
        thread.start();
​
        System.out.println("打断前打断标记:" + thread.isInterrupted());
        Thread.sleep(2000);
        thread.interrupt();
        Thread.sleep(2000);
        System.out.println("打断后打断标记:" + thread.isInterrupted());
       /*
        打断前打断标记:false
        java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at 打断.testInterrupt1$1.run(testInterrupt1.java:12)
        at java.lang.Thread.run(Thread.java:745)
        打断后打断标记:false
        */
    }
}

打断运行线程

返回true,不会重置打断状态为false

意思是打断运行线程,无论调用多少次isInterrupted,返回的始终是true

package 打断;
​
import java.util.concurrent.BrokenBarrierException;
/**
 *  如果打断的正在运行的线程,则会设置打断标记为true; park 的线程被打断,也会重新设置打断标记为true
 */
public class testisInterrupted2{
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1; ) {
​
                }
            }
        });
​
        thread.start();
        //打断前肯定是false
        System.out.println("打断前打断标记:" + thread.isInterrupted());
        Thread.sleep(2000);
        thread.interrupt();
        Thread.sleep(2000);
        //执行打断操作以后是 true 代表被打断
        System.out.println("打断后打断标记:" + thread.isInterrupted());
        //再次获取仍然是true 代表 说明没有重置打断标记为原始的false
        System.out.println("打断后打断标记:" + thread.isInterrupted());
       /*
        打断前打断标记:false
        打断后打断标记:true
        打断后打断标记:true
        */
    }
}

证明打断阻塞线程会返回打断标记false

因为一开始默认的打断标记就是false,所以我们先使用park打断后将打断标记置为true。

再测试打断阻塞线程会返回打断标记false。

package 打断;
​
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.locks.LockSupport;
​
public class testInterrupted3{
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("打断前打断标记:" +
                                   Thread.currentThread().isInterrupted());
                System.out.println("进入park");
                LockSupport.park();
                //此处是true
                System.out.println("打断后打断标记:" +
                                    Thread.currentThread().isInterrupted());
                //注释睡眠的代码返回的是false
                try {
                    System.out.println("进入睡眠");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out.println("我被打断了");
                }
                //Thread.currentThread().isInterrupted() 不会重置打断标记
                //但是状态却由true变成了false 说明被打断后状态被重置成了false
                System.out.println("打断后打断标记:" + 
                                   Thread.currentThread().isInterrupted());
            }
        });
​
        thread.start();
        Thread.sleep(1000);
        System.out.println("第一次打断");
        thread.interrupt();
        Thread.sleep(1000);
        System.out.println("第二次打断");
        thread.interrupt();
​
       /* 
        Thread-0
        打断后打断标记:false
        打断后打断标记:true
        我被打断了
        打断后打断标记:false
       */
        
         //注释睡眠的代码返回的是
        
         /* 
        Thread-0
        打断后打断标记:false
        打断后打断标记:true
        我被打断了
        打断后打断标记:true
       */
    }
}

打断被打断的线程

private static void test2() throws InterruptedException {
  Thread t1 = new Thread(() -> {
    try {
      Thread.sleep(10000);
    } catch (InterruptedException e) {
      log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
      Thread.currentThread().interrupt();//第二次是打断正常运行的线程,返回true
      log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
    }
  }, "t1");
  t1.start();
  sleep(1);
  t1.interrupt();//第一次打断sleep的线程,该线程恢复正常继续运行
}

输出:

16:09:08.677 c.Test14 [t1] - 打断状态:false

16:09:08.683 c.Test14 [t1] - 打断状态:true

第一次打断的是sleep的线程,会抛出异常重置打断标记为false。

第二次打断的是正常运行的线程,会重置打断标记为true。

interrupted:静态方法&会重置打断标记

静态方法,打断标记默认是false,会重置打断标记。第一次返回的是当前线程打断状态,重置指的是连续2次调用第一次返回的是当前线程打断状态,并且会立刻重置打断状态为false,所以第二次返回的一定是false

interrupted为静态方法,例子中的问题用Thread类调用就会减少此类的误解,这也是为什么不建议用实例调用类的静态方法的原因之一。

interrupted是静态方法。所以最好使用Thread.interrupted() ;

打断睡眠线程

抛出异常,返回打断标记为false

package 打断;

import java.util.concurrent.BrokenBarrierException;

public class testInterrupted1{
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("打断前打断标记:" + Thread.interrupted());
                for (int i = 0; i < 1; ) {
                    try {
                        Thread.sleep(500000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("异常");
                        break;
                    }
                }
                System.out.println("打断后打断标记:" + Thread.interrupted());
            }
        });

        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
        Thread.sleep(2000);

        /*
        打断前打断标记:false
        java.lang.InterruptedException: sleep interrupted
            at java.lang.Thread.sleep(Native Method)
            at 打断.testInterrupted1$1.run(testInterrupted1.java:12)
            at java.lang.Thread.run(Thread.java:745)
        打断后打断标记:false
        */
    }
}

打断运行线程

返回true,会重置打断标记为false

private static void test4() {
  Thread t1 = new Thread(() -> {
    for (int i = 0; i < 2; i++) {
      log.debug("park...");
      LockSupport.park();
      //如果打断标记是true,park会失效。
      //默认是true,调用完该方法会重置false
      log.debug("打断状态:{}", Thread.interrupted());
      //标记由true变为false,park继续生效
      log.debug("打断状态:{}", Thread.interrupted());//false
    }
  });
  t1.start();
  sleep(1);
  t1.interrupt();
}


输出:

15:55:52.440 c.Test14 [Thread-0] - park...

15:55:53.427 c.Test14 [Thread-0] - 打断状态:true

15:55:53.442 c.Test14 [Thread-0] - 打断状态:false

15:55:52.440 c.Test14 [Thread-0] - park...

15:55:53.427 c.Test14 [Thread-0] - 打断状态:true

15:55:53.442 c.Test14 [Thread-0] - 打断状态:false
package 打断;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.locks.LockSupport;

public class testInterrupted2{
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        //模拟正常运行或者park被打断
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("打断后打断标记:" + Thread.interrupted());
                LockSupport.park();
                System.out.println("打断后打断标记:" + Thread.interrupted());
                System.out.println("打断后打断标记:" + Thread.interrupted());
            }
        });

        thread.start();
        //主线程睡眠1秒 保证thread线程能被park
        Thread.sleep(1000);
        //当thread处于 park状态在被打断 
        thread.interrupt();


        /*
        	//第一次是返回的是false是初始状态
            打断后打断标记:false
            //第二次返回的是调用interrupt方法重置为true的状态
			打断后打断标记:true
			//第三次返回的是重复调用interrupted重置为false的状态
			打断后打断标记:false
        */
    }
}

区别

isInterrupted()返回当前打断标记,不会重置打断标记。

interrupted()返回当前打断标记,会重置打断标记。

package 测试打断;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.locks.LockSupport;

public class Different {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        //模拟正常运行或者park被打断
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("打断后打断标记:" + Thread.interrupted());
                LockSupport.park();
                System.out.println("打断后打断标记:" + Thread.interrupted());
                System.out.println("打断后打断标记:" + Thread.interrupted());
            }
        });

        thread.start();
        //主线程睡眠1秒 保证thread线程能被park
        Thread.sleep(1000);
        //当thread处于 park状态在被打断
        thread.interrupt();


        /*
        打断后打断标记:false
        打断后打断标记:true
        打断后打断标记:false
        */


        //模拟正常运行或者park被打断
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("打断后打断标记:" + Thread.currentThread().isInterrupted());
                LockSupport.park();
                System.out.println("打断后打断标记:" + Thread.currentThread().isInterrupted());
                System.out.println("打断后打断标记:" + Thread.currentThread().isInterrupted());
            }
        });

        thread2.start();
        //主线程睡眠1秒 保证thread线程能被park
        Thread.sleep(1000);
        //当thread处于 park状态在被打断
        thread2.interrupt();
        /*
        Thread-1
        打断后打断标记:false
        打断后打断标记:true
        打断后打断标记:true
        */
    }
}

区别在于:

isInterrupted的结果是ftt,即没有重置打断标记为false。

Interrupted的结果是ftf,重置打断标记为false。

总结:推荐使用isInterrupted方法

interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

一看到线程的interrupt()方法,根据字面意思,很容易将该方法理解为中断线程。

其实Thread.interrupt()并不会中断线程的运行,它的作用仅仅是为线程设定一个状态而已,即标明线程是中断状态,这样线程的调度机制或我们的代码逻辑就可以通过判断这个状态做一些处理,比如sleep()方法会抛出异常,或是我们根据isInterrupted()方法判断线程是否处于中断状态,然后做相关的逻辑处理。

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则线程会一直检查中断状态标示,如果发现中断状态标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false

抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

注意:synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。

与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。

但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。

你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

★join 方法详解

join底层使用 wait实现

join()相当于join(0);

join源码

join方法是被synchronized修饰的。代表锁的对象是this。


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;
                }
                //等待超时时间的join
                //即使在这里错误被唤醒
                //只要线程是alive状态,会重新进入循环继续阻塞等待。
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

线程是如何被阻塞的?又是通过什么方法唤醒的呢?先来看看Thread.join方法做了什么事情

从join方法的源码来看,join方法的本质调用的是Object中的wait方法实现线程的阻塞。

但是我们需要知道的是,调用wait方法必须要获取锁,所以join方法是被synchronized修饰的,synchronized修饰在方法

层面相当于synchronized(this),this就是previousThread本身的实例,也就是调用join方法的对象。

有很多人不理解previousThread.join()为什么阻塞的是主线程呢?

不理解阻塞主线程的原因是:

认为previousThread这个实例调用的join方法,所以应该阻塞previousThread线程。

实际上调用previousThread.join(),相当于

synchronized(previousThread){

long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            //死循环 如果previousThread线程是存活的
            while (isAlive()) {
                wait(0);
            }
        } else {
            //死循环 如果previousThread线程是存活的
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                //等待超时时间的join
                //即使在这里错误被唤醒
                //只要线程是alive状态,会重新进入循环继续阻塞等待。
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }

}

主线程会持有previousThread这个对象的锁。

然后调用join方法,wait方法去阻塞,join方法是在主线程中执行的。所以造成主线程阻塞。

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    Thread t = new Thread();
    t.start();
    t.join();
}


public static void main(String[] args) throws ExecutionException, InterruptedException {
    Thread t = new Thread();
    t.start();
   // t.join(); 这里的join可以理解为1个线程被调用wait阻塞了。
   // 所以阻塞的是主线程。
}

为什么previousThread线程执行完毕就能够唤醒主线程呢?或者说是在什么时候唤醒的?

线程执行完毕以后isAlive()是false,就会跳出死循环,join代码执行结束。主线程继续往下执行

如果1个线程t执行完毕需要30秒,执行t.join(2000);会怎么样?

相当于调用t.wait(2000);只会阻塞主线程2秒。

package join;

public class JoinThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {

        }
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        System.out.println(1);
        JoinThread joinThread=new JoinThread();
        joinThread.start();
        joinThread.join(2000);
        System.out.println(2);
        long end = System.currentTimeMillis()-start;
        System.out.println(end);
    }
}
/***
 1
 2
 2004
 ***/

为什么需要 join

下面的代码执行,打印 r 是什么?

package join;

import static java.lang.Thread.sleep;

public class TestJoin1 {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        test1();
    }

    private static void test1() throws InterruptedException {
        System.out.println("开始1");
        Thread t1 = new Thread(() -> {
            System.out.println("开始2");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束2");
            r = 10;
        });
        t1.start();
        System.out.println("结果为:" + r);
        System.out.println("结束1");
    }
}

因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10

而主线程立刻就要打印 r 的结果,所以只能打印出 r=0

用 sleep 行不行?为什么?

不行,因为不知道代码到底需要多久才能执行完毕

用 t1.join(),加在 t1.start() 之后即可

等够时间

package join;

import static java.lang.Thread.sleep;

public class TestJoin2 {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test3();
    }

    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        long start = System.currentTimeMillis();
        t1.start();
// 线程只需要1秒执行完成,join等待1.5秒
// 当t1线程执行结束会导致t1的isAlive()返回false,join 方法执行结束。实际只会花费1秒
        t1.join(1500);

        System.out.println("r1: " + r1 + " r2: " + r2);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

/**
r1: 10 r2: 0
1001
**/

没等够时间

package join;

import static java.lang.Thread.sleep;

public class TestJoin3 {
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
        test3();
    }

    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        long start = System.currentTimeMillis();
        t1.start();
		// 线程需要2秒才会执行赋值操作,但是join只等待了1.5秒,此时还未做赋值操作。
        t1.join(1500);
        long end = System.currentTimeMillis();
        System.out.println("r1:"+ r1 + ",cost:" + (end - start));
    }

    /*输出:r1:0 cost:1502
    实际执行1.5秒,但是由于代码需要2秒才能执行完毕,
    因此赋值操作还没有完成 主线程就结束了。*/
}

多个join

package join;

import static java.lang.Thread.sleep;

public class TestJoin4 {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test2();
    }

    private static void test2() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
            }
            r1 = 10;
        });

        Thread t2 = new Thread(() -> {
            try {
                sleep(2000);
            } catch (InterruptedException e) {
            }
            r2 = 20;
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        System.out.println("r1:"+r1 +",r2:"+r2 +",cost:" + (end - start));
    }
}

如果颠倒两个 join 呢?

最终都是输出: r1: 10 r2: 20 cost: 2005

多个join,以时间长的那个为准。

原理之join

是调用者轮询检查线程 alive 状态

t1.join();

相当于以下代码

synchronized (t1) {
    // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束
    while (t1.isAlive()) {
        //t1就算被唤醒然后获取到锁 也是继续进入while循环
        t1.wait();
    }
    //当t1.isAlive()=false
    //代码直接结束,跳出当前方法
}

join 体现的是【保护性暂停】模式,请参考保护性暂停

★Wait Notify详解

方法名称描述
notify()通知一个在对象上等待的线程,使其从wait()返回,而返回的前提是该线程获取到了对象的锁。
notifyAll()通知所有等待在该对象上的线程。
wait()调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。
wait(long)超时等待一段时间,这里的参数是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
wait(long, int)对于超时时间更细粒度的控制,可以达到毫秒。

-obj.wait() 让进入 object 监视器的线程到 waitSet 等待

-obj.notify() 在 object 上正在 waitSet 等待的线程中随机挑一个唤醒,让其解除阻塞可以参与竞争锁。

-obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。

必须获得此对象的锁,才能调用这几个方法。如果没有获得锁调用wait、notify会报错

wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到被notify 为止,然后参与竞争去重新获取锁继续向下执行。

wait(long n) 有时限的等待, 到 n 毫秒后结束等待并释放锁。然后参与竞争去重新获取锁继续向下执行或是被 notify参与竞争去重新获取锁继续向下执行。

wait(long) 为native方法,是Object类中,声明的;wait()方法时再Thread类中声明,其本质,调用的是Object的wait(long)方法,默认参数为0。

Wait Notify它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法。

验证wait必须获得对象的锁

public class Test18 {
    static final Object lock = new Object();
    public static void main(String[] args) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}

输出:

Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at cn.itcast.test.Test18.main(Test18.java:11)

验证notify必须获得对象的锁

public class Test18 {
    static final Object lock = new Object();
    public static void main(String[] args) {
            try {
                lock.notify();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
}

输出:

Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at cn.itcast.test.Test18.main(Test18.java:11)

notify唤醒wait

package wait;

public class Wait1 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        new Thread(()->{
            try {
                synchronized (lock) {
                    System.out.println("1获取到锁");
                    //wait() 方法会释放对象的锁
                    lock.wait();
                    System.out.println("永别了 牢笼1");
                }
            } catch(Exception e){
                e.printStackTrace();
            }

        }).start();
        Thread.sleep(3000);
        new Thread(()->{
                try {
                    synchronized (lock) {
                        System.out.println("2获取到锁");
                        lock.notify();
                        System.out.println("永别了 牢笼2");
                    }
                    } catch(Exception e){
                        e.printStackTrace();
                    }

        }).start();


       //Thread.sleep(3000);


    }
}
1获取到锁
2获取到锁
永别了 牢笼2
永别了 牢笼1

不唤醒wait线程

package wait;

public class Wait1 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        new Thread(()->{
            try {
                synchronized (lock) {
                    System.out.println("1获取到锁");
                    //wait() 方法会释放对象的锁
                    lock.wait();
                    System.out.println("永别了 牢笼1");
                }
            } catch(Exception e){
                e.printStackTrace();
            }

        }).start();
        Thread.sleep(3000);
        new Thread(()->{
                try {
                    synchronized (lock) {
                        System.out.println("2获取到锁");
                        //这里不再唤醒
                        //lock.notify();
                        System.out.println("永别了 牢笼2");
                    }
                    } catch(Exception e){
                        e.printStackTrace();
                    }

        }).start();
       //Thread.sleep(3000);

    }
}
1获取到锁
2获取到锁
永别了 牢笼2
//notify方法会在waitSet等待的线程中随机挑一个唤醒,让其解除阻塞可以参与竞争锁
//由于没有调用notify方法
//所以线程1会一直被阻塞

wait和sleep,wait会释放锁,sleep不会释放锁

Object lock = new Object();
new Thread(()->{
    synchronized (lock){
        try {
            System.out.println("2获取到锁");
            lock.wait(1);
            System.out.println("永别了 牢笼");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();


new Thread(()->{
    synchronized (lock){
        try {
            System.out.println("1获取到锁");
            Thread.sleep(5000);
            System.out.println("起床");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
2获取到锁

1获取到锁

起床

永别了 牢笼

由于线程2获取到锁,会立刻调用wait(1)释放锁,

线程1抢到锁,立刻睡眠5秒,执行完起床后释放锁

线程2抢到锁,从wait(1)的地方苏醒,继续向下执行,打印永别了 牢笼

假如有2个线程:

第一个线程 wait1秒然后睡5秒

第二个线程 wait1秒。

两种情况:此处只讨论wait和sleep

情况1:

-线程1先抢到,wait1秒,释放锁

-线程2获取到锁,执行wait1秒,释放锁

-线程1获取到锁,睡5秒,不释放锁,醒过来以后继续向下执行完所有的代码,释放锁。

情况2:

-线程2先抢到,wait1秒,释放锁

-线程1获取到锁,wait1秒,释放锁

-线程2获取到锁,执行完毕,释放锁

-线程1获取到锁,sleep5秒

Wait Notify 的正确姿势

sleep(long n) 和 wait(long n) 的区别

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法
  2. sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  4. 它们状态都是IMED_WAITING

WaitNotify原理

1620780519181.png

-调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态,Waiting是之前获取过锁的线程。

-BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片

-BLOCKED 线程会在 Owner 线程释放锁时唤醒

-WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争

wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到被notify 为止,然后参与竞争去重新获取锁继续向下执行。

wait(long n) 有时限的等待, 到 n 毫秒后结束等待。然后参与竞争去重新获取锁继续向下执行或是被 notify参与竞争去重新获取锁继续向下执行。

wait(long n)在等待/通知范式下引入超时等待模式,防止“永久”阻塞调用者,等待超时模式下,则分为两种情况:

1、在等待时间内被唤醒,则接着执行下面的代码。 即让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法 则当前线程被唤醒(进入“就绪状态”) ,进入EntryList 重新竞争。

2、未在等待时间内被唤醒, 超过指定的时间量,则当前线程被唤醒(进入“就绪状态”) ,进入EntryList 重新竞争

证明一下:

t2在wait1秒后,仍然会重新竞争获取到锁。

package join;

public class TestJoin5 {
    public static void main(String[] args) throws InterruptedException {
        TestJoin5 t = new TestJoin5();
        Thread thread = new Thread(() -> {
            synchronized (t) {
                System.out.println("我已经获取到锁了 哈哈哈哈");
                try {
                    Thread.sleep(100000);
                    System.out.println("睡醒了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                synchronized (t) {
                    System.out.println("第" + i + "次开始");
                    try {
                        t.wait(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第" + i + "次结束");
                }
            }
        });


        thread2.start();
        Thread.sleep(1000);
        thread.start();


    }
}


第0次开始
我已经获取到锁了 哈哈哈哈
睡醒了
第0次结束
第1次开始
第1次结束
第2次开始
第2次结束
第3次开始
第3次结束
package wait;

public class Wait1 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        new Thread(()->{
            try {
                synchronized (lock) {
                    System.out.println("1获取到锁");
                    //wait() 方法会释放对象的锁
                    lock.wait();
                    System.out.println("永别了 牢笼1");
                }
            } catch(Exception e){
                e.printStackTrace();
            }

        }).start();
        Thread.sleep(3000);
        new Thread(()->{
                try {
                    synchronized (lock) {
                        System.out.println("2获取到锁");
                        //线程1调用wait,进入了WaitSet 
                        //线程2调用了notify,会在waitSet等待的线程中挑一个唤醒加入entryList
                        //所以线程1 也不是立刻就能获取到锁,而是加入entryList有了竞争获取锁的权利
                        //必须等线程2 的代码块执行完毕 等线程2释放锁
                        //线程1竞争到锁线程1才能继续往下执行。
                        lock.notify();
                        System.out.println("永别了 牢笼2");
                    }
                    } catch(Exception e){
                        e.printStackTrace();
                    }

        }).start();


       //Thread.sleep(3000);


    }
}
1获取到锁
2获取到锁
永别了 牢笼2
永别了 牢笼1

-obj.wait() 让进入 object 监视器的线程到 waitSet 等待

-obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒加入entryList

-obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

Object lock = new Object();
new Thread(()->{
    synchronized (lock){
        try {
            System.out.println("2获取到锁");
            lock.wait(1);
            System.out.println("永别了 牢笼");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();


new Thread(()->{
    synchronized (lock){

        try {
            System.out.println("1获取到锁");
            Thread.sleep(5000);
            System.out.println("起床");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
2获取到锁
1获取到锁
起床
永别了 牢笼

由于线程2获取到锁,会立刻调用wait(1)释放锁,
线程1抢到锁,立刻睡眠5秒,执行完起床后释放锁
线程2抢到锁,从wait(1)的地方苏醒,继续向下执行,打印永别了 牢笼

sleep和wait的区别

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法
  2. sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  4. 它们状态都是IMED_WAITING

Wait Notify案例

案例:小南和小女用1房间干活,但是小南要有烟才能干活,小女要等外卖到了才能干活。

 
public class TestCorrectPostureStep5 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        //等待条件变量发生变化被唤醒
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //唤醒以后判断条件变量
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小南").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        //等待条件变量发生变化被唤醒
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //唤醒以后判断条件变量
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小女").start();
		
        
        //条件变量发生变化 唤醒等待线程,A和B都会被唤醒,获取到锁的可能是小男也可能是小女
        //如果小男获取到锁,判断没有烟,进入下一次循环,继续wait,并释放锁。
        //小女这次肯定可以拿到锁,执行干活逻辑
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖的").start();
        
         //条件变量发生变化 唤醒等待线程,同上。
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notifyAll();
            }
        }, "送烟的").start();

    }

}
工作线程1
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
工作线程2
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
条件变量线程:
synchronized(lock) {
修改条件
lock.notifyAll();
}

start 与 run

-直接调用run是在主线程中执行了run,没有启动新的线程

-使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

sleep 与 yield

sleep:

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield:

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

守护线程:垃圾回收线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

log.debug("开始运行...");
Thread t1 = new Thread(() -> {
 log.debug("开始运行...");
 sleep(2);
 log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");

输出:

08:26:38.123 [main] c.TestDaemon - 开始运行...

08:26:38.213 [daemon] c.TestDaemon - 开始运行...

08:26:39.215 [main] c.TestDaemon - 运行结束...

注意

-垃圾回收线程就是一种守护线程。

-Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求。