sleep和wait区别,并且查看线程运行状态

一、sleep和wait区别

区别一:语法使用不同

wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出 IllegalMonitorStateException 的异常

而 sleep 可以单独使用,无需配合 synchronized 一起使用。

区别二:所属类不同

wait 方法属于 Object 类的方法,而 sleep 属于 Thread 类的方法

区别三:唤醒方式不同

sleep 方法必须要传递一个超时时间的参数,且过了超时时间之后,线程会自动唤醒。而 wait 方法可以不传递任何参数,不传递任何参数时表示永久休眠,直到另一个线程调用了 notify 或 notifyAll 之后,休眠的线程才能被唤醒。也就是说 sleep 方法具有主动唤醒功能,而不传递任何参数的 wait 方法只能被动的被唤醒

区别四:释放锁资源不同

wait 方法会主动的释放锁,而 sleep 方法则不会。接下来我们使用代码的方式来演示一下二者的区别。

sleep 不释放锁

接下来使用 sleep 是线程休眠 2s,然后在另一个线程中尝试获取公共锁,如果能够获取到锁,则说明 sleep 在休眠时会释放锁,反之则说明不会释放锁,

在调用了 sleep 之后,在主线程里尝试获取锁却没有成功,只有 sleep 执行完之后释放了锁,主线程才正常的得到了锁,这说明 sleep 在休眠时并不会释放锁。

wait 释放锁

接下来使用同样的方式,将 sleep 替换成 wait,在线程休眠之后,在另一个线程中尝试获取锁,

当调用了 wait 之后,主线程立马尝试获取锁成功了,这就说明 wait 休眠时是释放锁的

区别五:线程进入状态不同

调用 sleep 方法线程会进入 TIMED_WAITING 有时限等待状态,而调用无参数的 wait 方法,线程会进入 WAITING 无时限等待状态。

总结

sleep 和 wait 都可以让线程进入休眠状态,并且它们都可以响应 interrupt 中断,但二者的区别主要体现在:语法使用不同、所属类不同、唤醒方式不同、释放锁不同和线程进入的状态不同。 ​

二、线程的状态Thread.State

三、供参考的多线程代码一、sleep和wait(WAITING和TIMED_WAITING状态)


public class Test04_ThreadState {

    public static void main(String[] args) {
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();
        new Thread(new Waiting(), "WaitingThread").start();

    }
    /**
     * 该线程不断的进行睡眠
     */
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtils.second(100);
            }
        }
    }
    /**
     * 该线程在Waiting.class实例上等待
     */
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

四、查看线程运行状态的方法

1.jps

获得线程号和线程的类名

2.jstack + 线程号

jstack 2636

看线程某个时刻的运行情况(线程的快照)

这里打印出来的是当前线程的快照dump。

3.jvisualvm(可视化软件)

在java的jdk目录下的bin目录下有jvisualvm.exe文件,可以

打开软件后可以看到正在运行的进程,如下图

可以在其中找到java代码创建的线程的名字对应的线程

可以看到WatingThread线程状态是WAITING,等待状态

TimeWaitingThread线程状态是TIMED_WAITING,超时等待状态

和我们这篇文章的  二、线程的状态Thread.State  对应。

五、案例

一、Blocked阻塞状态

Blocked阻塞状态代码


public class Test04_ThreadState {

    public static void main(String[] args) {
        new Thread(new Blocked(), "BlockedThread-1").start();
        new Thread(new Blocked(), "BlockedThread-2").start();
    }
    
    /**
     * 该线程在Blocked.class实例上加锁后,不会释放该锁
     */
    static class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtils.second(100);
                }
            }
        }
    }
}

Blocked阻塞状态代码jvisualvm查看

由于BlockedThread-1先创建,抢占到了系统资源,运行后拿到了锁,并调用了sleep方法,因此进入TIMED_WAITING超时等待状态,而BlockedThread-2拿不到锁,因此进入Blocked阻塞状态。线程快照dump如下图:

Blocked阻塞状态代码总结

1.触发Blocked状态的原因

由于BlockedThread-1先创建,抢占到了系统资源,运行后拿到了锁,并调用了sleep方法,因此进入TIMED_WAITING超时等待状态,而BlockedThread-2拿不到锁,因此进入BLOCKED阻塞状态。

2.sleep方法

由于sleep方法不会释放锁,因此BlockedThread-2无法拿到锁,进入阻塞状态,更加验证了sleep方法不会释放锁。

二、ReentrantLock可重入锁

ReentrantLock可重入锁代码


public class Test04_ThreadState {

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(new Sync(), "SyncThread-1").start();
        new Thread(new Sync(), "SyncThread-2").start();
    }
   
    static class Sync implements Runnable {

        @Override
        public void run() {
            lock.lock();
            try {
                SleepUtils.second(100);
            } finally {
                lock.unlock();
            }
        }

    }
}

ReentrantLock可重入锁代码jvisualvm查看

ReentrantLock可重入锁代码总结

1.synchronized和ReentrantLock区别(简略描述,之后会详细补充)

ReentrantLock锁更加面向对象
两个锁加锁之后另外一个线程进入状态不一样
synchronized进入BLOCKED状态是被动的 还没有进入到同步代码块中
ReentrantLock是一种主动进入锁的状态 已经进入到代码块中 程序恢复之后 它会从等待的位置继续执行

当SyncThread-2线程进入,而SyncThread-1线程已经拿到锁(lock)
此时SyncThread-2线程会进入WAITING等待状态。

三、有参wait方法和无参wait方法

