java线程池 -- 大话笔记
来源:互联网 发布:循序渐进学java 编辑:程序博客网 时间:2024/06/05 05:24
- java线程池的概念
- java线程池知识点扫盲
- java线程池的模式
- java线程池源码解析
java线程池概念
我们知道,java最大的特性在于其对多线程以及并发性的友好支持,但线程的创建是和我们的cpu有关系的,持续不断的创建势必会带来更多的开销,当我们有多个请求并发而来,且访问的时间很短,这就势必会造成我们cpu的压力,而java线程池就是为了解决这个问题而产生的,利用已有的线程来处理多个任务,以减少cpu创建线程时所带来的开销
java线程池知识点扫盲
在学习java线程池首先必须掌握几个要点
- 线程的创建以及其生命周期
- 锁机制
对于线程的创建以及其生命周期想必肯定是知道了,而在线程池的源代码中出现了一个类需要我们先了解一下ReentrantLock,由于对多线程用的还是比较少,所以就去了解了一下,读到源代码的时候,由于ReentrantLock主要依赖于abs来实现,然后读着读着发现aqs中出现了重入锁,一脸蒙蔽,什么是重入锁呢,用下面一个例子简单说明一下。
public class ReentrantTest { public void method1() { synchronized (ReentrantTest.class) { System.out.println("方法1获得ReentrantTest的内置锁运行了"); method2(); } } public void method2() { synchronized (ReentrantTest.class) { System.out.println("方法1里面调用的方法2重入内置锁,也正常运行了"); } } public static void main(String[] args) { new ReentrantTest().method1(); }}
由以上例子可得,只有同一线程的才有资格访问method2,而在abs中,通常用的是lock来做,举例
public class RepeatLock1 { boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.currentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } }}
由于ReentrantLock比较高级,他可以控制线程是否可以获取锁而不是像synchronized一样,一直处于等待状态,同时为了节省线程的开销,aqs提供了同一线程是否可以获得锁,如果已获取,那就+1,这在我们后面读源码的时候会讲到
接下来我们就来讲解一下ReentrantLock这个类
由上图我们可知,在ReentrantLock中的有两个内部类,公平锁和非公平锁,两个子类都是继承abs的,现在我们来跟一下非公平锁的实现方式吧。
final void lock() { acquire(1); }
当我们要获取锁的时候会调用这个lock方法,lock方法里面有个acquire(1)方法,那么这个方法有什么用呢,我们不妨在看一下源代码。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
这里我们不妨可以做个比喻,假如现在有一个线程已经占用了锁对象,那么第二个线程请求过来的时候就要通过tryAcquire()方法去判断是否可以获得锁,这里显然是获取不到,那么我们就会执行第二方法,将请求放到请求队列中去,我们先来看看addwaiter这个方法是如何放的。
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
这里会将当前的线程放到节点中去,然后再判断tail是否不为空,这里很显然的是tail为null,所以我们会执行enq这个方法,如下
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
由代码我们可求得,首先会把一个空节点加入到头部,然后再将tail=head,又由于他是循环的,那么接下来就会执行到else这一步,将node的前置节点指向head,通过compareAndSetTail(t, node)将node节点放到CLH尾部,然后再将刚刚header的下一个节点指向next,如图
然后返回节点,接下来我们在来看看会执行到的代码
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
由上我们不难知道首先我们会获取第一个节点是不是和clh中的第一个节点是否一致,这里很显然是一致的,不过由于我们的锁还没有释放,所以会执行第二步
shouldParkAfterFailedAcquire(p, node)会将header的waitstatus由0转变为-1,然后返回true,下一步parkAndCheckInterrupt()通过这个方法将线程暂时挂起来,防止死锁。
现在假设我们的后面又多了一个线程来请求,这时的队列会是什么样呢?
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
这时会执行第一个if,图如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
接下来又会执行如上代码,判断waitStatus是否>0,大于0表示已经过期,需要剔除掉,再判断节点waitStatus是不是等于0,如果等于0就把他变为-1,然后返回false,重新进入循环到下一步,与之前一样,当然了,当锁释放后,线程2会得到锁,这时后header节点会被剔除,第二个节点会变成header节点,感兴趣的可以自行去看源码。
ExecutorService
下面我们就来看看线程池ExecutorService这个类,下面有一副图表明他在api中的位置
暂时写到这,我继续去扫盲
- java线程池 -- 大话笔记
- java线程池笔记
- java线程池笔记
- Java线程和线程池学习笔记
- Java线程池总结笔记
- Java线程池学习笔记
- Java线程池ThreadPoolExecutor笔记
- Java学习笔记46:Java 线程池
- 大话进程与线程
- 大话进程与线程
- java 多线程学习笔记4-线程组 线程池 适配器
- 关于java 线程/线程池 的处理以及运用 笔记
- java学习笔记(线程池)
- java JDK 1.5 线程池学习笔记
- 学习笔记四:java线程池
- Java线程池学习笔记一
- Java线程池学习笔记二
- Java学习笔记(74)------------线程池
- Mysql语句练习
- CentOS安装流程
- C/C++面试题(一)
- 数据结构 C语言 队列 迷宫问题
- 文件I/O(不带缓存)++标准I/O(带缓存)
- java线程池 -- 大话笔记
- 从Adobe调查问卷看原型设计工具大战
- android QMI机制---modem消息发送
- ZOJ3876 May Day Holiday【日期计算】
- win10 物联网编程资料
- unity3D学习笔记之七 RectTransform与屏幕适配
- 剑指offer | 训练题49:数组中重复的数字
- 数学建模--整数规划
- Linux系统编程——进程间通信:消息队列