java 并发知识总结[java编程思想四版]
Java中的锁[原理、锁优化、CAS、AQS]
https://www.jianshu.com/p/e674ee68fd3f
JUC工具类用户指南
https://www.jianshu.com/p/8cb5d816cb69
四种线程池的比较
https://www.cnblogs.com/aaron911/p/6213808.html
1、并发的多面性
并发编程使我们可以将程序划分为多个分离的,独立运行的任务
对于多核处理器的形式,并发可以极大地提高吞吐量,充分利用cup资源,让程序更快的执行
对于单处理机器上,如果任务不会被阻塞,那使用并发就没有任何意义
1.2、定义任务,执行线程
1、实现 Runnable接口的run()方法,并由Thread构造器接受Runnable对象,调用start()开启线程,或者由线程池开启。ExecutorService.execute(Runnable r)/ExecutorService.sumbit(Runnable r)
方法
2、实现 Callable接口的call()方法[具有类型参数的泛型],必须使用线程池的ExecutorService.submit(Callable c)方法调用它。
3、直接继承Thread实现run方法,并自己开启线程start()
线程池的submit方法返回一个Future对象,Future.get()[会阻塞]/Future.get(long, TimeUnit )[超时返回]获取线程的返回值(参数为Runnable接口实现的返回null),isDone()验证线程是结束,cancel(true)可结束线程
实现多线程简单的例子
package concurrent.callable;
import java.util.concurrent.*;
// 实现Callable接口,从任务中返回值
class TaskWithResult implements Callable<String> {
private String id;
public TaskWithResult(String id) {
this.id = id;
}
@Override
public String call() throws Exception {
System.out.println("running " + id);
return "result: " + id;
}
}
class TaskRun implements Runnable { // 实现Runnable接口
private String id;
public TaskRun(String id) {
this.id = id;
}
@Override
public void run() {
System.out.println("running: " + id);
}
}
class ThreadRun extends Thread { // 继承Thread
private String id;
public ThreadRun(String id) { this.id = id; }
@Override
public void run() {
System.out.println("running: " + id);
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
Future<?> result = exec.submit(new TaskWithResult("callable"));
System.out.println(result.get());
exec.execute(new TaskRun("runnable"));
new ThreadRun("thraed").start();
}
}
/*output:
running callable
result: callable
running: runnable
running: thraed
* */
1.2、线程基本方法
1、休眠: Thread.sleep(long) 使任务中止执行给定的时间。 可中断, 也可用TimeUnit.MILLISECONDS.sleep(200)
2、让步:Thread.yield(),暗示线程调度机制,你可以把cup资源让给其他线程使用,但没有任何机制保证它被采纳
3、优先级, Thread.getPriority()、Thread.setPriority(int),可在线程中任意地方设置线程的优先级,它会将线程的重要性传递给调度器,为了兼容性,请使用MAX_PRIORITY(10)、NORM_PRIORITY(5)、MIN_PRIORITY(1)三种级别
4、加入一个线程:Thread.join(),一个线程在其他线程上调用join(),则会等待一段时间,直到第二个线程结束,join()可接受一个时间值,超过此时间必返回。该方法可中断
5、后台线程:Thread.setDaemon(), 指程序运行在后台提供一种通用服务的线程(如垃圾回收程序),并且这种线程并不属于程序中不可或缺的部分,因此当所有非后台线程结束时,程序终止,会杀死所有后台线程。提示:后台线程创建的线程也是后台线程
1.3、使用Executor
Executor执行器可以管理我们的Thread对象(线程池),简化并发编程(尽量优选Executor创建线程),它提供了一些间接层接口,4中线程池:
1、ExecutorService exec = Executors.newCachedThreadPool(),将为每个任务创建一个线程,任务个数最多Integer.MAX_VALUE个,所以分配线程的开销会很大
2、ExecutorService exec = Executors.newFixedThreadPool(int num),一次性预先多少个线程个数,可减少线程分配开销,即使用Thread对象的数量是有界的,如果任务超出这个数量,超出的任务将进入同步队列排队
3、ExecutorService exec = Executors.newSingleThreadExecutor()相当于线程数量为1的FixedThreadPool(1)的方法
4、ExecutorService exec = Executors.newScheduleThreadPool(int num)创建一个定长的线程池,而且支持定时的以及周期性的任务执行
ExecutorService.shutdown()方法会阻止后提交的任务,ExecutorService.shutdownNow()结束所有任务(中断正在执行的任务)
定制Executor创建的线程的属性(是否后台、优先级、名称、捕获错误等),通过编写ThreadFactory实现
定制处理线程未捕获的错误例子
package concurrent.error;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
// 定制未捕获异常需要实现 Thread.UncaughtExceptionHandler 接口
class MyUncaughtExceptionHandle implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
class MangleFac implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// t.setDaemon(true); // 定制后台线程, 验证定制化异常需要注释掉该方法
t.setPriority(Thread.MAX_PRIORITY); // 定制线程优先级
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandle()); // 定制未捕获异常处理
System.out.println(t);
return t;
}
}
public class ExceptionThread implements Runnable {
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
ExecutorService ex = Executors.newCachedThreadPool(new MangleFac());
ex.execute(new ExceptionThread());
ex.shutdown();
}
}
/*output:
Thread[Thread-0,10,main]
caught java.lang.RuntimeException
*/
也可调用Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()),这样处理器在不存在专有捕获异常处理器的情况下就会调用defaultUncaughtExceptionHandler
2、共享资源
解决多线程共享资源竞争问题
1、使用JVM实现的synchronized的内置支持,加锁避免竞争,synchronized同步方法/synchronized(ongbjec) { ... } 同步控制块 对共享资源加锁。
2、使用显示的Lock对象Lock lock = new ReentrantLock(); lock.lock(); try { ... } finally { lock.unlock(); } 对共享资源加锁
3、避免使用共享资源,使用线程的本地存储java.lang.ThreadLocal对象,内部使用当前线程的地址作为key来保存当前线程的变量
原子性:指不能被线程调度机制中断的操作,一旦执行,便可在可能发生‘多线程上下文切换’之前完成。除了long和double(JVM将64位分成了两个32位操作造成了long和double非原子操作,可以加入关键字volatile,但仍然需要JVM保障)的其他基本类型的读写/赋值/返回都是原子操作。
可见性:多线程在多核处理器中出现的可视化问题,因为cup会把变量缓存到自己的处理器中(每个线程会有自己的工作空间保存共享变量的副本),使用volatile关键字可解决可见性问题,volatile会强制多线程的工作内存变量过期,去主内存中读取数据,但volatile并不保障原子性。
原子类:https://www.jianshu.com/p/84c75074fa03,通过volatile和CAS支持[CAS技术为cpu将原3个指令变为一个指令完成]
2.1、死锁
共享资源竞争下而产生相互等待的现象,照成程序无法正常执行。
4大条件:
互斥:存在某个资源只允许一个线程访问(不可共享的资源,该条件一般不可被破坏)
占有且等待:至少一个任务持有一个资源且正在等待获取一个当前被其他任务持有的资源(一次性获取所有资源)
资源不可抢占:不可抢占别人已经占有了某项资源。(加入超时)
循环等待:存在线程链,使每个线程都占有下一个线程是所需要的一个资源(破坏循环)
3、终结任务
任务(线程)4中状态
1、新建New:此刻线程已经有资格获取cpu时间片。
2、就绪Runnable:只要调度器把时间片分配给线程,线程就可运行。
3、阻塞Blocked:当线程处于阻塞状态时,调度器忽略线程,直到线程就绪才会分配时间片。 (时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,即该线程允许运行的时间,使各个程序从表面上看是同时进行)
4、死亡Dead:处于死亡或终止(中断),线程不会在被调度
阻塞:sleep(mill)、join()、wait()直到得到notify()/notifyAll()通知就绪(Condition对象的await()等待方法, signal()/signalAll()唤醒方法)、等待O、同步synchronized都会造成阻塞
中断:可通过异常中断、调研Thread.interrupt()、可通过Futrue<T>对象的cancel(true)方法、通过线程池的shutdownNow()方法,其中可通过Thread.interrupted()方法检测线程是否离开了run方法(注意,异常和Thread.interrupted()会使中断状态复位,防止中断被多次相应处理)
I/O操作和synchronized锁的操作是不能中断的
你可以中断所有抛出InterruptedException的异常方法,可以通过关闭IO的底层资源close()方法,任务将解除堵塞,NIO中可自己相应中断。synchronized不可中断可换成java se5提供的ReentrantLock的lockInterruptibly()方法
lock()与 lockInterruptibly()比较区别在于:lock 优先考虑获取锁,待获取锁成功后,才响应中断。lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。
3、同步队列
同步队列在任何时刻都只允许一个任务插入和移除。
https://www.jianshu.com/p/8cb5d816cb69