java多线程:详解线程唤醒和阻塞的五种常用方法,sleep,suspend和 resume,wait和 notify,yield,join

1. sleep() 方法:

sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。(暂停线程,不会释放锁,睡眠结束,线程继续执行,线程自动释放锁

public class ThreadTest {

    public static void test() {

        new Thread (() -> {
            synchronized (ThreadTest.class) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                try {
                    Thread.sleep (5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("执行结束自动释放锁");
            }
        }).start ();

        new Thread (() -> {
            try {
                Thread.sleep (1000L);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (ThreadTest.class) {
                System.out.println ("线程二获得了ThreadTest对象锁");
            }
        }).start ();

    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 线程一获得了ThreadTest对象锁
  • 执行结束自动释放锁
  • 线程二获得了ThreadTest对象锁

分析:由于线程一率先获得了对象锁,开始执行sleep方法,此时线程一处于阻塞状态,而sleep并不会释放锁。导致线程二未能拿到锁,线程二处于阻塞状态。等待线程一睡眠时间完毕,线程一继续运行,线程一执行完毕自动释放锁,线程二成功获得对象锁,开始执行线程二同步代码块。

2.suspend() 和 resume() 方法:

挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(suspend不会释放锁,等待resume唤醒,线程继续执行,执行完毕,自动释放锁,不建议用,容易发生死锁,该方法已被JDK弃用

suspend() 
import java.util.concurrent.TimeUnit;

public class ThreadTest {

    public static void test() {

        Thread t1 = new Thread (() -> {
            synchronized (ThreadTest.class) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                try {
                    TimeUnit.SECONDS.sleep (2);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("执行结束自动释放锁");
            }
        });
        Thread t2 =  new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (ThreadTest.class) {
                System.out.println ("线程二获得了ThreadTest对象锁");
            }
        });

        t1.start ();
        t2.start ();
        try {
            System.out.println ("主线程休眠1秒钟");
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        t1.suspend ();
        System.out.println ("线程一被挂起");
    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 主线程休眠1秒钟
  • 线程一获得了ThreadTest对象锁
  • 线程一被挂起

分析:由于线程一率先获得了对象锁,而suspend并不会释放锁,如果没被resume唤醒,程序出现死锁,线程二无法获得对象锁,永远不会被执行。

resume() 

import java.util.concurrent.TimeUnit;

public class ThreadTest {

    public static void test() {

        Thread t1 = new Thread (() -> {
            synchronized (ThreadTest.class) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                try {
                    TimeUnit.SECONDS.sleep (2);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("线程一被唤醒");
                System.out.println ("执行结束自动释放锁");
            }
        });
        Thread t2 =  new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (ThreadTest.class) {
                System.out.println ("线程二获得了ThreadTest对象锁");
            }
        });

        t1.start ();
        t2.start ();
        try {
            System.out.println ("主线程休眠1秒钟");
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        t1.suspend ();
        System.out.println ("线程一被挂起");
        t1.resume ();
    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 主线程休眠1秒钟
  • 线程一获得了ThreadTest对象锁
  • 线程一被挂起
  • 线程一被唤醒
  • 执行结束自动释放锁
  • 线程二获得了ThreadTest对象锁

分析:由于线程一率先获得了对象锁,接着线程一被挂起,线程一阻塞,并没有释放锁,等待被唤醒,线程二没有拿到锁,处于阻塞状态,直到主线程执行resume,线程一被成功唤醒,线程一执行结束释放锁,线程二拿到锁,执行同步代码块。

3.wait() 和 notify() 方法:

两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁,再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)

wait()

import java.util.concurrent.TimeUnit;

public class ThreadTest {
    private static ThreadTest lock = new ThreadTest ();

    public static void test() {

        new Thread (() -> {
            synchronized (lock) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                try {
                    lock.wait ();
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("执行结束自动释放锁");
            }
        }).start ();

        new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (lock) {
                System.out.println ("线程二获得了ThreadTest对象锁");
            }
        }).start ();

    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 线程一获得了ThreadTest对象锁
  • 线程二获得了ThreadTest对象锁

分析:由于线程一率先获得了对象锁,执行同步代码块,wait方法被执行,线程一处于等待状态,由于wait方法会先释放对象锁,线程二成功获得锁,执行同步代码块。

notify()

import java.util.concurrent.TimeUnit;

public class ThreadTest {
    private static ThreadTest lock = new ThreadTest ();

    public static void test() {

        new Thread (() -> {
            synchronized (lock) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                try {
                    lock.wait ();
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("线程一被唤醒成功");
            }
        }).start ();

        new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (lock) {
                System.out.println ("线程二获得了ThreadTest对象锁");
                lock.notify ();
            }
        }).start ();

    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 线程一获得了ThreadTest对象锁
  • 线程二获得了ThreadTest对象锁
  • 线程一被唤醒成功

分析:由于线程一率先获得了对象锁,接着执行wait方法,处于等待状态,wait方法会自动释放锁,线程二拿到锁,执行notify方法,线程一被唤醒成功,线程一继续运行。

4. yield() 方法:

该方法与sleep()类似,只是不能由用户指定暂停多长时间,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知,不会释放锁

yield()

import java.util.concurrent.TimeUnit;

public class ThreadTest {
    private static ThreadTest lock = new ThreadTest ();

    public static void test() {

        new Thread (() -> {
            synchronized (lock) {
                System.out.println ("线程一获得了ThreadTest对象锁");
                Thread.yield ();
                try {
                    TimeUnit.SECONDS.sleep (2);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                System.out.println ("执行结束自动释放锁");
            }
        }).start ();

        new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (lock) {
                System.out.println ("线程二获得了ThreadTest对象锁");
            }
        }).start ();

    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • 线程一获得了ThreadTest对象锁
  • 执行结束自动释放锁
  • 线程二获得了ThreadTest对象锁

分析:由于线程一率先获得了对象锁,执行yield方法,线程一任然处于可执行状态,但yield方法并不释放锁,所以线程二还是处于阻塞状态,等待线程一执行完毕,线程二获得对象锁,线程二执行同步代码块。

5.join()方法:

当主线程开启一个或多个子线程的时候,使用join方法,必须等该线程运行结束,主线程或其他子线程才由阻塞状态转为可执行状态。

join()

import java.util.concurrent.TimeUnit;

public class ThreadTest {
    private static ThreadTest lock = new ThreadTest ();

    public static void test() {

        Thread t1 = new Thread (() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println ("A----" + i);
            }
        });

        Thread t2 = new Thread (() -> {
            for (int j = 0; j < 5; j++) {
                System.out.println ("B----" + j);
            }
        });

        t1.start ();
        try {
            t1.join ();
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        t2.start ();

    }

    public static void main(String[] args) {
        test ();
    }
}

控制台输出:

  • A----0
  • A----1
  • A----2
  • A----3
  • A----4
  • B----0
  • B----1
  • B----2
  • B----3
  • B----4

分析:主线程开始运行t1线程,接着join方法被调用,主线程处于阻塞状态,等待t1线程执行完毕,主线程状态变为可运行状态,t2线程被执行。

6.总结:以上是Java线程唤醒和阻塞的五种常用方法,不同的方法有不同的特点,其中wait() 和 notify()是其中功能最强大、使用最灵活的方法,但这也导致了它们效率较低、较容易出错的特性,因此,在实际应用中应灵活运用各种方法,以达到期望的目的与效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值