并发编程知识点总结

三个概念:

原子性: 一个操作不可中断,要么成功,要么失败;
可见性: 一个线程修改共享变量后其他线程能够立即获取这个修改;
有序性: 同一时刻只有一个线程可以获取锁,其他线程只能等待获取锁;

synchronized 原子性,有序性,可见性;
volatile 有序性,可见性

禁止指令重排的方式:1.volatil;2.lock;3.final

单例模式:

饿汉式: 加载的直接创建对象,避免多线程同步问题,缺点是浪费内存;
懒汉式: 判断不为空,创建对象,解决了内存浪费问题,但多线程可能产生多个实例;
双重校验锁(DCL): 判空 加锁 判空 创建对象;

缺点:cpu指令重拍导致调用构造函数前先分配了内存指向了引用,其他线程获取的就是未执行构造器的对象;
解决方式:增加volatile关键字,禁止指令重拍;

静态内部类
枚举

JAVA内存模型,JMM,JAVA MEMORY MODE

1.java内存模型将内存分为堆和栈;栈线程私有,堆是线程共享;
2.线程会在硬件的CPU核心上运行,CPU内部会寄存器和高速缓存(1,2,3级),单个CPU运算通过高速缓存与计算机主内存通过缓存一致性协议交互(为提高CPU运算单元充分利用,在保证执行结果一致的前提下发生指令重排);
3.与硬件架构类似,java每个线程通过JMM控制各自的本地内存与计算机主内存中的共享变量交互;

Happens-before:

前一个操作结果可以被后续的操作获取;

为什么需要?

线程运行程序过程都是在本地内存中完成,完成后以copy方式写入主内存,这就存在可见性问题,为了避免编译优化指令重排影响并发安全(如:DCL单例指令重排问题),通过happens-before原则保证并发安全;

规则

程序顺序执行 同一线程前面的写操作对后面的读操作可见;
volatile规则 对一个volatile变量的写操作结果,对后续这个变量的读操作可见;
线程启动规则 运行中的线程对共享变量修改结果对新开启的线程可见;
线程终止规则 线程终止前对共享变量的修改对其他线程可见;
线程中断规则 调用interrupt()方法设置线程可中断标记对后续获取可中断标记可见;
传递性 A happens-before B ,B hapepends-before C 则 A happens-before C;
管程锁定规则 同一个锁的unlock操作前对共享变量的修改对其他lock的线程可见;

java实现同步的方式:

1.synchronized关键字;
2.synchronized代码块;
3.volatile保证共享变量可见性,一般结合cas实现同步;
4.Lock,比如ReentrantLock,ReentrantReaderWriterLock实现同步;
5.通过ThreadLocal实现同步;

JUC包

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

共享锁与独占锁?

独占锁 也叫排他锁,只有一个线程可以获取锁,获取锁的线程可以读写数据;
共享锁 可以被多个线程同时拥有,某个线程设置了共享锁,其他线程不可以再设置独占锁,获取共享锁的线程只能读数据,不可写;

公平锁与非公平锁?

公平锁 按照CLH队列顺序依次获取锁,先去排队,每次获取锁都会检查当前节点是否为头结点;
非公平锁 先尝试获取锁,尝试失败,才会去排队,每次获取锁不会校验是否为头结点;

获取锁流程;

1.内部维护一个Sync类继承AQS(AbstractQueuedSynchronizer),根据构造方法不同创建公平锁与非公平锁,独占锁或者共享锁;
2.调用lock()方法;

公平锁(acquire())

1.获取state值;
state = 0 查看当前是否为等待队列第一个位置;
等待队列为空或者处于第一个位置 --> cas将state设置值;
有其他线程等待分配锁 --> 加入等待队列,等待唤醒执行3;
state != 0 加入CLH等待队列;
2.cas state
成功 --> 获取到锁,将占用线程设置为当前线程;
失败 --> 获取锁失败,加入CLH等待队列;
3.占有锁线程释放锁,唤醒等待队列的头结点所在线程,被唤醒线程重复1;

>>非公平锁**(cas --> acquire())

1.获取state者;

== 0 (不管等待队列是否有等待的线程)直接cas设置值;
= 0 加入CLH等待队列

2.cas state

成功 获取到锁,设置线程占用;
失败 进入3

3.获取锁失败

自旋加入等待CLH队列(addWaiter),队列首节点自旋获取锁(acquireQueued()),其他等待队列节点阻塞,等待成为首节点;
addwaiter方法:
1.如果head tail为空,创建哨兵节点,head,tail指向哨兵节点;
2.将当前线程封装的节点加入CLH队列尾部;
将tail指向的节点(当前尾节点)的next指针指向新加入节点;
将新加入节点的pred指向tail节点;
改变tail指针指向当前节点,完成双向联表的插入;

