首页 文章 精选 留言 我的

精选列表

搜索[源码学习],共10000篇文章
优秀的个人博客,低调大师

MapReudce源码分析之Mapper

Mapper是MapReduce编程模型中一个将输入的key/value对映射成一组中间key/value对的组件。Map是将输入记录转换成中间记录的单个任务。被转换的中间记录不需要与输入记录一样的类型。一个给定的输入对可能被映射成0个货多个输出对。Hadoop的MapReduce框架为作业中输入格式InputFormat产生的每个输入分片InputSplit产生一个Map任务。通过JobContext的getConfiguration()方法,Mapper的实现者可以获得任务的配置信息。MapReduce框架中Map部分首先会调用setup()方法,然后接着为输入分片的每个KeyValue对调用map()方法进行处理,最见后再调用cleanup()方法。所有给定输出key相关的中间值随后会被框架进行分组,继而被传递给Reducer以确定最终的输出。通过指定两个关键的RawComparator类,用户可以控制排序和分组。Mapper输出被每个Reducer分区。通过实现一个定值分区器Partitioner,用户可以控制哪些key和相关记录进入哪个Reducer。 Mapper的执行主流程在其run()方法内,代码如下: /** * Expert users can override this method for more complete control over the * execution of the Mapper. * 熟练或者老练的用户可以覆写该方法,以便更完整的控制Mapper的运行 * @param context * @throws IOException */ public void run(Context context) throws IOException, InterruptedException { // task开始运行时调用setup()方法进行初始化 setup(context); try { // 当context中仍有KeyValye对的话,一直循环 while (context.nextKeyValue()) { // 取出context中当前key、valye,连同context本身,调用map()方法处理 map(context.getCurrentKey(), context.getCurrentValue(), context); } } finally { // task结束运行时调用cleanup()方法进行清理 cleanup(context); } } run()方法执行的流程很简单,大体如下: 1、task开始运行时setup()初始化方法; 2、在try模块中,当context中仍有KeyValye对的话,一直循环: 取出context中当前key、valye,连同context本身,调用map()方法处理; 3、在finally模块中,task结束运行时调用cleanup()方法进行清理 。 是不是很简单,就像一个模板一样,按照setup()--map()--map()--......--map()--cleanup()的执行主线运行。而且,熟练或者老练的用户可以覆写该方法,以便更完整的控制Mapper的运行。 我们接下来再看下进行初始化的setup()方法和进行清理的cleanup()方法,代码如下: /** * Called once at the beginning of the task. * task开始运行时调用一次,做初始化工作 */ protected void setup(Context context ) throws IOException, InterruptedException { // NOTHING } /** * Called once at the end of the task. * task结束运行时调用一次,做清理工作 */ protected void cleanup(Context context ) throws IOException, InterruptedException { // NOTHING } 这两个函数分别在task开始运行或结束运行时调用一次,一遍完成初始化或清理工作,用户可覆写这两个方法,以便实现自己的初始化或清理逻辑,或者,干脆不用管,那么这两个方法是空方法,什么都不会做。 再来看下实现KeyValue对转换的核心功能map()方法,代码如下: /** * Called once for each key/value pair in the input split. Most applications * should override this, but the default is the identity function. * 针对输入分片split的每个key/value对都会调用一次。大多数应用程序应该覆写该方法,而默认实现是一个类似恒等式的功能,原样输出key、value */ @SuppressWarnings("unchecked") protected void map(KEYIN key, VALUEIN value, Context context) throws IOException, InterruptedException { context.write((KEYOUT) key, (VALUEOUT) value); } map()方法针对输入分片split的每个key/value对都会调用一次。大多数应用程序应该覆写该方法,而默认实现是一个类似恒等式的功能,原样输出key、value。 另外,Mapper中还有一个抽象内部类Context,它实现了MapContext接口,代表了Map任务运行时的上下文信息,我们后续再讲。

优秀的个人博客,低调大师

HBase源码分析之KeyValue

HBase内部,单元格Cell的实现为KeyValue,它是HBase某行数据的某个单元格在内存中的组织形式,由Key Length、Value Length、Key、Value四大部分组成。其中,Key又由Row Length、Row、Column Family Length、Column Family、Column Qualifier、Time Stamp、Key Type七部分组成。在HBase1.0.2版本中,它的结构如图: 从左到右,依次为: 1、Key Length:存储Key的长度,占4B; 2、Value Length:存储Value的长度,占4B; 3、Key:由Row Length、Row、Column Family Length、Column Family 3.1、Row Length:存储Row的长度,即rowkey的长度,占2B; 3.2、Row:存储Row实际内容,即Rowkey,其大小为Row Length; 3.3、Column Family Length:存储列簇Column Family的长度,占1B; 3.4、Column Family:存储Column Family实际内容,大小为Column Family Length; 3.5、Column Qualifier:存储Column Qualifier对应的数据,既然key中其他所有字段的大小都知道了,整个key的大小也知道了,那么这个Column Qualifier大小也是明确的了,无需再存储其length; 3.6、Time Stamp:存储时间戳Time Stamp,占8B; 3.7、Key Type:存储Key类型Key Type,占1B,Type分为Put、Delete、DeleteColumn、DeleteFamilyVersion、DeleteFamily等类型,标记这个KeyValue的类型; 4、Value:存储单元格Cell对应的实际的值Value。 下面,我们看下HBase中KeyValue是如何实现的。在KeyValue中,有三个十分重要的变量,如下: // KeyValue core instance fields. // KeyValyeh核心实例存储域 // KeyValue相关的不变byte[]数组,存储KeyValue实际内容 protected byte [] bytes = null; // an immutable byte array that contains the KV // KeyValue在数组bytes的起始位置 protected int offset = 0; // offset into bytes buffer KV starts at // KeyValue在数组bytes自起始位置offset后的长度 protected int length = 0; // length of the KV starting from offset. KeyValue内容是存储在byte[]数组bytes中的,它是一个不变的byte[]数组,而存储的起始位置与长度,则分别由offset和length标识。 下面,我们看下KeyValue中获取Key Length、Value Length、Row Length、Column Family、Value等等相关字段的方法,来验证下我们上面罗列出的KeyValue结构。 1、Key Length /** * @return Length of key portion. */ public int getKeyLength() { // 从KeyValue底层byte[]数组bytes中位置offset开始,获取一个int,也就是4B return Bytes.toInt(this.bytes, this.offset); } getKeyLength()方法用于获取KeyValue中Key长度Key Length,它从KeyValue底层byte[]数组bytes中位置offset开始,获取一个int,也就是4B,这也就验证了我们上面说的,KeyValue中第一个是Key Length,大小为4B。 2、Value Length /** * @return Value length */ @Override public int getValueLength() { // 从KeyValue底层byte[]数组bytes中offset+4开始,获取一个int,也就是4B // 也就是说,key length后紧跟着4B是value length int vlength = Bytes.toInt(this.bytes, this.offset + Bytes.SIZEOF_INT); return vlength; } getValueLength()方法用于获取KeyValue中Value长度Value Length,它从KeyValue底层byte[]数组bytes中offset+4开始,获取一个int,也就是4B,这也就验证了我们上面说的Key Length后紧跟着4B是Value Length。 3、Key起始位置 /** * @return Key offset in backing buffer.. */ public int getKeyOffset() { // ROW_OFFSET为key length、value length之后的位置 return this.offset + ROW_OFFSET; } ROW_OFFSET为Key Length、Value Length之后的位置,定义如下: // How far into the key the row starts at. First thing to read is the short // that says how long the row is. public static final int ROW_OFFSET = Bytes.SIZEOF_INT /*keylength*/ + Bytes.SIZEOF_INT /*valuelength*/; getKeyOffset()方法用于获取KeyValue中Key的起始位置,它的取值为整个KeyValue的起始位置offset加上ROW_OFFSET,而ROW_OFFSET为Key Length和Value Length所占大小,这也就验证了Key Length和Value Length之后就是Key。 4、Value起始位置 /** * @return the value offset */ @Override public int getValueOffset() { // Key的起始位置,再加上Key的长度,就是Value的起始位置 int voffset = getKeyOffset() + getKeyLength(); return voffset; } getValueOffset()方法用于获取KeyValue中Value的起始位置,它的值为通过getKeyOffset()方法获取的Key的起始位置,再加上通过getKeyLength()方法获取的Key的长度,这也就验证了KeyValue中继Key Length、Value Length、Key之后,就是Value。 5、Row Length /** * @return Row length */ @Override public short getRowLength() { // 从KeyValue底层byte[]数组bytes中key起始位置开始,获取一个short,也就是2B // getKeyOffset()起始时获取的key length加value length后的位置 // 也就是说,key length后紧跟着4B是value length,而value length后就是key的开始, // 而key前面的2B是row length return Bytes.toShort(this.bytes, getKeyOffset()); } getRowLength()方法用于获取KeyValue中Row长度Row Length,它从KeyValue底层byte[]数组bytes中key起始位置开始,获取一个short,也就是2B,这也就证明了Row Length是Key中第一个字段。 5、Row起始位置 /** * @return Row offset */ @Override public int getRowOffset() { // key的起始位置再加2B,即row length之后就是row return getKeyOffset() + Bytes.SIZEOF_SHORT; } getRowOffset()方法用于获取KeyValue中Row的起始位置,它的取值为Key的起始位置再加2B,即Row Length之后就是Row,与上面所讲一致! 6、Row /** * Primarily for use client-side. Returns the row of this KeyValue in a new * byte array.<p> * * If server-side, use {@link #getBuffer()} with appropriate offsets and * lengths instead. * @return Row in a new byte array. */ @Deprecated // use CellUtil.getRowArray() public byte [] getRow() { return CellUtil.cloneRow(this); } getRow()方法用于获取Row内容,它通过CellUtil的cloneRow()方法,传入本身KeyValue实例,返回一个byte[],而cloneRow()方法如下: public static byte[] cloneRow(Cell cell){ // output为一个大小为row length的byte[]数组 byte[] output = new byte[cell.getRowLength()]; // 将row从cell中copy至output copyRowTo(cell, output, 0); return output; } 可以看到,先构造一个Row Length大小的byte[]数组output,这也就意味着Row的大小是由之前的Row Length对应的值确定的。然后,调用copyRowTo()方法,将KeyValue中Row存储的内容拷贝至output数组并返回。而copyRowTo()方法,将cell(也就是KeyValue)中byte[]数组bytes,从Row的起始位置Row Offset处开始,拷贝到目标byte[]数组destination(也就是output),从0开始,拷贝数据的长度为Row Length,也就是会填满整个destination(output),代码如下: public static int copyRowTo(Cell cell, byte[] destination, int destinationOffset) { // 将cell中byte[]数组bytes,从row offset处开始,拷贝到目标byte[]数组destination,从0开始,拷贝数据的长度为row length, // 也就是会填满整个destination System.arraycopy(cell.getRowArray(), cell.getRowOffset(), destination, destinationOffset, cell.getRowLength()); // 返回数据拷贝的终止点 return destinationOffset + cell.getRowLength(); } 7、Family起始位置 /** * @return Family offset */ @Override public int getFamilyOffset() { return getFamilyOffset(getRowLength()); } /** * @return Family offset */ private int getFamilyOffset(int rlength) { // 获取family的起始位置:整个KeyValue起始位置offset + ROW_OFFSET(Key Length + Value Length) + 2B(Row Length) + 实际Row大小rlength + 1B(Family Length) return this.offset + ROW_OFFSET + Bytes.SIZEOF_SHORT + rlength + Bytes.SIZEOF_BYTE; } getFamilyOffset()方法用于获取KeyValue中Family的起始位置,它是整个KeyValue起始位置offset,加上ROW_OFFSET,也就是Key Length、Value Length所占大小,然后再加上Row Length所占大小2B,和通过getRowLength()方法获取的实际Row大小rlength,最后加上1B,即Family Length所占大小。这也就说明了,Key中ROw Length、Row之后就是Family Length和Family,而Family Length大小占1B。 8、Family Length /** * @return Family length */ @Override public byte getFamilyLength() { return getFamilyLength(getFamilyOffset()); } /** * @return Family length */ public byte getFamilyLength(int foffset) { // family起始位置减1,这个1B就是family length return this.bytes[foffset-1]; } getFamilyLength()方法用于获取KeyValue中Family长度Family Length,它是通过由getFamilyOffset()方法获取的Family位置减1来获取的,与上面得到的验证一致,Family前面1B就是Family Length。 9、Qualifier起始位置 /** * @return Qualifier offset */ @Override public int getQualifierOffset() { return getQualifierOffset(getFamilyOffset()); } /** * @return Qualifier offset */ private int getQualifierOffset(int foffset) { // Family起始位置加上Family长度Family Length return foffset + getFamilyLength(foffset); } getQualifierOffset()方法用于获取KeyValue中Qualifier的起始位置,它实际上是通过Family的起始位置再加上Family的长度Family Length,这也就说明了Family后就是Qualifier。 10、Qualifier长度 /** * @return Qualifier length */ @Override public int getQualifierLength() { return getQualifierLength(getRowLength(),getFamilyLength()); } /** * @return Qualifier length */ private int getQualifierLength(int rlength, int flength) { // Key长度减去Row长度、Family长度、Row Length长度、Family Length长度、Time Stamp长度、Key Type长度 return getKeyLength() - (int) getKeyDataStructureSize(rlength, flength, 0); } getQualifierLength()方法,用于获取KeyValue中Qualifier长度,KeyValue中并没有直接存储Qualifier长度,而是通过Key的总长度减去Key中除Qualifier外其它各部分长度来得到的,实际上是一个计算的过程,为Key长度减去Row长度、Family长度、Row Length长度、Family Length长度、Time Stamp长度、Key Type长度的和。 11、Timestamp起始位置 /** * @return Timestamp offset */ public int getTimestampOffset() { return getTimestampOffset(getKeyLength()); } /** * @param keylength Pass if you have it to save on a int creation. * @return Timestamp offset */ private int getTimestampOffset(final int keylength) { // Key的起始位置加上Key的长度,再减去Time Stamp和Key Type所占大小 return getKeyOffset() + keylength - TIMESTAMP_TYPE_SIZE; } getTimestampOffset()方法用于获取KeyValue中Time Stamp的起始位置,它是通过Key的起始位置加上Key的长度,再减去Time Stamp和Key Type所占大小来计算得到的。这意味着,在Key中,Time Stamp处于倒数第二个位置,也就是在Qualifier之后,在Key Type之前,而Key Type则居于最后。 12、获取TimeStamp /** * * @return Timestamp */ @Override public long getTimestamp() { return getTimestamp(getKeyLength()); } /** * @param keylength Pass if you have it to save on a int creation. * @return Timestamp */ long getTimestamp(final int keylength) { // 获取TimeStamp起始位置tsOffset int tsOffset = getTimestampOffset(keylength); // 从bytes中tsOffset位置开始读取一个Long,即8B return Bytes.toLong(this.bytes, tsOffset); } getTimestamp()方法是用来获取KeyValue中TimeStamp的,它先获取TimeStamp起始位置tsOffset,然后从bytes中tsOffset位置开始读取一个Long,即8B,这与上面提到的TimeStamp占8B是一致的。 13、获取Key Type /** * @return Type of this KeyValue. */ @Deprecated public byte getType() { return getTypeByte(); } /** * @return KeyValue.TYPE byte representation */ @Override public byte getTypeByte() { // 整个KeyValue的位置offset + Key长度 - 1 + Key Length所占长度和Value Length所占长度和 // 即Key Type位于整个Key的最后一个1B return this.bytes[this.offset + getKeyLength() - 1 + ROW_OFFSET]; } getType()和getTypeByte()方法用来获取KeyValue中Key Type值,它是通过在bytes中,从整个KeyValue的位置offset + Key长度 - 1 + Key Length所占长度和Value Length所占长度和位置处获取的一个Byte来得到的,即Key Type位于整个Key的最后一个1B,这与上面所述也是一致的。 14、获取Value值 /** * Returns value in a new byte array. * Primarily for use client-side. If server-side, use * {@link #getBuffer()} with appropriate offsets and lengths instead to * save on allocations. * @return Value in a new byte array. */ @Deprecated // use CellUtil.getValueArray() public byte [] getValue() { return CellUtil.cloneValue(this); } getValue()方法是用来从KeyValue中获取Value值得,它是整个Cell实际存储的内容,通过CellUtil的cloneValue()方法,传入KeyValue自身实力来获得。我们来看下这个cloneValue()方法: public static byte[] cloneValue(Cell cell){ // 创建Value Length大小的byte[]数组output byte[] output = new byte[cell.getValueLength()]; // 将cell中的value值copy至output copyValueTo(cell, output, 0); return output; } cloneValue()方法首先创建Value Length大小的byte[]数组output,然后调用copyValue()方法,将cell中的value值copy至output。而copyValue()方法很简单,从bytes数组的Value Offset处开始拷贝Value Length大小,至destination,代码如下: public static int copyValueTo(Cell cell, byte[] destination, int destinationOffset) { // 从bytes数组的Value Offset处开始拷贝Value Length大小,至destination System.arraycopy(cell.getValueArray(), cell.getValueOffset(), destination, destinationOffset, cell.getValueLength()); return destinationOffset + cell.getValueLength(); }

优秀的个人博客,低调大师

Java源码分析专题系列之【ThreadPoolExecutor】深入浅出的源码分析

线程池执行任务的流程 如果线程池工作线程数<corePoolSize,创建新线程执行task,并不断轮训t等待队列处理task。 如果线程池工作线程数>=corePoolSize并且等待队列未满,将task插入等待队列。 如果线程池工作流程数>=corePoolSize并且等待队列已满,且工作线程数<maximumPoolSize,创建新线程执行task。 如果线程池工作流程数>=corePoolSize并且等待队列已满,且工作线程数=maximumPoolSize,执行拒绝策略。 execute()原理 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task.The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 如果运行的线程数小于corePoolSize,尝试创建一个新线程(Worker),并执行 * 它的第一个任务command。 * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 如果task成功插入等待队列,我们仍需要进行双重校验是否可以成功添加一个线程 * (因为有的线程可能在我们上次检查以后已经死掉了)或者在我们进入这个 * 方法后线程池已经关闭了。 * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. * 如果等待队列已满,我们尝试新创建一个线程。如果创建失败,我们知道线程已关闭 或者已饱和,因此我们拒绝改任务。 */ int c = ctl.get(); // 高3位表示状态,低29位任务数量。 //工作线程小于核心线程数,创建新的线程。 if (workerCountOf(c) < corePoolSize) { //创建新的worker立即执行command,并且轮训workQueue处理task。 (核心线程数) if (addWorker(command, true)) return; c = ctl.get(); } //线程池在运行状态且可以将task插入队列 //第一次校验线程池在运行状态 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //第二次校验,防止在第一次校验通过后线程池关闭。 如果线程池关闭,在队列中删除task并拒绝task if (! isRunning(recheck) && remove(command)) reject(command); //如果线程数=0(线程都死掉了,比如:corePoolSize=0),新建线程且未指 // 定firstTask,仅仅去轮训workQueue else if (workerCountOf(recheck) == 0) addWorker(null, false); } //线程队列已满,尝试创建新线程执行task,创建失败后拒绝task //创建失败原因:1.线程池关闭;2.线程数已经达到maxPoolSize else if (!addWorker(command, false)) reject(command); } addWorker()原理 线程管理部分的分水岭-方法调用之前 都是任务管理(任务的创建、以及拒绝、添加任务队列等,任务线程池的状态监控等),方法调用之后属于线程的管理。(线程的执行和阻塞等) 参数: firstTask:worker线程的初始任务,可以为空。 core:true:将corePoolSize作为上限,false:将maximumPoolSize作为上限 private boolean addWorker(Runnable firstTask, boolean core) { retry: //外层循环判断线程池的状态 for (;;) { //在进行获取一次上下文操作机制 int c = ctl.get(); int rs = runStateOf(c); //线程池状态 // Check if queue empty only if necessary. // 线程池状态:RUNNING = -1、SHUTDOWN = 0、 STOP = 1、TIDYING = 2、TERMINATED = 3 //线程池至少是shutdown状态 if (rs >= SHUTDOWN && // 除了线程池正在关闭(shutdown), // 队列里还有未处理的task的情况,其他都不能添加 ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; //内层循环判断是否到达容量上限,worker+1 for (;;) { int wc = workerCountOf(c);//worker数量 //worker大于Integer最大上限 //或到达边界上限 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //CAS worker+1 if (compareAndIncrementWorkerCount(c)) break retry;//成功了跳出循环 c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) //如果线程池状态发生变化,重试外层循环 continue retry; // else CAS failed due to workerCount change; // retry inner loop // CAS失败workerCount被其他线程改变, // 重新尝试内层循环CAS对workerCount+1 } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; w = new Worker(firstTask); //1.state置为-1,Worker继承了AbstractQueuedSynchronizer. //2.设置firstTask属性. //3.Worker实现了Runable接口,将this作为入参创建线程. final Thread t = w.thread; if (t != null) { //addWorker需要加锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); int rs = runStateOf(c); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w);//workers是HashSet<Worker> //设置最大线程池大小 int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (!workerStarted) addWorkerFailed(w); } return workerStarted; } 可以理解就是每一个Worker对象都是一个AQS队列哦! addWorker方法有4种传参的方式: addWorker(command, true) 线程数小于corePoolSize。判断workers(HashSet<Worker>)大小,如果worker数量>=corePoolSize返回false,否则创建worker添加到workers,并执行worker的run方法(执行firstTask并轮询tworkQueue); addWorker(command, false) 线程数大于corePoolSize且workQueue已满。如果worker数量>=maximumPoolSize返回false,否则创建worker添加到workers,并执行worker的run方法(执行firstTask并轮询tworkQueue); addWorker(null, false) 没有worker存活也就是任务梳理runcount为0,创建worker去轮询workQueue,长度限制maximumPoolSize。 addWorker(null, true) 在execute方法中就使用了前3种,结合这个核心方法进行以下分析 以上无论哪种方式都需要进行相关的ReentrantLock的加锁,所以效率和性能不会特别好。所以有了一个小办法,prestartAllCoreThreads() prestartAllCoreThreads()原理 这个方法调用,启动所有的核心线程去轮询workQueue。因为addWorker是需要上锁的,预启动核心线程可以提高执行效率。 ThreadPoolExecutor 内部类Worker (线程管理的核心类) /** * Class Worker mainly maintains interrupt control state for * threads running tasks, along with other minor bookkeeping. * This class opportunistically extends AbstractQueuedSynchronizer * to simplify acquiring and releasing a lock surrounding each * task execution. This protects against interrupts that are * intended to wake up a worker thread waiting for a task from * instead interrupting a task being run. We implement a simple * non-reentrant mutual exclusion lock rather than use * ReentrantLock because we do not want worker tasks to be able to * reacquire the lock when they invoke pool control methods like * setCorePoolSize. Additionally, to suppress interrupts until * the thread actually starts running tasks, we initialize lock * state to a negative value, and clear it upon start (in * runWorker). * 1.Worker类主要负责运行线程状态的控制。 * 2.Worker继承了AQS实现了简单的获取锁和释放所的操作。来避免中断等待执行任务的线 * 程时,中断正在运行中的线程(线程刚启动,还没开始执行任务)。 * 3.自己实现不可重入锁,是为了避免在实现线程池控状态控制的方法,例如: * setCorePoolSize的时候中断正在开始运行的线程。 * setCorePoolSize可能会调用interruptIdleWorkers(),该方法中会调用worker的tryLock()方法 * 中断线程,自己实现锁可以确保工作线程启动之前不会被中断 */ private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ 封装任务线程机制。 final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { // inhibit interrupts until runWorker //状态置为-1,如果中断线程需要CAS将state 从0- >1,以此来保证能只中断从workerQueue getTask的线程 setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } // 核心方法机制 /** Delegates main run loop to outer runWorker */ public void run() { //首先执行w.unlock,就是把state置为0,对该线程的中断就可以进行了 runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } // 在setCorePoolSize/shutdown等方法中断worker线程时需要调用该方法, // 确保中断的是从workerQueue getTask的线程 protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } //调用tryRelease修改state=0,LockSupport.unpark(thread) 下一个等待锁的线程 public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } } 阻塞队列 workQueue 有多种选择,在 JDK 中一共提供了 7 中阻塞对列,分别为: ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。 此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平地访问队列 ,所谓公平访问队列是指阻塞的线程,可按照阻塞的先后顺序访问队列。非公平性是对先等待的线程是不公平的,当队列可用时,阻塞的线程都可以竞争访问队列的资格。 LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。 此队列的默认和最大长度为Integer.MAX_VALUE。 此队列按照先进先出的原则对元素进行排序。 PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。 (虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败,导致 OutOfMemoryError) DelayQueue: 一个使用优先级队列实现的无界阻塞队列。 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素 SynchronousQueue: 一个不存储元素的阻塞队列。 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。(SynchronousQueue 该队列不保存元素) LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。 相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。 LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。 是一个由链表结构组成的双向阻塞队列 关闭线程池 其实,如果优雅的关闭线程池是一个令人头疼的问题,线程开启是简单的,但是想要停止却不是那么容易的。通常而言, 大部分程序员都是使用 jdk 提供的两个方法来关闭线程池,他们分别是:shutdown 或 shutdownNow; 通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程(PS:中断,仅仅是给线程打上一个标记,并不是代表这个线程停止了,如果线程不响应中断,那么这个标记将毫无作用),所以无法响应中断的任务可能永远无法终止。 但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。 只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回 true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法。 这里推荐使用稳妥的 shutdownNow 来关闭线程池,至于更优雅的方式我会在以后的并发编程设计模式中的两阶段终止模式中会再次详细介绍。 线程池的优点 在 Java 并发编程框架中的线程池是运用场景最多的技术,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来至少以下4个好处。 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗; 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行; 第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。 第四:提供更强大的功能,比如延时定时线程池; 线程池的场景特性 任务的性质:CPU密集型任务、IO密集型任务和混合型任务; 任务的优先级:高、中和低; 任务的执行时间:长、中和短; 任务的依赖性:是否依赖其他系统资源,如数据库连接; 性质不同的任务可以用不同规模的线程池分开处理。分为CPU密集型和IO密集型。 CPU密集型任务应配置尽可能小的线程,如配置 Ncpu+1个线程的线程池。 (可以通过Runtime.getRuntime().availableProcessors()来获取CPU物理核数),参考建议哈 IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。 混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。 如果这两个任务执行时间相差太大,则没必要进行分解。 可以通过 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数。 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue来处理。它可以让优先级高的任务先执行(注意:如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行) 执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。 依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。 建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点。方式因为提交的任务过多而导致 OOM;

优秀的个人博客,低调大师

学习JQuery

1. 重要基础 简写加载函数,事情必须在DOM加载之后,否则无法绑定对象 $(document).ready() $(function(){}); //推荐缩写 使用包括 JQuery 库外语法类似的 JavaScript 库时,注意命名冲突 Jquery.noConflict() var $$ = JQuery; //换成 $$ 调用Jquery函数 2. 基础选择器 (选择器使用css1,css2的全部选择器语法,以及部分css3语法。) 对 id 元素使用选择器: 默认对第一个元素外的其他元素"失明",返回第一个对象。 调用属性 .length 或函数 .size() 查看对象集合的数量 获取对象集合里的元素 .get(0) 或加下标 [0] 群组选择器: 逗号列举 $("label, #text, .blue") 后代选择器 $("#nav h4 span") $("#nav").find("h4").find("span") 子代选择器(只能是直接的父子关系) $("#nav > span") $("#nav").children("span") 获取 span 的下一个同级兄弟元素 p $("div span + p") $("div span").next() 获取 span 以下的所有同级兄弟元素 p $("div span ~ p") $("div span").nextAll("p") 同级向上(向下)选取元素,直到找到 label 元素为止(不包括 label 元素) .prevUntil("label") //向上 .nextUntil("label") //向下 寻找同级兄弟 .siblings("input") .prevAll() 加上 .nextAll() 3. 过滤选择器 第一个(最后一个)元素 :first :last 排除满足条件的元素 .not(.blue) 奇(偶)序元素 :odd :even 等于/大于/小于 :eq(2) :gt(3) :lt(5) 标题元素(h1 ~ h6) :header 正在执行动画的元素 :animated 正被聚焦的元素 :focus 包含指定文本的元素 :contains(".com") 不包含文本或者无子元素的元素 :empty 包含文本或子元素的元素 :parent 具有指定类的元素 :has(".blue") 不可见(可见)的元素 :hidden :visible 每个父元素的第一个(最后一个)子元素 :first-child :last-child 只有一个子元素的元素 :only-child 每个指定子元素的元素 :nth-child(odd) 匹配的元素 .is(".bule") .hasClass("blue") .filter(".blue") .slice(1, 4) //第二个到第四个元素 4. 表单选择器 选择指定元素 :input :button 选择不同类型的input元素 :text :password :radio :checkbox :submit :reset :image :button :file :hidden 选择不同属性的元素 :enabled :disabled :checked :selected 5. 基本 DOM 操作 获取(设置)元素html内容 .html() //加参数则设置 获取(设置)元素文本内容 .text() //加参数则设置 获取(设置)表单元素内容 .val() //加参数则设置 获取(设置)元素属性值 .attr() //加参数则设置 .attr({A: a, B: b}) //设置多个参数 获取(设置)css样式 .css() //加参数则设置,多参数设置同上 添加css样式 .addClass("blue") .addClass("blue red green") 移除css样式 .removeClass("blue") //移除多个类同上 切换css样式 .toggleClass("blue") //切换多个类同上 .toggleClass(function(){}, [rate]) //自定义切换规则,第二个为可选参数频率 获取(设置)高度(宽度) .height() //加参数则设置 .width() //加参数则设置 .height(function(object, value){}) //object指定元素、value指定元素的高度 获取内外边框高度宽度 .innerWidth() .innerHeight() .outerWidth() //包括边框和内边距 .outerHeight() //同上 .outerWidth(true) //同上并包括外边框 .outerHeight(true) //同上 相对偏移 .offset() //相对于视口 .position() //相对于父元素 滚动条相关(获取/设置) .scrollTop() //垂直 .scrollLeft() //水平 6. DOM节点操作 元素内部前面插入节点 .prepend(html) //插入节点html .prepend(function(new, parent)) //向parent元素内部前面插入节点new .prependTo(target) //将元素移至指定元素target 元素内部后面插入节点 .append(html) .append(function(new, parent){}) .appendTo(target) 元素外部前面插入节点 .before() .before(function(new, parent){}) .insertBefore(target) 元素外部后面插入节点 .after() .after(function(new, parent){}) .insertAfter(target) 包裹节点 .wrap(target) //参数可为html内容或者对象节点 .wrap(function(target){}) .unwrap() //解除一层包裹 批量包裹 .wrapAll(parent) //被元素parent包裹 .wrapInner(parent) //包裹元素parent的子元素 复制节点 .clone(true) //加上true表示同时复制绑定事件、不加则只复制元素和内容 删除节点 .remove(‘p’) //可选参数 保留事件行为的删除 .detach() 删除节点内容 .empty() 替换节点 .replaceWith('span') .replaceAll('p') 7. 基本事件 (以下函数均含匿名函数) 点击、双击 .click() .dbclick() 点击弹起瞬间、弹起后 .mouseup() .mousedown() 鼠标穿出入触发(包括子元素) .mouseenter() .mouseleave() 鼠标穿出入触发(不包括子元素) .mouseover() .mouseout() 鼠标移动 .mousemove() 键盘按下弹起瞬间、弹起后 .keyup() .keydown() .keypress() 焦点激活与丢失 .focus() .blur() .focusin() .focusout() 其他事件 .select() //文本选定 .change() //值被改变 .submit //表单提交 .scroll() //滚动条拖动 .unload() //卸载本页面 .hover(fun1, [fun2]) //鼠标移入(移出) 8. 事件对象 (function(event){}) 页面坐标(属性) pageX //相对于页面 pageY screenX //相对于显示屏 screenY clientX //相对于视口 clientY 9. 高级事件 模拟用户行为 .trigger("click", [param1, param2]) .trigger("click", [{paramA1 : a, paramA2 : b}, {paramB1, paramB2}]) 绑定(解绑)事件 .on("click dbclick", [function(){}]) .off("click") 仅触发一次事件 .one() //用法同.on 10. 动画 显示/隐藏 .show() //可选参数持续时间,如3000表示3秒,或者“slow” .hide() //添加第二参数闭包函数则动画完毕后执行 自定义动画 .animate({'top': '+=88px', 'height': '200px'}, 'slow') 队列动画 .queue([function([next]){ ......; [next()[]}]) .dequeue() //两个函数都是执行下一个函数 上下卷动 .slideUp() .slideDown() .slidToggle() 动画操作 .delay() .stop() 动画属性 .fx.interval //运行帧数 .fx.off = ture //关闭动画效果 11. AJAX .load() 第一个参数:目的 url 或者带选择器的 url (可带get的参数) 第二个参数:不为空则为post方式 第三个参数:闭包函数(返回数据,状态,XMLHttpRequest对象) .get() .post() .AJAX() //底层函数 (以下为传入参数的属性) url type timeout data dataType beforeSend complete success error global cache content contentType async processData dataFilter ifModified jsonp username password scriptCharset xhr traditional $.ajaxStart() $.ajaxStop() $.ajaxError() $.ajaxSuccess() $.ajaxComplete() $.ajaxSend() $.ajaxSetup() //请求默认值来初始化参数 $('form').serialize() //获取表单所有值 $('form').serializeArray() //返回键值对的Json对象 12. 工具函數 除去字符串两边空格 $.trim(str) 遍历数组(对象) $.each(arr, function(index, value){}) 数据筛选 $.grep(arr, function(element, index){}) 修改数据 $.map(arr, function(element, index){}) 合并数组 $.merge(arr1, arr2) 6.测试类型函数 $.isArray(obj) $.isFunction(obj) $.isEmptyObject(obj) $.isPlainObject(obj) //纯粹对象 $.isNumeric(data) $.isWindow(data) //window对象 $.contains(obj) //包含其他节点 判断类型函数 $.type(data) 将对象键值对转换为URL字符串键值对 $.param(obj) 调用内部函数 $.proxy(obj, 'objFunc') //返回obj对象里的objFunc函数 13. 插件 1.Validate.js (jquery.validata.js 、 jquery.validate.messages_zh.js) 相关代码 $('form').validate() 必填项 class = “required” 邮箱 class = "email" 不得少于两位 minlength = "2" 网址 class = "url" 2. 自动完成插件 (jquery.autocomplete.js 、 jquery-migrate-1.2.1.js) var name = ['John', 'Jack', 'Tom', 'Kitty']; $('form input[name=name]').autocomplete(name, {minChars: 0}) //0表示不需要输入一个字符即可显示所有备选数据 3. 自定义插件 分类 封装对象方法的插件 封装全局函数的插件 选择器插件 插件规范 名字: jquery.[name].js 局部对象附加 jquery.fn 对象上,全局函数附加在 jquery 上 插件内部,this 指向的是当前的局部对象 可以通过 this.each 来遍历所有元素 所有的方法或插件,必须用分号结尾,避免出现问题 插件应该返回的是 JQuery 对象,以保证可链式连缀 避免插件内部使用 $ ,如果要使用,请传递 JQuery 进去

优秀的个人博客,低调大师

JSP学习

JSP的静态包含和动态包含区别 静态包含语句:<%@ include file="路径" %> 动态包含语句:<jsp:include page="路径"></jsp:include> 区别:两者的执行结果都是相同的,但是执行过程不同,静态包含先包含,后编译处理,而动态包含先编译处理,后包含,如果去执行这2个jsp页面命令看看生成的文件就知道了,静态包含对应只生成一个class文件,动态包含则不止生成一个class文件,那么这样当在包含的时候如果主文件包含其他文件时变量名相同就会导致冲突覆盖变量问题,而动态包含则不会,相当于调用不同的jsp,变量的空间都不同,自然不会覆盖,所以一律推荐使用动态包含语句 JSP的九大内置对象和四大作用域 九大对象:pageContext,request,response,session,application,config,out,page,exception 四大作用域:page范围:只在一个页面中保存数据(eg:pageContext.setAttribute(key,value)和pageContext.getAttribute(key)方法来测试) request范围:只在一个请求中保存数据(对应都有setAttribute和getAttribute方法设置和获取数据) session范围:在一次会话范围中保存数据,默认会话30分钟,只要不关掉浏览器保持单个用户访问就行了。 application范围:在整个服务器上保存数据,所有用户共享 cookie和session的区别:cookie的信息是存放在客户端的,session的信息是存放在服务器端的 提交数据的get方式和post方式区别:① get方式提交的数据放在url后面,以?分割url和传输数据,参数之间以&相连,post方式提交的数据放在数据包中;② get方式提交数据对长度有限制,而post方式理论上对长度无限制; ③ get在浏览器回退是无害的,而post在浏览器回退会再次请求 ④ get的主要优点是效率高 Cookies功能:主要用于实现保存账号密码 cookies其实是一种服务器和浏览器交换数据的方式,cookies由服务器创建,发送给浏览器,浏览器把cookies保存在用户本地,当再次访问网站的时候,就把cookies发送给服务器。 cookies操作截图 Session,即会话,会话指的是从用户打开浏览器访问一个网站开始,无论在这个网站中访问了多少页面,点击了多少链接,都属于同一个会话。 直到该用户关闭浏览器为止,都属于同一个会话。采用键值对方式存储数据。比如登录一个网站,登录后,在短时间内,依然可以继续访问而不用重新登录。但是较长时间不登录,依然会要求重新登录,这是因为服务端的session在一段时间不使用后,就失效了。这个时间,在Tomcat默认配置下,是30分钟。可以通过 tomcat/conf/web.xml 中的session-config 配置进行保存时间调整 请求重定向(response.sendRedirect)和请求转发(request.getRequestDispatcher)只能适合jsp 截图 JSTL标准标签库 和 EL表达式 EL(Express Lanuage)表达式可以嵌入在jsp页面内部,减少jsp脚本的编写,EL出现的目的是要替代jsp页面中脚本的编写。 foreach循环语句 格式化日期参数截图 EL表达式 JavaEE的三层架构 web层:与客户端交互 service层:复杂业务处理 dao层:与数据库进行交互 Web是一种典型的分布式应用架构,web架构的精华包括三个方面:HTML(超文本标记语言)技术是实现信息文档的表示,URL(统一资源定位)技术实现全球信息的精确定位,HTTP(超文本传输协议)实现分布式的信息传输。web的基本工作原理是请求与响应原理,用户通过浏览器向服务器发出请求,服务器对请求进行处理响应,把响应结果发给客户端,由浏览器解析显示所请求的结果信息。 架构图 浏览器中的GET请求和POST请求区别讲解: GET请求 向服务器请求数据 参数带在请求路径后面,意味着参数会暴露着,不安全,一般不用来提交数据,只是用来获取数据, 参数长度有限 POST请求 向服务器提交数据 比如注册 post请求无长度限制 可以提交敏感数据,常用于提交表单 常见HTTP状态码: 200 请求成功,一切正常 404 请求页面找不到 403 没有权限

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册