有参wait方法和无参wait方法代码

public class Test04_ThreadState {

    public static void main(String[] args) {
        new Thread(new Waiting(), "WaitingThread").start();
        new Thread(new WaitingMillisecond(), "WaitingMillisecondThread").start();
    }
  
    /**
     * 该线程在Waiting.class实例上等待,无参wait方法
     */
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 该线程在Waiting.class实例上等待,有参wait方法,单位为毫秒
     */
    static class WaitingMillisecond implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (WaitingMillisecond.class) {
                    try {
                        WaitingMillisecond.class.wait(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

有参wait方法和无参wait方法代码jvisualvm查看

有参wait方法和无参wait方法代码总结

1.有参wait方法参数单位

有参wait方法参数单位为毫秒

2.有参wait方法和无参wait方法的区别

有参wait方法会进入TIMED_WAITING超时等待状态

无参wait方法会进入WAITING等待状态

四、死锁

死锁代码


public class Test05_DeadLockDemo {

    /** A锁 */
    private static String A = "A";
    /** B锁 */
    private static String B = "B";

    public static void main(String[] args) {
        new Test05_DeadLockDemo().deadLock();
    }

    private void deadLock() {
        Thread t1 = new Thread(()->{
            synchronized (A) {
                SleepUtils.second(2);
                synchronized (B) {
                    System.out.println("1");
                }
            }
        });

        Thread t2 = new Thread(()->{
            synchronized (B) {
                synchronized (A) {
                    System.out.println("2");
                }
            }
        });
        t1.start();
        t2.start();
    }

}

死锁代码jvisualvm查看

死锁代码总结

分析:

t1线程和t2线程同时需要拿到A和B的锁

t1拿到了A锁,t2拿到了B锁

此时t2拿A锁时,A已经被t1线程占用了 ,t2进入阻塞状态

t1拿B锁时,B已经被t2线程占用了 ,t1进入阻塞状态

两个线程都进入阻塞状态,产生死锁问题。

解决办法:

1、避免多次锁定。尽量避免同一个线程进行多次Lock 。

2、使用相同的加锁顺序。如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。

3、使用定时锁。程序在调用 wait() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。

4、死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。

五、非守护线程(不使用守护线程方法,这里不算守护线程,只是举例)

非守护线程(不使用守护线程方法)代码

public class Test06_Daemon {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        Thread thread = new Thread(new DeamonRunner());

        thread.start();

        SleepUtils.second(3);
        System.out.println("over");
    }

    //这是条狗--追随主人
    static class DeamonRunner implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("守护线程开始睡觉");
            try {
                SleepUtils.second(6);
            } finally {
                System.out.println("DeamonRunner 最后执行");
            }
        }
    }
}

非守护线程(不使用守护线程方法)代码执行结果

main
Thread-0
守护线程开始睡觉
over
DeamonRunner 最后执行

进程已结束,退出代码0

非守护线程(不使用守护线程方法)代码总结

当启动非守护线程的进程结束了,非守护线程不会立刻结束,即使启动非守护线程的进程结束了,非守护线程代码也会执行完自己的代码。

六、守护线程(使用守护线程方法,真正的守护线程)

守护线程代码


public class Test06_Daemon {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        Thread thread = new Thread(new DeamonRunner());
        //设置为main线程的守护线程
        thread.setDaemon(true);
        thread.start();

        SleepUtils.second(3);
        System.out.println("over");
    }

    //这是条狗--追随主人
    static class DeamonRunner implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("守护线程开始睡觉");
            try {
                SleepUtils.second(6);
            } finally {
                System.out.println("DeamonRunner 最后执行");
            }
        }
    }
}

守护线程代码执行结果

main
Thread-0
守护线程开始睡觉
over

进程已结束,退出代码0

守护线程代码总结

使用setDeamon(true)方法,将此线程作为守护线程,守护当前执行体的线程。

此方法必须在线程启动之前调用。

主线程结束了,守护线程也会立刻结束,如果守护线程代码没执行完,守护线程后续没执行的代码也不会再执行。

最后总结:

        a.线程的状态 
            jps看线程的线程号
            jstack看线程某个时刻的运行情况(线程的快照)
            jvisualvm对线程进行dump

        b.线程调用sleep
            进入TIMED_WAITING超时等待状态 不会释放锁
        c.线程调用wait
            调用无参wait方法进入WAITING状态 会释放锁    

            调用有参wait方法,加上等待时间进入TIMED_WAITING超时等待状态 会释放锁    
        d.A线程进入B线程已经拿到锁(synchronized)
            A线程会进入BLOCKED阻塞状态
        e.当A线程进入B线程已经拿到锁(lock)
            A线程会进入WAITING等待状态
        f.synchronized和lock区别
            1.lock锁更加面向对象
            2.两个锁加锁之后另外一个线程进入状态不一样:

                synchronized是BLOCKED状态,lock是WAITING状态
            3.synchronized进入BLOCKED状态是被动的 还没有进入到同步代码块中
            4.lock是一种主动进入锁的状态 已经进入到代码块中 程序恢复之后 它会从等待的位置继续执行

        g.死锁 
            双方各自持有对方需要的锁,不释放 ,双方都处于一种BLOCKED状态
        h.守护线程 
            如果被守护的线程终止 该守护线程也会终止。如果启动非守护线程的进程终止,非守护线程会执行完代码再终止。 

如果你不太理解synchronized锁的对象是什么,你可以看下面这篇文章:

synchronized锁的对象是什么

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穗余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值