【1年Java面试资料整理】多线程
一、实现多线程方式
- 继承Thread类
- 实现Runnable接口,重写run()方法
- 实现Callable接口,通过FutureTask包装器创建Thread线程(JDK 1.5),有返回值
public interface Callable<V> { V call() throws Exception; }
二、CountDownLatch和CyclicBarrier
- CountDownLatch
- 减计数方式
- 计数为0时,释放所有等待的线程
- 计数为0,无法重置
- 不可重复用
- CyclicBarrier
- 加计数方式
- 计数达到指定值,释放所有等待的线程
- 计数达到指定值,计数重置0
- 可重复用
三、volatile
- 定义:修饰的变量,会具有可见性。
- 理解:
- 在多个线程中,同一个共享变量,有可能会由于运行时缓存机制,得到不同的结果
- 使用volatile修饰变量时,每次取共享变量会从主存取
- 禁止指令重排优化(指令重排:虚拟机优化程序执行顺序,不影响程序执行结果,但是在多线程环境中,可能会线程不安全)
- 适合1个写,多个读的场景
- 为什么volatile没有原子性
- 如果中间有其他的CPU修改了值,就会丢失
- 解决原子性,可以用CAS(基于乐观锁机制)
- JDK 1.5 提供了原子操作类 如AtomicInteger JUC包(java.util.concurrent)
四、线程间通信
- 共享变量(这个很容易理解啦)
- wait、nitify
- wait:释放当前锁,让出cpu,进入等待状态
- notify:唤醒正在等待的线程,不会立马释放锁
- Lock/Condition机制(这个以后再去了解)
- 管道机制,创建管道流 PipedOutputStream PipedInputStream
五、同步集合和并发集合区别
- 同步集合List、Map等,会将整个List或Map锁起来
- 并发集合(JDK 1.5)ConcurrentHashMap等,将Map划分几个片段,只对相关的几个片段上锁
六、线程池
- JDK 1.5 提供Executor框架,创建不同的线程池
- submit()和execute()方法区别
- submit有返回值,不会抛出异常
- execute不关心返回值,会抛出异常
- 常用线程池
- newSingleThreadExecutor 单线程化线程池
- newFixedThreadPool 定长线程池
- newCachedThreadPool 可缓存线程池
- newScheduledThreadPool 支持定时、周期性任务执行
七、死锁
- 死锁定义:竞争资源产生互相等待的现象
- 死锁产生条件:
- 互斥条件:一个资源每次只能被一个线程调用
- 请求与保持条件:阻塞时,不释放资源
- 不剥夺条件:已获得的资源,未使用完之前,不能强行剥夺
- 循环等待
- 防止死锁
- 设置加锁顺序
- 设置加锁时限
- 死锁检测
八、sleep和yield
- sleep:
- 暂停当前线程,给其他线程机会
- 进入阻塞状态,阻塞结束后,进入就绪状态
- yield:
- 给优先级相同或更高的线程机会
- 当前线程直接进入就绪状态
九、守护线程
- 定义:一个服务线程,服务其他的线程,类型于线程回收线程
十、线程状态
- new:新建状态
- runnable:就绪状态
- running:运行状态
- blocked:堵塞状态
- waiting:等待
- timed_waiting:线程等待一段时间
- terminated:进场结束状态
十一、多线程锁升级
十二、Double-check-Locking 单例模式
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }