前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >java多线程实现原理

java多线程实现原理

作者头像
逝兮诚
发布2019-10-30 18:04:18
发布2019-10-30 18:04:18
86100
代码可运行
举报
文章被收录于专栏:代码人生代码人生
运行总次数:0
代码可运行

java内存模型

java的内存模式 线程 - 工作内存 - 主存。线程会读写工作内存,CPU会周期性的将工作数据刷入主存,如果多个线程写工作内存,就会导致每个线程的工作内存、主存内存数据都不一致,最终导致执行结果无法预期。

代码语言:javascript
代码运行次数:0
复制
线程1 —|工作内存|—>  [      ]
                   [ 主存 ]
线程2 —|工作内存|—>  [      ]

happens-before规则

由于编译器,CPU,内存会出现指令重排序。其中一个数据的读写指令的重排序,会对执行结果产生变化,对于这种存在数据依赖的指令是不允许重排序的。happens-before是java对程序员的一种保证,它的内容是:

  1. 对于单个线程,之前的操作一定happens-before之后的操作
  2. 对于synchronized关键字,加锁的命令一定在解锁执行
  3. 对于volatile对象,写一定在读之前执行
  4. happens-before具有延续性,即a happens-before b,b happens-before c,那么a happens-before

happens-before并不是要求处理器一定要顺序一致性的执行指令,而是保证重排序的执行对最终执行结果没有影响。

synchronized

synchronized是锁,悲观锁。它的原理是,它锁住的对象会放入到monitor中,monitor只允许一个线程进入,synchronized括住的代码就是要进入monitor获得对象的代码。

代码语言:javascript
代码运行次数:0
复制
synchronized(lock) {
  a(lock);
  b(lock);
}

wait/notify模式

所有的Object都有wait/notify方法,wait可以让当前线程进入等待状态,notify可以让等待线程恢复运行状态。wait/notify必须在synchronized语句块中运行,而且必须是锁着的那个对象执行wait/notify方法。

使用wait/notify可以实现线程间的通信。

wait/notify经典范式

代码语言:javascript
代码运行次数:0
复制
synchronized(lock) {
  while(条件不满足)
  	lock.wait();
  逻辑运行
}

synchronized(lock) {
	改变条件
  lock.notify();
}

volatile语义

volatile保证了对象在多线程的可见性,即volatile对象多线程看到的,都是一致的。它在于java模型中的语意是,volatile对象写入工作内存后,会立刻刷入主存;而volatile的读,会废弃工作内存内的,直接读主存的数据。volatile简单的读写是原子性的。

AQS

AQS(AbstractQueueSynchronized)是JUC的的核心实现类。它采用模版方法模式,实现获取独占锁获取资源,释放资源,共享锁获取资源,共享锁释放资源四个模版方法,实现类需要实现其获得资源和释放资源的自己实现。

AQS有一个同步队列,用于存放等待的线程节点,AQS有队列的head和tail对象。

获取资源的流程,获取资源,获取失败新建等待节点,放入同步队列,不断自旋判断前驱是first并是否能获得资源,将当前节点设为头节点,则返回true,不能返回false;释放独占锁资源是尝试释放资源,成功唤醒下一个同步队列的线程。共享锁获取资源和释放资源与独占锁相似,只是独占锁获取资源方法返回true/false,而共享锁返回int,当放回值为0时,共享锁释放成功。

AQS的逻辑实现

代码语言:javascript
代码运行次数:0
复制
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
代码语言:javascript
代码运行次数:0
复制
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
代码语言:javascript
代码运行次数:0
复制
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
代码语言:javascript
代码运行次数:0
复制
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

JUC中的锁

JUC中的锁是指Lock接口,lock和unlock是加锁,解锁的方法。Lock接口的实现类有重入锁,读写锁。

