并发编程之线程
一、创建线程的方式
1.继承Thread类,重写该类的run()方法。类
2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。接口 静态代理
3.使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。有返回值和异常。JUC包
4.通过线程池来创建线程
5.java线程中start()和run()的区别:start()可以启动线程,而run()只是一个普通方法。
二、线程的生命周期及五种基本状态
2.1 线程生命周期:新建(new)、就绪(start/yield等)、运行(CPU run)、阻塞(sleep/join等)、死亡(stop/加flag)。
2.2 Thread类和Object的方法
sleep暂停/yield礼让属于类方法;join插队属于对象方法。
2.3 守护线程
用户线程和守护线程(Daemon)。jvm的退出不用等待守护线程执行完成。例:垃圾回收线程。应用场景:jvm退出时,线程能够自动退出。
设置守护线程:t.setDaemon(true) 验证守护线程:t.isDaemon.
2.4 Hook(钩子)线程
jvm退出的时候,钩子线程就会被启动执行。
Hook(钩子)可以执行一些资源释放的操作,比如关闭数据库连接,Socket 连接等。
Runtime.getRuntime().addShutdownHook(new CleanWorkThread());
2.5 常用方法
isAlive()、setName()、getName()、currentThread()类方法。
三、高并发
3.1 并发:同一个对象多个线程同时操作。抢占资源
3.2 线程同步(线程安全):队列与锁 Synchronized
锁机制:锁住对象或Class对象。
3.3 Synchronized方法和Synchronized块 线程安全
synchronized 方法:public synchronized void test(){}。
synchronized 块:synchronized (obj){}。 obj:同步监视器。粒度更小
性能优化:临界值、double checking。
3.4 并发容器
concurrent包:CopyOnWriteArrayList
3.5 死锁
3.5.1 产生死锁的条件
1)互斥使用:即当资源被一个线程使用(占有)时,别的线程不能使用。
2)不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3)请求和保持:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4)循环等待:即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样 就形成了一个等待环路。
3.5.2 解决死锁
1)资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
2)只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
3)可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
4)资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
3.5.3 死锁的根本原因
1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;
2)默认的锁申请操作是阻塞的。
多个线程都拥有锁,且都需要等待对方释放资源。
3.6 并发协作 线程协作
3.6.1生产者消费者模式
1) 线程通信
3.6.2协作模型之管程法
1)生产者 消费者 缓冲区
2)Object类 wait() 释放锁,notify()/notifyall() 成对出现
3.6.3协作模型之信号灯法
1)生产者 消费者 标志位
3.6 高级主题-定时调度
java.util.Timer
java.util.TimerTask
3.7 任务定时调度框架-quartz
Scheduler:调度器,控制所有的调度
Trigger:触发条件,采用DSL模式
JobDetail:需要处理的任务
Job:执行逻辑
3.8 重排序:Happen-Before
重排序:编译器和CPU会尝试重排指令使得代码更快地运行。目的:提高性能
数据依赖:编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
3.9 volatile修饰
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,
当有其他线程需要读取时,它会去内存中读取新值(JMM通过将工作内存的设为失效,以让其直接去读内存)。
volatile变量的内存可见性是基于内存屏障实现的。
1) java内存模型中的可见性、原子性和有序性。
可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。Java中volatile、synchronized和final实现可见性。
原子性:synchronized、lock、unlock保证原子性。
有序性:volatile、synchronized保证线程之间操作的有序性。
2) volatile:保证线程间变量的可见性。不能保证原子性。
3) 原理:内存屏障。通过内存屏障可以禁止特定类型处理器的重排序。
4) volatile规则:如果一个线程对volatile变量读,另一个线程对该变量写,那么写操作一定发生在读操作之前。
3.10 dcl单例模式
volatile:保证线程间变量的可见性。不能保证原子性。
1)构造器私有化-->避免外部new构造器
2)提供私有的静态属性-->存储对象的地址
3)提供公共的静态方法-->获取属性
3.11 ThreadLocal 线程的本地存储区域
get/set/initialValue初始化方法。
private static修饰
InheritableThreadLocal:继承上下文环境的数据,拷贝一份给子线程。
3.12 可重入锁实现原理
锁可以延续使用+计数器。
可重入锁:公平锁和不公平锁。公平锁:等待时间长的先获得锁。
JUC包:ReentrantLock.
3.13 CAS原子操作(Compare and swap 比较并交换)。
悲观锁:synchronized关键字、lock接口。
乐观锁:CAS:属于硬件级的操作(利用CPU的CAS指令),是乐观锁的一种实现。效率高
JUC(java.util.concurrent)中AtomicXxx类:AtomicInteger.decrementAndGet()。
四、多线程小结
4.1 推导lambda简化线程 拉姆达
4.2 在并发编程中,比较常用的是使用synchronized关键字和Lock接口同步,或者volatile关键字,来确保多线程下的有序性。