首页 文章 精选 留言 我的

精选列表

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

Beetl 3.0.16 版本发布,Java 模板引擎

本次发布做了微小改动,合并了一个最新版Struts2兼容的问题,感谢网友提供的PR struts2.5 兼容 增加一个CachedWriter来,可以在某些场景下提高IO性能 Maven <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>3.0.16.RELEASE</version> </dependency> Beetl作为一个有特色功能和包含丰富功能的国产老模板引擎(有9年的维护历史),在最新的性能评测中,虽然面临很多新的极简模板引擎挑战,如Rocker,Mustache,但性能仍然排在第一位(Mac Pro,Core I7) Benchmark Mode Cnt Score Error Units Beetl.benchmark thrpt 10 80685.217 ± 3056.314 ops/s Freemarker.benchmark thrpt 10 22262.620 ± 917.084 ops/s Handlebars.benchmark thrpt 10 23077.234 ± 271.438 ops/s Mustache.benchmark thrpt 10 25899.922 ± 235.957 ops/s Pebble.benchmark thrpt 10 43756.295 ± 1816.826 ops/s Rocker.benchmark thrpt 10 49089.556 ± 693.635 ops/s Thymeleaf.benchmark thrpt 10 6998.717 ± 160.461 ops/s Trimou.benchmark thrpt 10 28561.924 ± 759.969 ops/s Velocity.benchmark thrpt 10 25087.417 ± 251.709 ops/s

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

JAVA线程池原理与源码分析

