1.线程池补充
- 快捷方式创建线程池,为什么不能用?
- 不能使用无边界的阻塞队列——生产速度(提交任务)如果快于消费速度(线程执行速度),阻塞队列存放的任务会越来越多,导致OOM,整个进程换掉。
- 即便指定了有边界的阻塞队列,还需要指定拒绝策略(一般是自定义),一般是记录任务(日志,数据库,其他)
2.死锁
class Pen {
private String pen = "笔" ;
public String getPen() {
return pen;
}
}
class Book {
private String book = "本" ;
public String getBook() {
return book;
}
}
public class DeadLock {
private static Pen pen = new Pen() ;
private static Book book = new Book() ;
public static void main(String[] args) {
new DeadLock().deadLock();
}
public void deadLock() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (pen) {
System.out.println(Thread.currentThread()+" :我有笔,我就不给你");
Thread.sleep(1000);
synchronized (book) {
System.out.println(Thread.currentThread()+" :把你的本给我!");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Pen") ;
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (book) {
System.out.println(Thread.currentThread()+" :我有本子,我就不给你!");
Thread.sleep(1000);
synchronized (pen) {
System.out.println(Thread.currentThread()+" :把你的笔给我!");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Book") ;
thread1.start();
thread2.start();
}
}
- 死锁产生的条件:
- 多个线程申请所资源
形成的结果:死锁的线程始终处于阻塞态,任务会一直无法执行。
- 解决方式:
- 资源一次性分配(破环请求与保持条件)
- 可剥夺资源,在线程满足条件时,释放掉已占有的资源;
- 资源有序分配:系统为每类资源赋予一个编号,每个线程按照编号递请求资源,释放则相反。
- 检测死锁的手段:
- jconsole中的检测死锁
- jstack
3. volatile
- 作用:修饰在变量上
- 保证内存的可见性
- 建立内存屏障,禁止指令重排序
- 原理:
- 如何保证可见性?
缓存一致性协议:读取时,CPU缓存值置为无效,从主存读
写回主存,发起通知。 - 如何禁止指令重排序?
指令重排序,有as-if-serial的规范,规定了如何重排序(单线程下,前后关联的不能重排序),但是不保证多线程下,指令之间的重排序。
字节码层面看:
提供了主存到工作内存,读写操作的8大原子性指令,这些指令,存在happens-before的原则。
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁;
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。(读/写,都是原子性的指令)
传递性:就是happens-before原则具有传递性,即hb(A,B),hb(B,C),那么hb(A,C).
CPU层面看
使用volatile修改的变量操作,CPU指令(汇编)是lock前缀指令(加锁操作)
变量写回主存时,之前的操作都需要完成;
加锁属于总线锁/高速缓存锁
加锁类似于建立了一个屏障:写回主存,之前的都完成,之后的读操作(读和写不能重排序)
4.callable的使用
- java中,创建线程只有一种方式,必须是new Thread
- 如何定义线程要执行的任务代码?
- 继承Thread,重写run
- 实现Runnable,重写run
- 实现Callable接口,重写call方法
提供了一种获取线程执行结果的方式
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(1);
return 2;
}
};
FutureTask task = new FutureTask(callable);
new Thread(task).start();
System.out.println(task.get());
System.out.println(3);
}
}
- Runnable和Callable也可以作为任务,在线程池中提交
new Thread(task).start();
System.out.println(task.get());
System.out.println(3);
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(new Runnable() {
@Override
public void run() {
}
});
pool.submit(new Runnable() {
@Override
public void run() {
}
});
Future<String> future=pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(1);
return "2";
}
});
future.get();