锁流程共享锁与独占锁几个区别点

获取锁的时候,独占锁会判断state是否为0后直cas设置state;共享锁判断是否存在独占锁,不存在则通过cas增加state;
释放锁的时候,独占锁只会unpark头结点,共享锁会唤醒所有waitStatus为-1的节点;

J.U.C Condition类;

由AQS实现,ConditionObject.class
condition需要绑定一个锁使用,通过Lock接口的的newCondition方法获取,获取的方法在AQS里实现;
condition的await和single方法必须先获取到锁;

常用的并发工具类

ReentrantLock
可重入锁,可以根据构造不同创建公平锁和非公平锁,默认非公平锁;

ReentrantReadWriteLock
可重入读写锁,可以创建公平锁和非公平锁,分为读锁和写锁,读锁是共享锁,写锁是独占锁;

CountDownLatch
同步计数器,构造函数设置需要等待的线程数,每完成一个,count--,count为0继续,否则等待,用于协调多个线程同步;
countDown,线程调用,减少计数器(state值),await,线程调用,可用于main线程,等待计数器为0;
不可重置

CyclicBarrier
屏障,栅栏,主要解决线程组内部线程之间的相互等待问题,一组知道最后一个线程达到屏障(线程内部调用await方法),所有阻塞的线程才可以继续执行,不会阻塞主线程,;
可通过reset方法重置

Semaphore
信号量,可以控制某个资源访问的线程数量,初始构造state个线程;
底层通过AQS共享锁原理实现;
调用acquire方法,如果state不为0,则可以获取锁,将state--;
调用release方法,将state++;

Exchange
交换器,exchange相当于一个交换场所,内部维护slot槽(Node,包换index,hash,待交换对象等)节点,通过exchange()方法设置交换点;
首先执行到exchange()方法阻塞等待其他交换线程;

线程池 Executor

创建线程的两种方式
new Thread()
实现Runnable接口

为什么使用线程池
创建和销毁线程花销较大,有时候比业务时间还长,频繁创建和销毁线程,增加系统负担和系统耗时;

线程池的作用

提高效率 减少系统维护线程的开销;
解耦 运行和创建分开;
复用 线程可以复用;
方便管理 方便管理线程,比如设置最大访问线程数等;

jdk的给的线程池创建方式
ThreadPoolExcutor extends AbstractExecutorService implement ExecutorService extends Executor
通过Executors类静态方法通过不同参数调用ThreadPoolExcutor的构造方法;

ThreadPoolExeutor参数
corePoolSize: 核心线程个数,创建线程池的时候创建,与pool生命周期相同;
maximumPoolSize 最大线程个数,核心线程加后创建的线程最大个数;
keepAliveTimeunit 非核心线程空闲销毁等待时间,unit为时间单位;
workQueue 任务等待获取线程的阻塞队列;

newSingleThreadPool

创建核心线程为1,最大线程数为1,等待队列为LinkedBlockingQueue的线程池;

newFixedThreadPool

创建一个指定线程个数的线程池,core和max一样,等待队列为LinkedBlockingQueue;

newCacheThreadPool

创建一个可变大小的线程池,等待队列为LinkedBlockingQueue;

newScheduledThreadPool
创建一个指定核心线程数的可变线程池,等待队列为DelayedWorkQueue();

几个问题
固定线程数,可能会导致等待队列创建大量任务耗费内存,甚至oom;
最大线程数Integer.MAX_VALUE,可能创建大量线程导致oom;

线程池执行任务流程
提交任务 -> 判断线程池线程个数是否达到最大核心线程数 -> 未达到,创建核心线程执行任务 ->达到核心线程数,尝试加入等待队列 ->加入等待队列失败,判断总线程数量是否达到最大线程数,未达到创建线程,达到抛异常;

线程池的执行状态
RUNNING 线程池接受任务并对任务进行处理,线程创建后处于RUNNING状态;
SHOTDOWN RUNNINT状态调用shotdown()方法转化为SHOTDOWN状态,线程池不接受新任务,但会处理已添加的任务;
STOP RUNNING/SHOTDOWN状态调用shotdownNow()转化为STOP状态,线程池不接受任务,不处理已添加任务,中断正在执行的任务;
TIDYING 处于STOP/SHOTDOWN状态的线程池中执行任务数为0转化为TIDYING状态,同时调用terminnated()方法;
TERMINATED 线程池执行完terminated方法后处于中止状态;

因此需要使用线程池的时候需要根据系统具体业务,通过ThreadPoolExecutor构造定制化的线程池;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352