推荐书籍:1.java编程思想 2.企业应用架构模式 3.java并发编程实战
并发编程基础篇
课程目录:
1.线程安全基础知识、synchronized、volatile关键字的实际场景使用
2.线程之间通信wait、notify、ThreadLocal、单列和多线程
一、线程安全
线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的
synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥去”或“临界区”
二、对象锁的同步和异步
同步:A线程先持有object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
异步:A线程先持有object对象的Lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法
三、脏读
对于对象的同步和异步的方法,我们在设计自己的程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读
总结:
在我们对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue/getValue方法同时加锁synchronized同步关键字,保证业务(service)的原子性,不然会出现业务错误
四、synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。
五、volatile关键字的概念
volatile概念:volatile关键字的主要作用是使变量在多个线程间可见
总结:在java中,每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中的变量值的拷贝。当线程执行时,他在自己的工作内存中操作这些变量。为了存取一个共享的变量,一个线程通常先获取锁并去清除他的内存工作区,把这些共享变量从所有线程的共享内存区中正确的装入到他自己所在的工作内存区中,当线程解锁时保证该工作内存区中变量的值写回到共享内存区中。
一个线程可以执行的操作有:使用(use)、赋值(assign)、装在(load)、存储(store)、锁定(lock)、解锁(unlock)
而主内存中可以执行的操作有:读(read)、写(write)、锁定(lock)、解锁(unlock),每个操作都是原子的
volatile的作用就是强制线程到主内存(共享内存)里去读取变量,而不去线程工作内存区读取,从而实现了多个线程间的变量可见,也就是满足线性安全的可见性
volatile关键字只具有可见性,没有原子性。要实现原子性建议使用atomic类的系列类,支持原子性操作(注意:atomic类只保证本身方法原子性,并不保证多次操作的原子性)(atomic的解释在连接:
https://blog.csdn.net/weixin_34652797/article/details/78034251)。解释如下:
package com.pochi.juc;
public class AtomicProblem {
public static void main(String[] args) {
AtomicExample atomicExample = new AtomicExample();
for(int i=0;i<10;i++){
new Thread(atomicExample).start();
}
}
}
class AtomicExample implements Runnable{
private int value;
@Override
public void run() {
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getValue());
}
public int getValue() {
return value++;
}
}
运行上面的代码可以发现,可能会返回两个一样的值,如果在 value 前加 volatile 是否能解决问题呢?加上去
后发现还是不能,还是会产生两个一样的值。只能用 atomic 原子操作类能解决。它实际上是用了 cas 原理。
CAS:(Compare and swap)比较交换策略;
每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少
六、线程之间的通信
使用wait/notify方法实现线程间的通信(注意:这两个方法时Object的类的方法,换句话说java为所有的对象都提供了这两个方法)
1.wait和notify必须配合synchronized关键字使用
2.wait方法释放锁,notify方法不释放锁
七、ThreadLocal
ThreadLocal概念:线程局部变量,是一种多线程间并发访问变量的解决方案。与其synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。
从性能上说,ThreadLocal不具有绝对的优势,在并发不是很高的时候,加锁的性能会更好,但作为一套与锁完全无关的线程安全解决方案,在高并发量或者竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争
package com.lptxyl.base;
public class ConnThreadLocal {
public static ThreadLocal<String> th = new ThreadLocal<String>();
public void setTh(String value){
th.set(value);
}
public String getTh(){
return Thread.currentThread().getName() + ": " + th.get();
}
public static void main(String[] args) {
final ConnThreadLocal ct = new ConnThreadLocal();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ct.setTh("z3");
System.out.println(ct.getTh());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(ct.getTh());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
输出的结果:
并发编程中级篇
课程目录:
1.同步类容器,并发类容器介绍和概念
2.ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList和CopyOnWriteArraySet底层实现讲解
3.实战队列(Queue)和双端队列(Deque)讲解(ConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue、ProrityBlockingQueue、DelayQueue、SynchronousQueue、LinkedBlockingDeque)
4.多线程的设计模式。Future模式、Master-Worker模式、生产者-消费者模式;在实际工作中如何应用及其场景描述
一、ConcurrentMap
ConcurrentMap接口下有两个重要实现:
ConcurrentHashMap
ConcurrentSkipListMap(支持并发排序功能,弥补ConcurrentHashMap)
ConcurrentHashMap内部使用段(Segment)来标识这些不同的部分,每个段其实就是一个小的HashTable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。
二、Copy-On-Write容器
Copy-On-Write简称COW,是一种用于程序设计中的优化策略
JDK里的COW容器有两种:CopyOnWriteArrayList和CopyOnWriteArraySet。
什么是CopyOnWrite容器?
CopyOnWrite容器即写时复制的容器。通俗的理解就是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
三、并发Queue
在并发队列上 JDK 提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种队列都继承自Queue
1、ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的 高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一个基于链接节点的无界线程安全队列。 该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。
ConcurrentLinkedQueue重要方法:
add()和offer()都是加入元素的方法(ConcurrentLinkedQueue中,这两个方法没有任何区别)
poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。
2、BlockingQueue
具体实现如下:
BlockingQueue的核心方法:
放入数据:
offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,
则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中
加入BlockingQueue,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻
断直到BlockingQueue里面有空间再继续.
获取数据:
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,
取不到时返回null;
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,
队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到
BlockingQueue有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),
通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
四、Future模式
五、Master-Worker模式
六、生产者-消费者模式
并发编程高级篇
课程目录:
1.JDK多任务执行框架
2.Concurrent.util工具类详细讲解和使用
3.(重入锁、读写锁使用)锁的高级深化
一、Executor框架
二、自定义线程池
三、自定义线程池使用详细
并发编程框架篇
课程目录
1.disruptor框架介绍和Hello World
2.disruptor详细说明与使用
3.disruptor应用(并发场景实例讲解)
公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,
否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式