天天看点

对比分析线程休眠三种方式sleepwaitLockSupport

线程休眠

  • sleep
    • sleep线程休眠方式
    • 休眠当前线程
  • wait
    • 线程通讯机制
    • wait举例
    • wait方法
    • 面试问题
      • wait 为什么要加锁?
      • wait 为什么要释放锁?
      • Thread.sleep(0) 和 Object.lock(0) 区别
      • wait 和 sleep 的区别
      • 为什么 wait 会放到 Object 中而不是 Thread 中?
  • LockSupport
    • 不传参
    • 传参
    • wait 和 LockSupport 的区别

sleep

sleep线程休眠方式

sleep线程休眠一共有 3 种方式:

  • 方式一:

    Thread.sleep(1000);

  • 方式二:

    TimeUnit.SECONDS.sleep(1);

    (SECONDS为秒单位,还有day,month等)
  • 方法三:

    Thread.sleep(TimeUnit.SECONDS.toMillis(1));

休眠当前线程

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
public class ThreadDemo {
	public static void main(String[] args) throws InterruptedException {
		System.out.println(System.currentTimeMillis());
		Thread.sleep(3 * 1000);
		System.out.println(System.currentTimeMillis());
	}
}
           

sleep 休眠缺点:必须传递一个明确的结束数据

wait

线程通讯机制

线程通讯机制:一个线程的动作可以让另一个线程感知到就叫做线程通讯机制

wait(休眠)/ notify(唤醒)/ notifyAll(唤醒全部)

wait举例

我们让wait无限期休眠,1秒后让主线程唤醒它

public class ThreadDemo38 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1:进入线程方法");
                synchronized (lock) {
                    try {
                        //线程休眠
                        lock.wait();
//                    Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程1:执行完成");
            }
        });
        t1.start();

        Thread.sleep(1000);
        System.out.println("主线程唤醒线程1");

        synchronized (lock2) {
            //唤醒线程
            lock2.notify();
        }

    }

}

           
对比分析线程休眠三种方式sleepwaitLockSupport

wait/ notify/ notifyAll 使用注意事项

  1. 在使用以上方法的时候必须要加锁
  2. 加锁对象和 wait/ notify/ notifyAll 的对象必须保持一致,否则会报错
  3. 一组wait和 notfiy / notifiyAll 必须时同一对象
  4. notifyAll 只能唤醒当前对象的所有等待线程

wait方法

方法 描述
wait(long):void 毫秒级别最大等待时间
wait(long, int):void 纳秒级别最大等待时间
wait(l):void 调用wait(0),表示永久等待

如果线程有最大等待时间,在等待期间没有被唤醒,则会在最大等待时间时自己醒来

public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1:进入休眠"+new Date());
                synchronized (lock) {
                    try {
                        //线程休眠
                        lock.wait(3000);
//                    Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程1:执行完成"+new Date());
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2:进入休眠"+new Date());
                synchronized (lock) {
                    try {
                        //线程休眠
                        lock.wait(3000);
//                    Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程2:执行完成"+new Date());
            }
        });
        t2.start();

        Thread.sleep(2000);
        System.out.println("主线程唤醒线程1和线程2");

//        synchronized (lock) {
//            //唤醒线程
//            lock.notifyAll();
//        }

    }
           

这里线程自动醒来

对比分析线程休眠三种方式sleepwaitLockSupport

可以看到,线程自动在3秒后醒来了

如果提前唤醒他,

对比分析线程休眠三种方式sleepwaitLockSupport

可以看到,线程在2秒后被唤醒

面试问题

wait 为什么要加锁?

wait在使用的时候需要释放锁,在释放锁之前必须要有一把锁,所以要加锁

wait 为什么要释放锁?

wait默认是不传任何值的,当不传递任何值得时候表示永久等待,这样就会造成一把锁被一个线程一直持有,为了避免这种问题的方式,所以在使用 wait 时一定要释放锁

Thread.sleep(0) 和 Object.lock(0) 区别

  1. sleep 它是 Thread 的静态方法;而 Lock 是 Object 的方法;
  2. sleep(0) 立即触发一次 CPU 资源的抢占;而 lock(0) 是永久等待下去

wait 和 sleep 的区别

相同点:

  1. 都可以让当前线程休眠
  2. 都必须要处理一个 Interrupt 异常

不同点:

  1. wait 来自与 Object 中的一个方法;而 sleep 来自于 Thread
  2. 传参不同,wait 可以没有参数,而 sleep 必须有一个大于等于 0 的参数
  3. wait 使用必须加锁,sleep 使用时不用加锁
  4. wait 使用时会释放锁,而 sleep 使用时不会释放锁
  5. wait 默认不传参的情况下会进入 WAITING 状态,而 sleep 会进入 TIME_WAITING 状态

为什么 wait 会放到 Object 中而不是 Thread 中?

wait 必须要加锁和释放锁,而锁又是属于对象级别的,而不是线程级别的(线程和锁是一对多的关系,也就是一个线程可以拥有多把锁),为了灵活起见(一个线程当中会有多把锁),就把 wait 放在 Object 当中

LockSupport

不传参

不传参默认是 无限期休眠,WAITING状态

我们想指定唤醒某个线程的时候,这时候notify就不能够满足了,因为它是随机唤醒的

我们可以用 LockSupport.park(); 方法让线程休眠

用LockSupport.unpark(t1); 方法让指定线程唤醒

Object lock = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1:进入休眠"+new Date());
                LockSupport.park();
                System.out.println("线程1:执行完成"+new Date());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2:进入休眠"+new Date());
                LockSupport.park();
                System.out.println("线程2:执行完成"+new Date());
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3:进入休眠"+new Date());
                LockSupport.park();
                System.out.println("线程3:执行完成"+new Date());
            }
        });

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(1000);
        System.out.println("唤醒线程");
        LockSupport.unpark(t1);

        Thread.sleep(1000);
        System.out.println("唤醒线程");
        LockSupport.unpark(t2);

        Thread.sleep(1000);
        System.out.println("唤醒线程");
        LockSupport.unpark(t3);
           

其次,LockSupport 虽然不用处理interrupt 异常,但是能正常感知 interrupt 异常

Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程休眠前状态:" +
                        Thread.interrupted());
                LockSupport.park();
                System.out.println("线程休眠后状态:" +
                        Thread.interrupted());
            }
        });
        t1.start();

        Thread.sleep(1000);

        //终止线程
        t1.interrupt();
        LockSupport.unpark(t1);
        
           
对比分析线程休眠三种方式sleepwaitLockSupport

可以正常感知 interrupt 异常

传参

使用 LockSupport.parkUntil 方法进行传参

注意:此方法传参并不是和wait,sleep一样是等待多长时间停止休眠,而是具体的时间

比如我这里就是系统现在时间 + 1秒后进行唤醒

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程进入休眠:" + new Date());
                LockSupport.parkUntil(System.currentTimeMillis() + 1000);
                System.out.println("线程终止休眠:" + new Date());
            }
        }).start();
           

wait 和 LockSupport 的区别

相同点 :

  1. 二者都可以让线程进行休眠
  2. 二者都可以传参或者不传参,并且二者线程状态也是一致的

不同点:

  1. wait 必须要配合 synchronized 一起使用(必须加锁),而 LockSupport 不用加锁
  2. wait 只能唤醒全部和随机的线程,而 LockSupport 可以唤醒指定的线程