重入锁是一个独占锁,它通过两个继承AQS的同步器来实现加锁,解锁操作。重入锁有两个同步器,一个是非公平同步器,一个是公平同步器,默认采用非公平同步器。公平同步器继承AQS类,它实现tryAcquire和tryRelease方法,tryAcquire的实现逻辑是当state为0时,当前线程是同步队列头节点对应线程且cas修改state为1,成功将holdThread改为当前线程;当state不为0,判断holdThread是否是当前线程,如果是state=state+1,如果不是返回false。非公平锁和公平锁逻辑相似,只是没有判断是否是头节点这个判断。释放资源相似,都是state=state-1。非公平锁造成一个线程释放资源之后又获取资源,减少线程切换次数。

读写锁有两个锁对象,一个读锁,一个写锁,它们公用一个同步器,其中写锁用的是其独占锁的资源获取和释放方法,读锁用的是其共享锁的资源获取和释放方法。独占锁和共享锁通过将32位的state的前16位用于记录读锁资源,后16位用于记录写锁资源。

condition的实现

condition是AQS的等待队列,它通过await新建线程节点放入等待队列,并让线程阻塞;signal唤醒等待队列中的节点线程。它相当于实现了wait/notify的方法。

一个condition对象就是一个新的等待队列,它记录了头,尾节点,它和同步队列公用一个Node类。

阻塞队列

BlockQueue接口有linkedBlockQueue,ArrayBlockQueue等实现类,它们有通过锁的加,解锁实现。

还是有ConcurrentLinkedQueue,它是通过循环+CAS+volatile头尾节点实现,它对volatile头尾节点的写有个优化,不是每次都写,而是隔一次再写,如入队,第一次只更新next到新节点,第二次才更新tail节点;出对第一次将head的item赋null,第二次才更新头节点。

同步工具

同步工具有Fork/Join框架,等待多线程完成框架CountDownLatch,线程屏障框架CyclicBarrier,两个线程交换数据的Exchanger框架。

Fork/Join框架是一个自动分解任务,放入任务队列,同时线程池执行任务的一个框架。使用需要继承fork/join的任务类,并在实现方法中实现任务分割的规则,分割的任务调用fork方法时,将任务放入任务队列并安排工作线程执行,调用join时,阻塞线程到结果返回。ForkJoinPool就是一个继承类的实现类。

CountDownLatch框架实现的是原先的join功能,它的作用是规定多少个线程执行,主线程调用await阻塞,等规定数量的线程执行后,主线程唤醒。它通过同步器实现,await就是获得共享锁,当state==0时,就可以获得锁。

CycliBarrier是线程屏障,当设定数量的线程都达到线程屏障时,所有线程能才能执行。它使用重入锁和condidion实现,当调用await时,获得独占锁,state–,当state!=0时,放入等待队列;==0时,signalAll恢复所有等待线程。

线程池Executor

executor通过execute执行任务。它主要的实现类是TheadPoolExecutor。通常使用ExecutorService创建线程池,它提供了三个模版创建,fixTheadPool,singleTheadPool,cacheTheadPool。

实现是一个线程set,一个阻塞任务队列。执行execute时,判断当前线程数是否小于coreSize,如果小于新建核心工作线程,把任务给它执行;当大于coreSize时,将任务放入阻塞队列;当阻塞队列放不下时,判断线程数是否小于maxSize,如果小于,新建非核心工作线程,把任务给它执行;如果线程数大于maxSize,执行饱和策略。

工作线程Work的run方法逻辑是,循环取任务,执行任务.run。如果task==null,跳出循环。获取任务的方法会死循环获得任务队列任务,只有在任务队列为空,且线程数大于核心线程数,且超时的情况下,返回null,结束运行,可以设置允许核心线程timeout。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/08/13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java内存模型
  • happens-before规则
  • synchronized
  • wait/notify模式
  • volatile语义
  • AQS
  • JUC中的锁
  • condition的实现
  • 阻塞队列
  • 同步工具
  • 线程池Executor
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档