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,这时可以使用 yield
或 sleep
来让出 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 闲时,优先级几乎没作用。例如:
- 加 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
*/
- 设置优先级
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
*/
- 同时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()方法的区别:
- sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法
通常(取决于调度器)
给优先级相同或优先级更高的线程执行机会的概率要大一些。 - sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行。
- sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。
- 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已经执行结束了。