一 线程基础
1、synchronized取得的锁都是对象锁,哪个线程执行synchronized修饰的方法,哪个线程就获得这个方法所属对象的锁。不同对象不同锁,互不影响。
另一种情况是static静态方法加synchronized表示类级别的锁,锁定.class类。如:
public static synchronized void printNum(String tag)
2、脏读
ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库,必需要具有这四种
特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。
Oracle中undo:相当于日志,数据库在执行DML操作时会把旧值放到undo里,这样可以实现回滚。
3、synchronized细节
3.1 synchronized锁重入:当一个线程得到一个对象的锁后,再次请求此对象时可以再次得到该对象的锁。若出现异常,锁自动释放。此时要及时处理!
3.2 涉及父子继承,synchronized修饰父的方法和子的方法实现加锁,也没问题。
3.3 synchronized代码块可以对任意的Object加锁(不要对字符串常量加锁,会出现死循环,可以new一个String对象)。
当对象本身发生了改变,这个锁就变了。如:String loc = “a”; loc = “b”; //这就变了
但如果对象本身不变,对象的属性值改变,那锁还是不变。如:
Final Student s = new Student(); s的名字年龄值改变,但还是多个Thread中同一个s调用方法,那还是同步。
4、Volatile
Volatile关键字只能让成员变量在多个线程之间可见。但它不能保证变量的原子性,要实现原子性建议使用atomic类的系列对象(注意atomic类只保证本身方法原子性,不保证多次操作的原子性):
private static AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 相当于++。
volatile算轻量级synchronized,性能强于synchronized,不会造成阻塞。
5、wait、notify
使用wait、notify实现线程间通信。这两个方法都是Object类的方法,也就是说java为所有的对象都提供了这两个方法。
它俩必须配合synchronized使用。
wait方法释放锁,notify方法不释放锁。
6、ThreadLocal
线程局部变量。它完全不提供锁,而使用以空间换时间的方式,为每个线程提供变量的独立副本,以保障线程安全。高并发时可以用它。
7、多线程的单例模式
需要用dubble check Instance或static inner class。
8、同步类容器:线程安全的,如Vector、HashTable;
经典错误ConcurrentModificationException:比如当容器迭代过程中,被并发的修改了内容。
9、并发类容器:jdk5以后出现,如ConcurrentHashMap代替HashTable,CopyOnWriterArryList代替Vector等等,
详见
10、并发Queue
Jdk提供两套并发队列实现,这两套都继承Queue:
10.1 以ConcurrentLinkedQueue为代表:高性能队列,适用于高并发场景下的队列,无锁。
10.2 以BlockingQueue为代表:阻塞队列
a. ArrayBlockingQueue有界队列
b. LinkedBlockingQueue无界队列
c.SynchronousQueue数据极少,不需要放队列
这三种自上而下分别对应三种应用场景:数据量很大、不大、很少。
d. PriorityBlockingQueue基于优先级的阻塞队列,重写comparable排序,无界队列。
e.DelayQueue到延迟时间,才能从队列获取该元素。
二 线程设计模式
-
future模式,类似ajax。比如用多个线程执行不同模块,以空间换时间,从而减少程序执行时间。
举个例子main函数请求一方法发参数过去,那方法会启动一个线程去查数据,并告诉请求方先干别的吧,main函数调一方法接收返回结果时,这方法会wait,直到查到数据才被notify,然后返回结果。
关键就是用了个wait、notify。
-
masterWorker模式,常用的并行计算模式。即系统由两类进程协作工作:master进程和worker进程。Master负责接受和分配任务,worker负责处理子任务。当各个Worker子进程处理完成后,会将结果返回给master,由master做归纳总结。其好处是能将一个大任务分解成若干小任务,并执行,从而提高系统的吞吐量。
-
生产-消费模式:生产者线程负责提交用户请求,消费者线程则负责处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。
三 JDK多任务执行框架
Executor框架:在java.util.Concurrent下,是jdk并发包核心。比较重要的类:Executors,扮演线程工厂。通过Executors,可以创建特定功能的线程池。
Executors创建线程池方法:
newFixedThreadPool(),固定线程数,池无空闲,任务就暂缓到任务队列。
newSingleThreadExecutor(),一个线程的线程池,池无空闲,任务就暂缓到任务队列。
newCachedThreadPool(),根据实际线程数随时调整池大小,无上限,无任务不创建线程,每个空闲线程60s后自动回收。
newScheduledThreadPool(),返回SchededExecutorService对象,可指定线程数量。
Executors还提供一个可以自定义线程的方法:public ThreadPoolExecutor();
此自定义方法的构造方法对于队列是什么类型的比较关键:
使用有界队列时,如果有新的任务需要执行,若线程池实际线程数小于corePoolSize,则优先创建线程,若大于corePoolSize,则会将任务加入队列,若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。
使用无界的任务队列时:LinkedBlockingQueue。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。
附:spring定时器大都用spring Shedule,而不是spring quartz了。
四 Concurrent.util工具类
1、CyclicBarrier:等所有线程都准备好了一起出发。
详见
2、CountDownLacth:监听初始化操作,初始化完毕,通知主线程继续工作。
3、Callable和Future使用:即Future模式,它非常适合处理很耗时、很长的业务逻辑,可减小系统响应时间,提高系统的吞吐量。
4、Semaphore信号量——java层面的限流。适合对系统高并发访问量进行评估:
4.1 PV page view :页面浏览量,用户每刷新一次就会被记录一次。
4.2 UV unique visitor:24小时内相同ip客户端只记录一次。
4.3 QPS query per second:每秒查询数,可根据压力测试得到估值。
4.4 RT response time:请求响应时间。
五 (重入锁、读写锁使用)锁的高级深化
-
重入锁:在需要进行同步的代码加上锁定。
-
读写锁:reentrantReadWriteLock,核心是实现读写分离的锁,在读多写少的高并发访问下,性能高于重入锁。
六 并发框架Disruptor
Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作。
Disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式的实现,或者事件监听模式的实现。
下载disruptor-3.3.2.jar引入我们的项目既可以开始disruptor之旅。
在使用之前,首先说明disruptor主要功能加以说明,你可以理解为他是一种高效的"生产者-消费者"模型。也就性能远远高于传统的BlockingQueue容器。
在Disruptor中,我们想实现hello world 需要如下几步骤:
第一:建立一个Event类
第二:建立一个工厂Event类,用于创建Event类实例对象
第三:需要有一个监听事件类,用于处理数据(Event类)
第四:我们需要进行测试代码编写。实例化Disruptor实例,配置一系列参数。然后我们对Disruptor实例绑定监听事件类,接受并处理数据。
第五:在Disruptor中,真正存储数据的核心叫做RingBuffer,我们通过Disruptor实例拿到它,然后把数据生产出来,把数据加入到RingBuffer的实例对象中即可。