汇智动力-Java并发编程
一、并发的级别
1、阻塞
一个线城市阻塞的,要么在其他线程释放资源之前,当前线程无法执行,例如使用synchronized或者重入锁;
2、无饥饿
如果线程是有优先级的,那么线程调度的时候总会倾向于满足高优先级的线程,从而造成低优先级的线程饥饿,而无饥饿则能够保证饥饿不会产生,常见的手段是公平锁,保证先来后到;
3、无障碍
无障碍的并发不会因为临界区导致线程挂起,每个线程都可以直接进入进入临界区,如果检测到脏数据则对自己所做的修改立即回滚,确保数据安全;
4、无锁
无锁的并行都是无障碍的,在无锁的调用中,一个典型的特点是包含一个无穷循环,在这个循环中,线程会不断的尝试修改共享变量,如果没有冲突修改成功,否则,会不断的尝试继续修改,无锁能保证必然有一个线程能够在有限步内完成操作离开临界区;
5、无等待
无锁只要求有一个线程可以在有限步内完成操作,二无等待要求所有的线程都必须在有限步内完成;这样就不会引起饥饿问题;
一个典型的无等待结构就是RCU(Read-Copy-Update)。它的基本思想是对数据的读不加控制,所有的读都是无等待的,但在写数据的时候,先取得原数据的副本,接着只修改副本数据,在合适的时间内再返回;
二、jmm的一些特性
1、原子性
一个操作不可中断,一个线程一旦开始执行一个操作就不会被其他线程干扰;
例如在32位机器上对int的读写,但是long的读写就不是原子性的,如果两个线程同时对一个long数据进行读写的话就有可能会造成脏数据;
2、可见性
一个线程修改了一个共享变量的值,其他线程能否立即知道这个修改;
3、有序性
多线程的执行可能会导致程序的乱序执行,而乱序的原因则是因为为了保证指令流水线不被中断从而对指令进行重排;
有前后依赖性的指令指令不能被重排:
volatile变量;
解锁必然要发生在加锁前
传递性:A先于B,B先于C,则A先于C;
三、Java并行基础
1、线程状态图及基本操作
2、新建线程
start()而不是run()
3、终止线程
stop:stop比较暴力,会直接终止线程,并且释放这个线程所持有的锁,可能会导致数据不一致性;
正确的方法应该是在线程体里面增加一个标志位,标志位代表是否终止该线程,线程执行的时候回检查标志位的状态,如果为true,则退出执行;
4、线程中断
关于线程中断有3个方法:
public void Thread.interrupt() //中断线程,设置中断标志位
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除中断状态
在具体应用的时候需要像stop线程一样,在线程执行方法里面检查该线程是否被中断,如果被中断就退出;
5、线程等待和通知
线程等待和通知通过调用obj.wait() 和 obj.notify()方法执行,当调用wait方法时,该线程就会在这个对象上等待,直到其他线程调用该对象notify方法,obj就是多个线程之间的通信手段;
wait方法必须在同步快中调用,notify方法会在该对象的阻塞队列中随机选择一个线程运行;
wait方法和sleep方法一样 都可以让线程等待一段时间,除了wait可以被唤醒,sleep需要被打断,另外一个主要的区别就是wait方法会释放目标对象的锁,而sleep方法则不会;
6、suspend和resume
suspend和resume方法已被废弃,因为suspend在挂起线程的同时并不会释放任何锁资源,从而导致其他线程也无法执行,直到调用了resume方法,被挂起的程序才能被访问;
7、join和yield
join表示无限等待,他会一直阻塞当前线程,直到目标线程执行完毕;
yield会让出cpu,然后就想争夺cpu;
10、volatile与jmm
volatile关键字保证共享数据的可见性,线程会在释放锁之前把数据写回主存,保证其对其他线程可见;但是volatile只是保证可见性,并不能保证互斥性,而synchronized既可以保证可见性,又能保证互斥性;
11、守护线程
系统的守护者,在后台默默执行一些系统服务,如垃圾回收线程、jit线程;与之相对的是用户线程,用户线程是系统的工作线程,如果用户线程全部结束,那这个程序也就无事可做了,应用程序就会退出;
12、线程优先级
在Java中,使用1到10表示线程的优先级,数字越大,优先级越高;
13、synchronized关键字用法
制指定加锁对象
直接作用于实例方法,进入同步代码的时候会获得当前实例的锁;
直接作用于静态方法:相对于当前类加锁,进入同步代码的时候会获得当前类的锁;
14、Java集合的线程安全性
线程安全的Java集合:vector,stack,enumeration,hashtable,还有并发包里面的一些类:CopyOnWriteArrayList, ConcurrentHashMap,CopyOnWriteArraySet。
四、Java并发包
1、synchronized的功能扩展:重入锁(ReentrantLock)
重入锁性能要比synchronized好,控制力度比较小,而且使用起来也更灵活;
重入锁线程在等待的时候可以被中断,而synchronized要么获得锁继续执行,要么保持等待;
重入锁线程在等待的时候可以限时等待;
重入锁提供了公平锁和非公平锁两种模式;
2、Condition条件
wait和notify方法和synchronized关键字合作使用,而condition是和重入锁合作使用通过lock.newCondition()方法可以生成一个与重入锁绑定的Condition对象;
Condition提供了一些基本方法:
await:是当期线程等待,并释放当前锁,当其他线程使用signal或者signalAll时,线程会重新获得锁并继续执行;
awaitUninterruptibly与await基本相同,但它不会再等待过程中响应中断;
signal唤醒一个线程
signalAll方法唤醒所有等待线程;
3、Semaphore信号量:允许多个线程同时访问
acquire申请许可,若无法获得,则线程等待直到有一个线城释放许可或当前线程被中断;
acquireUninterruptibly和acquire类似,不过不响应中断;
release释放许可
4、ReadWriteLock读写锁
读写分离锁,如果使用synchronized或者可重入锁,线程的所有读之间,读与写之间,写与写之间都是串行操作,但读与度之不会造成数据的破坏,这也就是读写锁所做的事情;
5、倒计时器CountDownLatch
6、循环 栅栏CyclicBarrier
7、线程池
Executor提供了各种各样的线程池:
newFixedThreadPool:固定数量线程的线程池,当任务超出线程数量时,暂存在阻塞队列中;
newSingleExecutor:返回一个只有一个线程的线程池,多于一个任务的话,保存在阻塞队列中;
newCachedThreadPool:返回一个可根据实际情况调整线程数量的线程池,但是可以设置线程池的最大线程数量,线程存活时间等;
newSingleThreadScheduleExecutor:返回一个ScheduledExecutorService对象,线程池大小为1,有定时执行任务的功能;
newScheduleThreadPool:返回一个ScheduledExecutorService对象,但线程池的线程数量可以指定;
当线程池超过负载的时候(线程池用完,等待队列用完啊),jdk有几种内置的策略:
AbortPolicy:直接抛出异常,阻止系统正常工作
CallerRunsPolicy:只要线程池未关闭,直接在线程池中运行该任务
DiscardOledestPolicy:丢弃一个最老的请求
DiscardPolicy:直接丢弃,不与任何处理;
8、Fork/Join框架:分而治之
类似于分治思想,Fork阶段用来把任务分割成多个子任务,Join阶段用来收集结果
五、锁的优化及注意事项
1、提高锁性能的几点建议
减小锁持有时间、减小锁粒度、读写分离锁替换独占锁、锁分离(不同的操作不同的锁)、锁粗化(锁的粒度太小也不好,会造成线程反复的申请锁)
2、jdk提供的锁策略
锁偏向、轻量级锁、自旋锁、锁消除
3、人手一支笔:ThreadLocal
ThreadLocal把共享变量编程每个线程的内部变量,只有当前线程访问;
4、无锁CAS
CAS的算法过程是这样的:它包含3个参数CAS(V,E,N),V表示要更新的变量,E表示预期的值,N表示新值。仅当V等于E时,才把V更新为N,如果不等,表示有其他的线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值;
但是CAS也有可能出现问题,即如果一个线程改了值,但是又被另一个线程改了回去,则当前线程会认为其值没有变化过进而对其进行更新,在有的应用可能会出现问题,可以在增加一个时间戳参数来保证。