1、 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
Thread t1 = new Thread(() -> System.out.println("t1执行"));
Thread t2 = new Thread(() -> System.out.println("t2执行"));
Thread t3 = new Thread(() -> System.out.println("t3执行"));
try {
// t1先启动
t1.start();
t1.join();
// t2
t2.start();
t2.join();
// t3
t3.start();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
上述主要是利用join方法 等待该线程终止。主线程等待t1执行完 再开启t2线程,等待t2线程执行完再执行线程t3
join方法源码分析
public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
2、 什么是线程
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
3、 线程和进程有什么区别?
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间
4、 Java中Runnable和Callable有什么不同?
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是
(1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,c表示异步计算的结果。
5、Java中CountDownLatch 和CyclicBarrier 有什么不同?
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能,一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
CyclicBarrier通过它可以实现让一组线程等待至某个状态之后再全部同时执行,CyclicBarrier可以被重用
CountDownLatch |
CyclicBarrier |
减计数方式 |
加计数方式 |
计算为0时释放所有等待的线程 |
计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 |
计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 |
调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 |
可重复利用 |
6、线程运行的多种状态
- 创建 new Thread类或者其子类对象。
- 运行 start(). 具备者CPU的执行资格和CPU的执行权。
- 冻结 释放了CPU的执行资格和CPU的执行权。比如执行到sleep(time) wait() 导致线程冻结。
- 临时阻塞状态: 具备者CPU的执行资格,不具备CPU的执行权。
- 消亡:线程结束。run方法结束
7、Java中的volatile 变量是什么?
volatile是一个特殊的修饰符,只有成员变量才能使用它。 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
volatile特性一:内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。
volatile特性二:可以禁止指令重排序
volatile关键字保证了操作的可见性,但是不能保证对变量的操作是原子性。
8、 volatile的原理和实现机制
加入volatile关键字时,生成的汇编代码会多出一个lock前缀指令
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
9、 使用volatile关键字的场景
1、状态标记量
volatile boolean flag= false;
// 线程1
context = loadContext();
flag= true;
// 线程2
while(!flag) {
sleep();
}
doSomethingWithConfig(context);
2、双重检查
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {// 1
if (instance == null) {// 2
instance = new Singleton();// 3
}
}
}
return instance;
}
}
3、独立观察(independent observation)
安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变