1、线程池常用接口介绍 1.1、Executor public interface Executor { void execute(Runnable command); } 执行提交的Runnable任务。其中的execute方法在将来的某个时候执行给定的任务,该任务可以在新线程、池化线程或调用线程中执行,具体由Executor的实现者决定。 1.2、ExecutorService ExecutorService继承自Executor,下面挑几个方法介绍: 1.2.1、shutdown() void shutdown(); 启动有序关闭线程池,在此过程中执行先前提交的任务,但不接受任何新任务。如果线程池已经关闭,调用此方法不会产生额外的效果。此方法不等待以前提交的任务完成执行,可以使用awaitTermination去实现。 1.2.2、shutdownNow() List<Runnable> shutdownNow(); 尝试停止所有正在积极执行的任务, 停止处理等待的任务,并返回等待执行的任务列表。 此方法不等待以前提交的任务完成执行,可以使用awaitTermination去实现。除了尽最大努力停止处理积极执行的任务外,没有任何保证。例如,典型的实现是:通过Thread#interrupt取消任务执行,但是任何未能响应中断的任务都可能永远不会终止。 1.2.3、isShutdown() boolean isShutdown(); 返回线程池关闭状态。 1.2.4、isTerminated() boolean isTerminated(); 如果关闭后所有任务都已完成,则返回 true。注意,除非首先调用了shutdown或shutdownNow,否则isTerminated永远不会返回true。 1.2.5、awaitTermination(long timeout, TimeUnit unit) boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; 线程阻塞阻塞,直到所有任务都在shutdown请求之后执行完毕,或者超时发生,或者当前线程被中断(以先发生的情况为准)。 1.2.6、submit <T> Future<T> submit(Callable<T> task); 提交一个value-returning任务以执行,并返回一个表示该任务未决结果的Future。 Future的 get方法将在成功完成任务后返回任务的结果。 1.3、ScheduledExecutorService 安排命令在给定的延迟之后运行,或者定期执行,继承自ExecutorService接口由以下四个方法组成: //在给定延迟之后启动任务,返回ScheduledFuture public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit); public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); //创建并执行一个周期性操作,该操作在给定的初始延迟之后首次启动,然后在给定的周期内执行; //如果任务的任何执行遇到异常,则禁止后续执行。否则,任务只会通过执行器的取消或终止而终止。 //如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会并发执行。 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit); //创建并执行一个周期性操作,该操作在给定的初始延迟之后首次启动,然后在一次执行的终止和下一次执行的开始之间使用给定的延迟。 //如果任务的任何执行遇到异常,则禁止后续执行。否则,任务只会通过执行器的取消或终止而终止。 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit); 1.4、ThreadFactory public interface ThreadFactory { Thread newThread(Runnable r); } 按需创建新线程的对象。 1.5、Callable @FunctionalInterface public interface Callable<V> { V call() throws Exception; } 返回任务结果也可能抛出异常。 1.6、Future public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException; Future表示异步计算的结果。方法用于检查计算是否完成,等待计算完成并检索计算结果。只有当计算完成时,才可以使用方法get检索结果,如果需要,可以阻塞,直到准备好为止。取消由cancel方法执行。还提供了其他方法来确定任务是否正常完成或被取消。一旦计算完成,就不能取消计算。 1.7、Delayed public interface Delayed extends Comparable<Delayed> { //在给定的时间单位中返回与此对象关联的剩余延迟 long getDelay(TimeUnit unit); } 一种混合风格的接口,用于标记在给定延迟之后应该执行的对象。 1.8、ScheduledFuture public interface ScheduledFuture<V> extends Delayed, Future<V> {} 2、线程池工作流程 新任务进来时: 如果当前运行的线程少于corePoolSize,则创建新线程(核心线程)来执行任务。 如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue。 如果BlockingQueue队列已满,则创建新的线程(非核心)来处理任务。 如果核心线程与非核心线程总数超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler拒绝策略。 3、ThreadPoolExecutor介绍 构造方法: public ThreadPoolExecutor( int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 参数说明: corePoolSize 除非设置了 allowCoreThreadTimeOut,否则要保留在线程池中的线程数(即使它们是空闲的)。 maximumPoolSize 线程池中允许的最大线程数。 keepAliveTime 当线程数大于corePoolSize时,这是多余的空闲线程在终止新任务之前等待新任务的最长时间。 unit keepAliveTime参数的时间单位。 workQueue 用于在任务执行前保存任务的队列。这个队列只包含execute方法提交的Runnable任务。 threadFactory 执行程序创建新线程时使用的工厂。 handler 由于达到线程边界和队列容量而阻塞执行时使用的处理程序。 3.1、BlockingQueue SynchronousQueue 不存储元素的阻塞队列,一个插入操作,必须等待移除操作结束,每个任务一个线程。使用的时候maximumPoolSize一般指定成Integer.MAX_VALUE。 LinkedBlockingQueue 如果当前线程数大于等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中。 ArrayBlockingQueue 可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则执行拒绝策略。 DelayQueue 队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。 priorityBlockingQuene 具有优先级的无界阻塞队列。 3.2、RejectedExecutionHandler 有4个ThreeadPoolExecutor内部类。 AbortPolicy 直接抛出异常,默认策略。 CallerRunsPolicy 用调用者所在的线程来执行任务。 DiscardOldestPolicy 丢弃阻塞队列中靠最前的任务,并执行当前任务。 4、DiscardPolicy 直接丢弃任务。 最好自定义饱和策略,实现RejectedExecutionHandler接口,如:记录日志或持久化存储不能处理的任务。 3.3、线程池大小设置 CPU密集型 尽量使用较小的线程池,减少CUP上下文切换,一般设置为CPU核心数+1。 IO密集型 可以适当加大线程池数量,IO多,所以在等待IO的时候,充分利用CPU,一般设置为CPU核心数2倍。 但是对于一些特别耗时的IO操作,盲目的用线程池可能也不是很好,通过异步+单线程轮询,上层再配合上一个固定的线程池,效果可能更好,参考Reactor模型。 混合型 视具体情况而定。 3.4、任务提交 Callable 通过submit函数提交,返回Future对象。 Runnable 通过execute提交,没有返回结果。 3.5、关闭线程池 shutdown() 仅停止阻塞队列中等待的线程,那些正在执行的线程就会让他们执行结束。 shutdownNow() 不仅会停止阻塞队列中的线程,而且会停止正在执行的线程。 4、线程池实现原理 4.1、 线程池状态 线程池的内部状态由AtomicInteger修饰的ctl表示,其高3位表示线程池的运行状态,低29位表示线程池中的线程数量。 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 主池控制状态ctl是一个原子整数,包含两个概念字段: workerCount:指示有效线程数。 runState:指示是否运行、关闭等。 为了将这两个字段打包成一个整型,所以将workerCount限制为(2^29)-1个线程,而不是(2^31)-1个线程。 workerCount是工作线程数量。该值可能与实际活动线程的数量存在暂时性差异,例如,当ThreadFactory在被请求时无法创建线程,以及退出的线程在终止前仍在执行bookkeeping时。 用户可见的池大小报告为工作线程集的当前大小。 runState提供了生命周期,具有以下值: RUNNING:接受新任务并处理排队的任务 SHUTDOWN:不接受新任务,而是处理队列的任务。 STOP:不接受新任务,不处理队列的任务,中断正在进行的任务。 TIDYING:所有任务都已终止,workerCount为零,过渡到状态TIDYING的线程将运行terminated()钩子方法。 TERMINATED:terminated()方法执行完毕。 为了允许有序比较,这些值之间的数值顺序很重要。运行状态会随着时间单调地增加,但不需要达到每个状态。转换: RUNNING -> SHUTDOWN 在调用shutdown()时,可以隐式地在finalize()中调用。 (RUNNING or SHUTDOWN) -> STOP 调用shutdownNow()。 SHUTDOWN -> TIDYING 当队列和池都为空时。 STOP -> TIDYING 当池是空的时候。 TIDYING -> TERMINATED 当terminated()钩子方法完成时。 当状态达到TERMINATED时,在awaitTermination()中等待的线程将返回。 下面看以下其他状态信息: //Integer.SIZE为32,COUNT_BITS为29 private static final int COUNT_BITS = Integer.SIZE - 3; //2^29-1 最大线程数 private static final int CAPACITY = (1 << COUNT_BITS) - 1; /** * 即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务; * 111 0 0000 0000 0000 0000 0000 0000 0000 * -1 原码:0000 ... 0001 反码:1111 ... 1110 补码:1111 ... 1111 * 左移操作:后面补 0 * 111 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int RUNNING = -1 << COUNT_BITS; /** * 即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务; * 000 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int SHUTDOWN = 0 << COUNT_BITS; /** * 即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在* 运行的任务; * 001 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int STOP = 1 << COUNT_BITS; /** * 即高3位为010,所有任务都已终止,workerCount为零,过渡到状态TIDYING的线程将运行terminated()钩子方法; * 010 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int TIDYING = 2 << COUNT_BITS; /** * 即高3位为011,terminated()方法执行完毕; * 011 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int TERMINATED = 3 << COUNT_BITS; //根据ctl计算runState private static int runStateOf(int c) { //2^29 = 001 0 0000 0000 0000 0000 0000 0000 0000 //2^29-1 = 000 1 1111 1111 1111 1111 1111 1111 1111 //~(2^29-1)=111 0 0000 0000 0000 0000 0000 0000 0000 //假设c为 STOP 001 0 0000 0000 0000 0000 0000 0000 0000 // 最终值: 001 0 0000 0000 0000 0000 0000 0000 0000 return c & ~CAPACITY; } //根据ctl计算 workerCount private static int workerCountOf(int c) { //2^29-1 = 000 1 1111 1111 1111 1111 1111 1111 1111 //假设c = 000 0 0000 0000 0000 0000 0000 0000 0001 1个线程 //最终值: 000 0 0000 0000 0000 0000 0000 0000 0001 1 return c & CAPACITY; } // 根据runState和workerCount计算ctl private static int ctlOf(int rs, int wc) { //假设 rs: STOP 001 0 0000 0000 0000 0000 0000 0000 0000 //假设 wc: 000 0 0000 0000 0000 0000 0000 0000 0001 1个线程 //最终值: 001 0 0000 0000 0000 0000 0000 0000 0001 return rs | wc; } private static boolean runStateLessThan(int c, int s) { return c < s; } private static boolean runStateAtLeast(int c, int s) { return c >= s; } //RUNNING状态为负数,肯定小于SHUTDOWN,返回线程池是否为运行状态 private static boolean isRunning(int c) { return c < SHUTDOWN; } //试图增加ctl的workerCount字段值。 private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); } //尝试减少ctl的workerCount字段值。 private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); } //递减ctl的workerCount字段。这只在线程突然终止时调用(请参阅processWorkerExit)。在getTask中执行其他递减。 private void decrementWorkerCount() { do { } while (!compareAndDecrementWorkerCount(ctl.get())); } Doug Lea大神的设计啊,感觉计算机的基础真的是数学。 4.2、 内部类Worker Worker继承了AbstractQueuedSynchronizer,并且实现了Runnable接口。 维护了以下三个变量,其中completedTasks由volatile修饰。 //线程这个工作程序正在运行。如果工厂失败,则为空。 final Thread thread; //要运行的初始任务。可能是null。 Runnable firstTask; //线程任务计数器 volatile long completedTasks; 构造方法: //使用ThreadFactory中给定的第一个任务和线程创建。 Worker(Runnable firstTask) { //禁止中断,直到运行工作程序 setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } 既然实现了Runnable接口,必然实现run方法: //Delegates main run loop to outer runWorker public void run() { //核心 runWorker(this); } 4.3、runWorker(Worker w)执行任务 先看一眼执行流程图,再看源码,会更清晰一点: 首先来看runWorker(Worker w)源码: final void runWorker(Worker w) { //获取当前线程 Thread wt = Thread.currentThread(); //获取第一个任务 Runnable task = w.firstTask; //第一个任务位置置空 w.firstTask = null; //因为Worker实现了AQS,此处是释放锁,new Worker()是state==-1,此处是调用Worker类的 release(1)方法,将state置为0。Worker中interruptIfStarted()中只有state>=0才允许调用中断 w.unlock(); //是否突然完成,如果是由于异常导致的进入finally,那么completedAbruptly==true就是突然完成的 boolean completedAbruptly = true; try { //先处理firstTask,之后依次处理其他任务 while (task != null || (task = getTask()) != null) { //获取锁 w.lock(); //如果池停止,确保线程被中断;如果没有,请确保线程没有中断。这需要在第二种情况下重新检查,以处理清除中断时的shutdownNow竞争 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //自定义实现 beforeExecute(wt, task); Throwable thrown = null; try { //执行任务 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //自定义实现 afterExecute(task, thrown); } } finally { task = null; //任务完成数+1 w.completedTasks++; //释放锁 w.unlock(); } } completedAbruptly = false; } finally { //Worker的结束后的处理工作 processWorkerExit(w, completedAbruptly); } } 下面再来看上述源码中的getTask()与processWorkerExit(w, completedAbruptly)方法: 4.3.1、getTask() 根据当前配置设置执行阻塞或定时等待任务,或者如果该worker因为任何原因必须退出,则返回null,在这种情况下workerCount将递减。 返回空的情况: 大于 maximumPoolSize 个 workers(由于调用setMaximumPoolSize) 线程池关闭 线程池关闭了并且队列为空 这个worker超时等待任务,超时的worker在超时等待之前和之后都可能终止(即allowCoreThreadTimeOut || workerCount > corePoolSize),如果队列不是空的,那么这个worker不是池中的最后一个线程。 private Runnable getTask() { // Did the last poll() time out? boolean timedOut = false; for (; ; ) { //获取线程池状态 int c = ctl.get(); int rs = runStateOf(c); //仅在必要时检查队列是否为空。 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { //递减ctl的workerCount字段 decrementWorkerCount(); return null; } //获取workerCount数量 int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //线程超时控制 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { //尝试减少ctl的workerCount字段 if (compareAndDecrementWorkerCount(c)) return null; continue; } try { //如果有超时控制,则使用带超时时间的poll,否则使用take,没有任务的时候一直阻塞,这两个方法都会抛出InterruptedException Runnable r = timed ?workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) :workQueue.take(); //有任务就返回 if (r != null) return r; //获取任务超时,肯定是走了poll逻辑 timedOut = true; } catch (InterruptedException retry) { //被中断 timedOut = false; } } } 4.3.1、processWorkerExit(Worker w, boolean completedAbruptly) 为垂死的worker进行清理和bookkeeping。仅从工作线程调用。除非completedAbruptly被设置,否则假定workerCount已经被调整以考虑退出。此方法从工作集中移除线程,如果线程池由于用户任务异常而退出,或者运行的工作池小于corePoolSize,或者队列非空但没有工作池, 则可能终止线程池或替换工作池。 private void processWorkerExit(Worker w, boolean completedAbruptly) { // If abrupt, then workerCount wasn't adjusted // true:用户线程运行异常,需要扣减 // false:getTask方法中扣减线程数量 if (completedAbruptly) //递减ctl的workerCount字段。 decrementWorkerCount(); //获取主锁,锁定 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //更新完成任务计数器 completedTaskCount += w.completedTasks; //移除worker workers.remove(w); } finally { //解锁 mainLock.unlock(); } // 有worker线程移除,可能是最后一个线程退出需要尝试终止线程池 tryTerminate(); int c = ctl.get(); // 如果线程为running或shutdown状态,即tryTerminate()没有成功终止线程池,则判断是否有必要一个worker if (runStateLessThan(c, STOP)) { // 正常退出,计算min:需要维护的最小线程数量 if (!completedAbruptly) { // allowCoreThreadTimeOut 默认false:是否需要维持核心线程的数量 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 如果min ==0 或者workerQueue为空,min = 1 if (min == 0 && !workQueue.isEmpty()) min = 1; // 如果线程数量大于最少数量min,直接返回,不需要新增线程 if (workerCountOf(c) >= min) return; // replacement not needed } // 添加一个没有firstTask的worker addWorker(null, false); } } 4.4、任务提交 提交有两种: Executor#execute(Runnable command) Executor接口提供的方法,在将来的某个时候执行给定的命令.该命令可以在新线程、池化线程或调用线程中执行,具体由Executor的实现者决定。 ExecutorService#submit(Callable<T> task) 提交一个value-returning任务以执行,并返回一个表示该任务未决结果的Future。Future的get方法将在成功完成任务后返回任务的结果。 4.5、任务执行 4.5.1、 execute(Runnable command) 任务执行流程图: 三步处理: 如果运行的线程小于corePoolSize,则尝试用给定的命令作为第一个任务启动一个新线程。对addWorker的调用原子性地检查runState和workerCount,因此可以通过返回false来防止错误警报,因为错误警报会在不应该添加线程的时候添加线程。 如果一个任务可以成功排队,那么我们仍然需要再次检查是否应该添加一个线程 (因为自上次检查以来已有的线程已经死亡),或者池在进入这个方法后关闭。因此,我们重新检查状态,如果必要的话,如果停止,则回滚队列;如果没有,则启动一个新线程。 如果无法对任务排队,则尝试添加新线程。 如果它失败了,我们知道pool被关闭或饱和,所以拒绝任务。 public void execute(Runnable command) { //任务为空,抛出异常 if (command == null) throw new NullPointerException(); //获取线程控制字段的值 int c = ctl.get(); //如果当前工作线程数量少于corePoolSize(核心线程数) if (workerCountOf(c) < corePoolSize) { //创建新的线程并执行任务,如果成功就返回 if (addWorker(command, true)) return; //上一步失败,重新获取ctl c = ctl.get(); } //如果线城池正在运行,且入队成功 if (isRunning(c) && workQueue.offer(command)) { //重新获取ctl int recheck = ctl.get(); //如果线程没有运行且删除任务成功 if (!isRunning(recheck) && remove(command)) //拒绝任务 reject(command); //如果当前的工作线程数量为0,只要还有活动的worker线程,就可以消费workerQueue中的任务 else if (workerCountOf(recheck) == 0) //第一个参数为null,说明只为新建一个worker线程,没有指定firstTask addWorker(null, false); } else if (!addWorker(command, false)) //如果线程池不是running状态 或者 无法入队列,尝试开启新线程,扩容至maxPoolSize,如果addWork(command, false)失败了,拒绝当前command reject(command); } 下面详细看一下上述代码中出现的方法:addWorker(Runnable firstTask, boolean core)。 4.5.1.1、addWorker(Runnable firstTask, boolean core) 检查是否可以根据当前池状态和给定的界限(核心或最大值)添加新worker,如果是这样,worker计数将相应地进行调整,如果可能,将创建并启动一个新worker, 并将运行firstTask作为其第一个任务。 如果池已停止或有资格关闭,则此方法返回false。如果线程工厂在被请求时没有创建线程,则返回false。如果线程创建失败,要么是由于线程工厂返回null,要么是由于异常 (通常是Thread.start()中的OutOfMemoryError)),我们将回滚。 private boolean addWorker(Runnable firstTask, boolean core) { //好久没见过这种写法了 retry: //线程池状态与工作线程数量处理,worker数量+1 for (; ; ) { //获取当前线程池状态与线程数 int c = ctl.get(); //获取当前线程池状态 int rs = runStateOf(c); // 仅在必要时检查队列是否为空。如果池子处于SHUTDOWN,STOP,TIDYING,TERMINATED的时候 不处理提交的任务,判断线程池是否可以添加worker线程 if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) return false; //线程池处于工作状态 for (; ; ) { //获取工作线程数量 int wc = workerCountOf(c); //如果线程数量超过最大值或者超过corePoolSize或者超过maximumPoolSize 拒绝执行任务 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //试图增加ctl的workerCount字段 if (compareAndIncrementWorkerCount(c)) //中断外层循环 break retry; // Re-read ctl c = ctl.get(); //如果当前线程池状态已经改变 if (runStateOf(c) != rs) //继续外层循环 continue retry; //否则CAS因workerCount更改而失败;重试内循环 } } //添加到worker线程集合,并启动线程,工作线程状态 boolean workerStarted = false; boolean workerAdded = false; //继承AQS并实现了Runnable接口 Worker w = null; try { //将任务封装 w = new Worker(firstTask); //获取当前线程 final Thread t = w.thread; if (t != null) { //获取全局锁 final ReentrantLock mainLock = this.mainLock; //全局锁定 mainLock.lock(); try { //持锁时重新检查。退出ThreadFactory故障,或者在获取锁之前关闭。 int rs = runStateOf(ctl.get()); //如果当前线程池关闭了 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //测试该线程是否活动。如果线程已经启动并且还没有死,那么它就是活的。 if (t.isAlive()) throw new IllegalThreadStateException(); //入工作线程池 workers.add(w); 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; } 再分析回滚工作线程创建逻辑方法:addWorkerFailed(w)。 回滚工作线程创建,如果存在,则从worker中移除worker, 递减ctl的workerCount字段。,重新检查终止,以防这个worker的存在导致终止。 private void addWorkerFailed(Worker w) { //获取全局锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //如果存在,则从worker中移除worker if (w != null) workers.remove(w); //递减ctl的workerCount字段。 decrementWorkerCount(); //重新检查终止 tryTerminate(); } finally { mainLock.unlock(); } } 其中的tryTerminate()方法: 如果是SHUTDOWN或者STOP 且池子为空,转为TERMINATED状态。如果有条件终止,但是workerCount不为零,则中断空闲worker,以确保关机信号传播。必须在任何可能使终止成为可能的操作之后调用此方法--在关机期间减少worker数量或从队列中删除任务。该方法是非私有的,允许从ScheduledThreadPoolExecutor访问。 final void tryTerminate() { for (; ; ) { int c = ctl.get(); //如果线程池处于运行中,或者阻塞队列中仍有任务,返回 if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) return; //还有工作线程 if (workerCountOf(c) != 0) { //中断空闲工作线程 interruptIdleWorkers(ONLY_ONE); return; } //获取全局锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //设置ctl状态TIDYING if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { //方法在执行程序终止时调用,默认什么都不执行 terminated(); } finally { //完成terminated()方法,状态为TERMINATED ctl.set(ctlOf(TERMINATED, 0)); //唤醒所有等待条件的节点 termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } } //方法在执行程序终止时调用,默认什么都不执行 protected void terminated() {} 4.5.1.2、 reject(Runnable command)拒绝策略 为给定的命令调用被拒绝的执行处理程序。 final void reject(Runnable command) { handler.rejectedExecution(command, this); }

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

