Java多线程和并发总结
Java多线程和高并发总结
-
wait/notify必须存在于synchronized块中。
-
volatile
多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save) -
Thread类最佳实践: 写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。
-
如何获取线程中异常
Thread t = new Thread(task);
t.setUncaughtException(new UncaughtExceptionHandler(){...});
t.start();
不能用try,catch来获取线程中的异常 -
Future和FutureTask
Future是接口,FutureTask是类Future表示一个异步计算的结果. 异步计算是在其他线程进行的, 因此异步计算的结果, 有可能有值, 也有可能没有值. 于是, Future就提供了一些方法来处理这种未知状态:
a. isDone() 异步任务是否完成, 即否有结果
b. get() 获取异步任务结果, 如果异步任务未完成, 此方法会一直阻塞, 直到异步方法完成 或 任务被取消 (调用了cancel()方法)
c. cancel() 取消异步任务, 如果异步任务已经完成, 那么取消失败(即cancel()方法返回false)
d. isCancelled() 查询异步任务是否已被取消FutureTask就是一个可取消的异步任务,FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到
//1 FutureTask<Integer> future = new FutureTask<Object>(callable); new Thread(future).start(); //这里可以做其他事情 future.get();//阻塞获得结果 //2 public void function() { // 1. init services final ExecutorService executorService = ... // 2. make task FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { String result = ""; //do something; return result; } }); // 3. submit task executorService.submit(futureTask); // 4. get result String result = futureTask.get(); // 5. do something with result // ... }
-
ThreadLocal类
用处:保存线程的独立变量。对一个线程类(继承自Thread) 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。 主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。
-
cas和aba
CAS:对于内存中的某一个值V,提供一个旧值A和一个新值B。如果提供的旧值V和A相等就把B写入V。这个过程是原子性的。
CAS执行结果要么成功要么失败,对于失败的情形下一班采用不断重试。或者放弃。
ABA:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
如何解决ABA问题? 通常是使用版本戳来解决这个问题,避免并发中的问题。 CAS是一种乐观锁技术 -
重排序/happens before
JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。
happen-before原则解决了重排序带来的多线程运行问题。
如果两个操作之间具有happens-before 关系,那么前一个操作的结果就会对后面一个操作可见。happens-before原则规则:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
-
线程池
Java通过Executors提供四种线程池,分别为:
(缓存、固定、定时、单例)- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。(定时任务建议用这个,不要用TimerTask,因为:异常退出所有任务)
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。