Thread类的方法之一--start、run、sleep、yield、join

suspend()、resume()、stop()方法已经过时,不建议使用,容易破坏同步代码块,造成线程死锁。

1. start 和 run

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码,注意调用start方法只是让线程进入Runnable(就绪)状态,等待调度器调度(CPU分配时间片),并不意味着马上执行,只有得到CPU时间片线程才会进入Running(运行)状态。

2. sleep

让当前正在执行的线程(写在哪个线程里哪个线程暂停)暂停一段时间,调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)。

sleep()方法有两种重载形式。

  • static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
  • static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。

当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,注意睡眠结束后的线程未必会立刻得到执行,还需要等待调度器的调度。因此sleep()方法常用来暂停程序的执行以及限制cpu的使用。

  • 应用sleep限制cpu的使用

在没有利用 cpu 来计算时,不要让while(true) 空转浪费 cpu,这时可以使用 yieldsleep 来让出 cpu 的使用权给其他程序。

while(true) { 
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) { 
        e.printStackTrace();
    } 
}

有一个问题是sleep方法的可读性不太强,其实Java1.5后提供了TimeUnit.时间单位.sleep方法,这个方法可读性比较强,效果跟sleep完全相同(因为底层也是调用了sleep方法)

TimeUnit.SECONDS.sleep(1);  // sleep一秒钟
Thread.sleep(1000)          // 等价

3. yield

它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是让该线程从Running(运行)进入 Runnable(就绪)状态,然后让出处理器资源去调度执行其它线程。yield()只是让当前线程“暂停”一下,让系统的线程调度器重新调度一次,具体的实现依赖于操作系统的任务调度器,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。

  • 在这里介绍一下线程优先级

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。Thread类提供了setPriority(int newPriority)getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下三个静态常量。

  • MAX_PRIORITY :其值是10。
  • MIN_PRIORITY :其值是1。
  • NORM_PRIORITY:其值是5。

其实调度器并不是严格按照优先级调度,只是说优先级高的被调度的几率大一下,如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用。例如:

  1. 加 yield
public class Test {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
                Thread.yield();         // 1
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
//        t1.setPriority(Thread.MIN_PRIORITY); // 2
//        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

// yield让出后得到的机会少
/*
---->1 158521
---->1 158522
---->1 158523
---->1 158524
              ---->2 1043
---->1 158525
---->1 158526
---->1 158527
---->1 158528
*/
  1. 设置优先级
public class Test {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
//                Thread.yield();         // 1
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.setPriority(Thread.MIN_PRIORITY);  // 2
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

// 优先级低的得到的机会少,但他们两个之间具体什么关系依赖于调度器
/*
---->1 3
---->1 4
---->1 5
              ---->2 49
              ---->2 50
              ---->2 51
              ---->2 52
*/
  1. 同时yield和设置优先级
public class Test {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
               Thread.yield();         // 1
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.setPriority(3);                          // 2
        t2.setPriority(2);
        t1.start();
        t2.start();
    }
}

// 优先级低并且yield后的得到的机会更少,但仍然会得到调度机会,他们两个之间具体什么关系依赖于调度器
/*
---->1 11632
---->1 11633
---->1 11634
---->1 11635
---->1 11636
              ---->2 144
              ---->2 145
              ---->2 146
              ---->2 147

sleep()方法和yield()方法的区别:

  1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法通常(取决于调度器)给优先级相同或优先级更高的线程执行机会的概率要大一些。
  2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行。
  3. sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。
  4. sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

4. join

当在某个程序执行体中(在哪个线程的执行体里调用,则哪个线程被阻塞)调用其他线程的join()方法时,此线程将被阻塞,Java中是由Runnable转到Waiting状态,直到被join()方法加入的线程执行完为止。如果此时有多个线程,则多个线程(除了被join的线程)并发执行。join()方法内部调用了wait()方法,可以把join()理解成特殊的wait(long millis)方法,这个方法的millis恰好等于join的线程的执行时间。关于join的源码详解请移步这里

join()方法有如下3种重载形式:

  • join():等待被join的线程执行完成。
  • join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。
  • join(long millis, int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫微秒。

通过下面这个例子能更加深入的理解join的作用:

public class Test {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        t1.join();   // 1. 不使用join  2. 使用join
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}
// 不使用join,main线程先于t1线程执行,r打印为0
/*Output1
00:47:15.208 c.Test10 [main] - 开始
00:47:15.309 c.Test10 [t1] - 开始
00:47:15.309 c.Test10 [main] - 结果为:0
00:47:15.311 c.Test10 [main] - 结束
00:47:16.315 c.Test10 [t1] - 结束
*/
// 使用join,main线程阻塞等t1线程执行结束再打印r为10,此处如果用sleep也可以但不好,因为无法预测准确的时间。
/*Output2
00:43:15.128 c.Test10 [main] - 开始
00:43:15.216 c.Test10 [t1] - 开始
00:43:16.218 c.Test10 [t1] - 结束
00:43:16.218 c.Test10 [main] - 结果为:10
00:43:16.221 c.Test10 [main] - 结束
*/

那么join用来干什么哪?我们可以借助join实现多线程的同步,同步和异步的概念以调用方角度来讲如下:

  • 需要等待结果返回,才能继续运行就是同步;
  • 不需要等待结果返回,就能继续运行就是异步。

例如我们要等待多个线程的执行结果:

public class TestJoin {
    static int r = 0;
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test();
    }
    
    private static void test() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(1);
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            sleep(2);
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t1.join();
        log.debug("t2 join end");
        t2.join();
        log.debug("t1 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

/*Output:
21:33:39.856 c.TestJoin [main] - join begin
21:33:41.864 c.TestJoin [main] - t2 join end
21:33:41.867 c.TestJoin [main] - t1 join end
21:33:41.868 c.TestJoin [main] - r1: 10 r2: 20 cost: 2014

上例我们发现,如果join多个线程,join的多个线程并行执行,则被join的线程等待的时间应该为最长线程的执行时间。如果颠倒两个 join 呢?最终都是输出相同的结果,因为如果先join t2,则t2执行结束后,t1.join就无需等待了,此时t1已经执行结束了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值