Beetl 3.0.15 版本发布,Java 模板引擎

本次发布修复了两个Bug 俩对模板占位符顺序,比如“今天是星期{day},我的名字叫{{name}},性别{sex}” 导致解析不完全 访问自定义的Map报错 <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>3.0.13.RELEASE</version> </dependency> Beetl 在线使用体验http://ibeetl.com/beetlonline/ Beetl 模板(文本处理)引擎功能齐全,使用体验优秀,具备许多创新功能和实现。也是当今世界跑的最快的模板引擎,从 JDK6以来,有一些新生代模板引擎诞生,Beetl 仍然是性能王者,如下是一个性 JMH 能测试结果,来自template-benchmark

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

Java描述设计模式(22):策略模式

本文源码:GitHub·点这里 || GitEE·点这里 一、生活场景 每年双十一,各大电商平台会推出不同的满减策略,当用户的消费金额满一定额度后,会进行减去一定的优惠额度,从而来一波清仓甩卖,使用策略模式来描述该流程。 public class C01_InScene { public static void main(String[] args) { // 选择满减策略,走相应的计算方式 FullReduce strategy = new Full100 (); Payment price = new Payment(strategy); double quote = price.payment(300); System.out.println("最终价格为:" + quote); } } /** * 付款 */ class Payment { private FullReduce fullReduce ; public Payment (FullReduce fullReduce){ this.fullReduce = fullReduce ; } public double payment (double totalPrice){ return this.fullReduce.getPayMoney(totalPrice) ; } } /** * 金额满减接口 */ interface FullReduce { double getPayMoney (double totalPrice) ; } /** * 不同的满减策略 */ class Full100 implements FullReduce { @Override public double getPayMoney(double totalPrice) { if (totalPrice >= 100){ totalPrice = totalPrice-20.0 ; } return totalPrice ; } } class Full500 implements FullReduce { @Override public double getPayMoney(double totalPrice) { if (totalPrice >= 500){ totalPrice = totalPrice-120.0 ; } return totalPrice ; } } 二、策略模式 1、基础概念 策略模式属于对象的行为模式。策略模式中定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客 户端。 2、模式图解 3、核心角色 环境角色 持有一个Strategy策略接口角色的引用。 抽象策略角色 通常由一个接口或抽象类实现。此角色给出所有的具体策略类要实现的接口。 具体策略角色 包装相关的算法或业务流程。 4、源码实现 public class C02_Strategy { public static void main(String[] args) { Strategy strategy = new ConcreteStrategyB() ; Context context = new Context(strategy) ; context.userMethod(); } } /** 环境角色类 */ class Context { //持有一个具体策略的对象 private Strategy strategy; /** * 构造函数,传入一个具体策略对象 * @param strategy 具体策略对象 */ public Context(Strategy strategy){ this.strategy = strategy; } public void userMethod (){ this.strategy.strategyMethod(); } } /** 抽象策略类 */ interface Strategy { // 策略方法 void strategyMethod () ; } /** 具体策略类 */ class ConcreteStrategyA implements Strategy { @Override public void strategyMethod() { System.out.println("策略A方法"); } } class ConcreteStrategyB implements Strategy { @Override public void strategyMethod() { System.out.println("策略B方法"); } } 三、策略模式总结 策略模式的关键是:变化的与不变分离,体现了“对修改关闭,对扩展开放”原则。客户端增加行为不用修改原有代码,只要添加一种策略即可,易于切换、易于理解、易于扩展。策略过多是会导致类数目庞大,变得难以维护。 四、源代码地址 GitHub·地址 https://github.com/cicadasmile/model-arithmetic-parent GitEE·地址 https://gitee.com/cicadasmile/model-arithmetic-parent

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

Java描述设计模式(21):状态模式

本文源码:GitHub·点这里 || GitEE·点这里 一、生活场景 1、场景描述 变色龙是爬行动物,是非常奇特的动物,它有适于树栖生活的种种特征和行为,身体也会随着环境的变化而变化出适应环境的颜色,非常神奇。下面基于状态模式对该变化过程进行描述。 2、代码实现 public class C01_InScene { public static void main(String[] args) { Chameleon chameleon = new Chameleon("红色","花丛环境") ; LifeContext lifeContext = new LifeContext() ; // 树叶环境 BodyColor bodyColor = new GreenColor (); lifeContext.setBodyColor(bodyColor); lifeContext.change(chameleon); // 树枝环境 bodyColor = new GrayColor() ; lifeContext.setBodyColor(bodyColor); lifeContext.change(chameleon); } } /** * 变色龙 */ class Chameleon { public String color ; public String contextDesc ; public Chameleon(String color, String contextDesc) { this.color = color; this.contextDesc = contextDesc; } } /** * 变色龙生存环境 */ class LifeContext { private BodyColor bodyColor; public void setBodyColor(BodyColor bodyColor) { this.bodyColor = bodyColor; } public void change (Chameleon chameleon){ bodyColor.change(chameleon) ; } } /** * 变色龙身体颜色抽象类 */ interface BodyColor { void change (Chameleon chameleon); } /** * 变色龙身体颜色具体类 */ class GreenColor implements BodyColor { @Override public void change(Chameleon chameleon) { System.out.println("变化前:"+chameleon.color+";"+chameleon.contextDesc); chameleon.contextDesc = "树叶环境" ; chameleon.color = "绿色" ; System.out.println("变化后:"+chameleon.color+";"+chameleon.contextDesc); } } class GrayColor implements BodyColor { @Override public void change(Chameleon chameleon) { System.out.println("变化前:"+chameleon.color+";"+chameleon.contextDesc); chameleon.contextDesc = "树枝环境" ; chameleon.color = "灰色" ; System.out.println("变化后:"+chameleon.color+";"+chameleon.contextDesc); } } 二、状态模式 1、基础概念 状态模式是对象的行为模式,状态模式允许一个对象在其内部状态改变的时候改变其行为。状态模式把对象的行为封装在不同状态的对象中,每一个状态对象都是抽象状态类的子类。意图是让一个对象在其内部状态改变的时候,其行为也随之改变。 2、模式图解 3、核心角色 环境角色 持有具体状态类的对象实例。这个具体状态类的实例给出此环境对象的现有状态。 抽象状态角色 定义一个接口,封装环境对象的状态所对应的行为。 具体状态角色 具体状态类实现了环境的状态所对应的行为。 4、源码实现 public class C02_State { public static void main(String[] args){ Context context = new Context(); State state = new ConcreteStateA() ; context.setState(state); context.printInfo("当前环境状态A"); state = new ConcreteStateB(); context.setState(state); context.printInfo("当前环境状态B"); } } /** * 环境角色 */ class Context { private State state; public void setState(State state) { this.state = state; } public void printInfo (String info) { state.stateInfo(info); } } /** * 抽象状态角色 */ interface State { void stateInfo (String param); } /** * 具体状态角色 */ class ConcreteStateA implements State { @Override public void stateInfo (String info) { System.out.println("ConcreteStateA:" + info); } } class ConcreteStateB implements State { @Override public void stateInfo (String info) { System.out.println("ConcreteStateB:" + info); } } 三、模式总结 将容易产生问题的if-else语句拆分,状态模式将每个状态的行为封装到对应的一个类中,代码有很强的可读性。 符合“开闭原则”,容易增删操作,管理状态。 会存在很多状态时。每个状态都要一个对应的类,会产生很多类,增加维护难度。 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,不同的状态有不同的行为,可以考虑使用状态模式。 四、源代码地址 GitHub·地址 https://github.com/cicadasmile/model-arithmetic-parent GitEE·地址 https://gitee.com/cicadasmile/model-arithmetic-parent

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

Java描述设计模式(20):命令模式

本文源码:GitHub·点这里 || GitEE·点这里 一、生活场景 1、场景描述 智能电脑的品牌越来越多,由此诞生了一款电脑控制的APP,万能遥控器,用户在使用遥控器的时候,可以切换为自家电视的品牌,然后对电视进行控制。 2、代码实现 public class C01_InScene { public static void main(String[] args) { TVClient tvClient = new TVClient() ; Remote remote = new RemoteApp(tvClient) ; UserClient userClient = new UserClient(remote) ; userClient.action("HM","换台"); } } /** * 遥控接口 */ interface Remote { void controlTV (String tvType,String task); } /** * 遥控器APP */ class RemoteApp implements Remote { private TVClient tvClient = null ; public RemoteApp (TVClient tvClient){ this.tvClient = tvClient ; } @Override public void controlTV(String tvType, String task) { tvClient.action(tvType,task); } } /** * 用户端 */ class UserClient { // 持有遥控器 private Remote remote = null ; public UserClient (Remote remote){ this.remote = remote ; } public void action (String tvType, String task){ remote.controlTV(tvType,task); } } /** * 电视端 */ class TVClient { public void action (String tvType, String task){ System.out.println("TV品牌:"+tvType+";执行:"+task); } } 二、命令模式 1、基础概念 命令模式属于对象的行为模式。命令模式把一个请求或者操作封装到一个对象中。把发出命令的动作和执行命令的动作分割开,委派给不同的对象。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行。 2、模式图解 3、核心角色 命令角色 声明所有具体命令类的抽象接口。 具体命令角色 定义接收者和行为之间的交互方式:实现execute()方法,调用接收者的相应操作 , 传递命令信息。 请求者角色 负责调用命令对象执行请求,相关的方法叫做行动方法。 接收者角色 执行请求。任何一个类都可以成为接收者,执行请求的方法叫做行动方法。 4、源码实现 public class C02_Command { public static void main(String[] args) { Receiver receiver = new Receiver(); Command command = new ConcreteCommand(receiver); Invoker invoker = new Invoker(command); invoker.action("卧倒"); } } /** * 命令角色 */ interface Command { // 执行方法 void execute(String task); } /** * 具体命令角色类 */ class ConcreteCommand implements Command { //持有相应的接收者对象 private Receiver receiver = null; public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } @Override public void execute(String task) { //接收方来真正执行请求 receiver.action(task); } } /** * 请求者角色类 */ class Invoker { // 持有命令对象 private Command command = null; public Invoker(Command command){ this.command = command; } // 行动方法 public void action(String task){ command.execute(task); } } /** * 接收者角色类 */ class Receiver { // 执行命令操作 public void action(String task){ System.out.println("执行命令:"+task); } } 三、Spring框架应用 Spring框架中封装的JdbcTemplate类API使用到了命令模式。 1、JdbcOperations接口 public interface JdbcOperations { @Nullable <T> T execute(StatementCallback<T> var1) ; } 2、JdbcTemplate类 这里只保留模式方法的代码。 public class JdbcTemplate implements JdbcOperations { @Nullable public <T> T execute(StatementCallback<T> action) { try { T result = action.doInStatement(stmt); } catch (SQLException var9) { } finally { } } } 3、StatementCallback接口 @FunctionalInterface public interface StatementCallback<T> { @Nullable T doInStatement(Statement var1) ; } 四、命令模式总结 松散的耦合 命令模式使得命令发起者和命令执行者解耦,发起命令的对象完全不知道具体实现对象是谁。这和常见的MQ消息队列原理是类似的。 动态的控制 命令模式把请求封装起来,可以动态地对它进行参数化、队列化和等操作和管理,使系统更加的灵活。 良好的扩展性 命令发起者和命令执行者实现完全解耦,因此扩展添加新命令很容易。 五、源代码地址 GitHub·地址 https://github.com/cicadasmile/model-arithmetic-parent GitEE·地址 https://gitee.com/cicadasmile/model-arithmetic-parent

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

Java设计模式之原型模式

​ 一、概述 原型模式(Prototype Pattern)用于创建重复的对象,同时又能保证性能。它属于创建型设计模式,它提供了一种创建对象的最佳方法。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 二、介绍 意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。主要解决:在运行期建立和删除模型。何时使用: 当一个系统应该独立于它的产品创建,构成和表示时。 当要实例化的类是在运行时指定时,例如,通过动态装载。 为了避免一个与产品类层次平行的工厂类层次时。 当一个类的实例只能有几个不同状态组合中的一种时。创建相应数目的原型并克隆它们可能比每次用何时的状态手工实例化该类更方便一些。 如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。 实现克隆操作,继承Cloneable,重写clone()方法。 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有稳定的接口。 应用实例:细胞分裂;Object的clone()方法。优点:性能提高;逃避构造函数的约束。缺点: 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 必须实现Cloneable接口。 使用场景: 资源优化场景。 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等。 性能和安全要求的场景。 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 一个对象多个修改者的场景。 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 在实际项目中,原型模式很少单独出现,一般是和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。 注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现Cloneable,重写,深拷贝是通过实现Serializable读取二进制流。 三、实现 我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类ShapeCache,该类把shape对象存储在一个Hashtable中,并在请求的时候返回他们的克隆。具体UML图如下: 步骤1 创建一个实现了Cloneable接口的抽象类 Shape: public abstract class Shape implements Cloneable { private String id; protected String type; abstract void draw(); public String getType(){ return type; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } } 步骤2 创建扩展了上面抽象类的实体类: //Rectangle public class Rectangle extends Shape { public Rectangle(){ type = "Rectangle"; } @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } //Square public class Square extends Shape { public Square(){ type = "Square"; } @Override public void draw() { System.out.println("Inside Square::draw() method."); } } //Circle public class Circle extends Shape { public Circle(){ type = "Circle"; } @Override public void draw() { System.out.println("Inside Circle::draw() method."); } } 步骤3 创建一个类,从数据库获取实体类,并把它们存储在一个Hashtable中。 public class ShapeCache { private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>(); public static Shape getShape(String shapeId) { Shape cachedShape = shapeMap.get(shapeId); return (Shape) cachedShape.clone(); } // 对每种形状都运行数据库查询,并创建该形状 // shapeMap.put(shapeKey, shape); // 例如,我们要添加三种形状 public static void loadCache() { Circle circle = new Circle(); circle.setId("1"); shapeMap.put(circle.getId(),circle); Square square = new Square(); square.setId("2"); shapeMap.put(square.getId(),square); Rectangle rectangle = new Rectangle(); rectangle.setId("3"); shapeMap.put(rectangle.getId(),rectangle); } } 步骤4 测试类: public class PrototypePatternDemo { public static void main(String[] args) { ShapeCache.loadCache(); Shape clonedShape = (Shape) ShapeCache.getShape("1"); System.out.println("Shape : " + clonedShape.getType()); Shape clonedShape2 = (Shape) ShapeCache.getShape("2"); System.out.println("Shape : " + clonedShape2.getType()); Shape clonedShape3 = (Shape) ShapeCache.getShape("3"); System.out.println("Shape : " + clonedShape3.getType()); } } 输出结果: Shape : Circle Shape : Square Shape : Rectangle

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

JAVA设计模式(2)建造者模式

1 定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 类型:创建类模式。 四个要素: 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。 2 代码实现 class Product { private String name; private String type; public void showProduct(){ System.out.println(“名称:”+name); System.out.println(“型号:”+type); } public void setName(String name) { this.name = name; } public void setType(String type) { this.type = type; } } abstract class Builder { public abstract void setPart(String arg1, String arg2); public abstract Product getProduct(); } class ConcreteBuilder extends Builder { private Product product = new Product(); public Product getProduct() { return product; } public void setPart(String arg1, String arg2) { product.setName(arg1); product.setType(arg2); } } public class Director { private Builder builder = new ConcreteBuilder(); public Product getAProduct(){ builder.setPart(“宝马汽车”,“X7”); return builder.getProduct(); } public Product getBProduct(){ builder.setPart(“奥迪汽车”,“Q5”); return builder.getProduct(); } } public class Client { public static void main(String[] args){ Director director = new Director(); Product product1 = director.getAProduct(); product1.showProduct(); Product product2 = director.getBProduct(); product2.showProduct(); } } 3 比较及总结 建造者模式的优点: 首先,建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。 其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。 建造者模式与工厂模式的区别: 建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个“导演类”的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。 与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。 总结: 建造者模式与工厂模式类似,他们都是建造者模式,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

Spring

Spring

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册