关于多线程:谈谈你对CompletableFuture类的理解

概念CompletableFuture是jdk1.8引入的一个基于事件驱动的一个异步回调类。 比方在一个批量领取的业务逻辑外面,波及到查问订单/领取和发送邮件告诉这三个逻辑,那么这三个逻辑是依照程序逻辑同步去实现的。这种设计形式会导致这个办法的执行效率会比较慢。 所以这里能够间接应用CompletableFuture,比方把查问订单的逻辑放在一个异步线程外面去解决,而后基于CompletableFuture的一个事件回调机制,能够配置查问订单完结之后的一个触发领取的一个动作。领取完结之后再主动触发邮件告诉。从而极大晋升了这个业务场景的解决性能。 CompletableFuture提供了五种不同的形式把多个异步工作组成一个具备先后关系的解决链。而后基于事件来驱动工作链的一个执行。 CompletableFuture的应用thenCombine它是把两个工作组合在一起,当两个工作都执行完结当前触发某个事件的回调。 thenCompose把两个工作组合在一起,这两个工作是串行执行的,第一个工作执行实现后主动去触发第二个工作。 thenAccept第一个工作执行实现后主动去触发第二个工作,并且第一个工作的执行后果作为第二个的参数。纯正接管上一个工作的执行后果不返回新值的一个计算。 thenApply和thenAccept一样,它具备返回值。 thenRun第一个工作执行实现当前触发执行一个实现了Runnable接口的一个工作。 CompletableFuture补救了本来Future的有余,使程序能够在非阻塞的状态上来实现异步的一个回调机制。

September 1, 2023 · 1 min · jiezi

关于多线程:Java-锁机制实现多线程售票案例

本文首发自[慕课网](imooc.com) ,想理解更多IT干货内容,程序员圈内热闻,欢送关注"慕课网"及“慕课网公众号”!作者:王军伟Tech|慕课网讲师 1. 前言本文内容次要是应用 Java 的锁机制对多线程售票案例进行实现。售票案例少数状况下次要关注多线程如何平安的缩小库存,也就是残余的票数,当票数为 0 时,进行缩小库存。 本节内容除了关注车票库存的缩小,还会波及到退票窗口,可能更加贴切的模仿实在的场景。 本节内容须要学习者关注如下两个重点: 把握多线程的售票机制模型,在后续的工作中如果波及到相似的场景,可能第一工夫理解场景的整体构造;应用 Condition 和 Lock 实现售票机制,坚固咱们本章节内容所学习的新的锁机制。2. 售票机制模型售票机制模型是源于现实生活中的售票场景,从开始的单窗口售票到多窗口售票,从开始的人工统计票数到后续的零碎智能在线售票。多并发编程可能实现这一售票场景,多窗口售票状况下保障线程的安全性和票数的正确性。 如上图所示,有两个售票窗口进行售票,有一个窗口解决退票,这既是现实生活中一个简略的售票机制。 3. 售票机制实现场景设计: 创立一个工厂类 TicketCenter,该类蕴含两个办法,saleRollback 退票办法和 sale 售票办法;定义一个车票总数等于 10 ,为了不便察看后果,设置为 10。学习者也可自行抉择数量;对于 saleRollback 办法,当产生退票时,告诉售票窗口持续售卖车票;对 saleRollback 进行特地设置,每隔 5000 毫秒退回一张车票;对于 sale 办法,只有有车票就进行售卖。为了更便于察看后果,每卖出一张车票,sleep 2000 毫秒;创立一个测试类,main 函数中创立 2 个售票窗口和 1 个退票窗口,运行程序进行后果察看。批改 saleRollback 退票工夫,每隔 25 秒退回一张车票;再次运行程序并察看后果。实现要求:本试验要求应用 ReentrantLock 与 Condition 接口实现同步机制。 实例: public class DemoTest { public static void main(String[] args) { TicketCenter ticketCenter = new TicketCenter(); new Thread(new saleRollback(ticketCenter),"退票窗口"). start(); new Thread(new Consumer(ticketCenter),"1号售票窗口"). start(); new Thread(new Consumer(ticketCenter),"2号售票窗口"). start(); }}class TicketCenter { private int capacity = 10; // 依据需要:定义10涨车票 private Lock lock = new ReentrantLock(false); private Condition saleLock = lock.newCondition(); // 依据需要:saleRollback 办法创立,为退票应用 public void saleRollback() { try { lock.lock(); capacity++; System.out.println("线程("+Thread.currentThread().getName() + ")产生退票。" + "以后残余票数"+capacity+"个"); saleLock.signalAll(); //产生退票,告诉售票窗口进行售票 } finally { lock.unlock(); } } // 依据需要:sale 办法创立 public void sale() { try { lock.lock(); while (capacity==0) { //没有票的状况下,进行售票 try { System.out.println("正告:线程("+Thread.currentThread().getName() + ")筹备售票,但以后没有残余车票"); saleLock.await(); //残余票数为 0 ,无奈售卖,进入 wait } catch (InterruptedException e) { e.printStackTrace(); } } capacity-- ; //如果有票,则售卖 -1 System.out.println("线程("+Thread.currentThread().getName() + ")售出一张票。" + "以后残余票数"+capacity+"个"); } finally { lock.unlock(); } }}class saleRollback implements Runnable { private TicketCenter TicketCenter; //关联工厂类,调用 saleRollback 办法 public saleRollback(TicketCenter TicketCenter) { this.TicketCenter = TicketCenter; } public void run() { while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } TicketCenter.saleRollback(); //依据需要 ,调用 TicketCenter 的 saleRollback 办法 } }}class Consumer implements Runnable { private TicketCenter TicketCenter; public Consumer(TicketCenter TicketCenter) { this.TicketCenter = TicketCenter; } public void run() { while (true) { TicketCenter.sale(); //调用sale 办法 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }}后果验证: ...

June 1, 2023 · 2 min · jiezi

关于多线程:多线程基础

一、线程生命周期(状态) 二、线程死亡(DEAD)线程会以上面三种形式完结,完结后就是死亡状态。失常完结 1. run()或 call()办法执行实现,线程失常完结。异样完结 2. 线程抛出一个未捕捉的 Exception 或 Error。调用 stop 3. 间接调用该线程的 stop()办法来完结该线程—该办法通常容易导致死锁,不举荐应用。三、JAVA 线程实现/创立形式 继承 Thread 类实现 Runnable 接口。ExecutorService、Callable<Class>、Future 有返回值线程基于线程池的形式 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize);} public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());} public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());} public static ExecutorService newSingleThreadExecutor() { return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}四、sleep 与 wait 区别 ...

February 27, 2023 · 1 min · jiezi

关于多线程:性能优化之使用vueworker插件基于Web-Worker开启多线程运算提高效率

什么是Web Worker15年前,也就是2008年,html第五版html5公布,这一版的公布,提供了不少新的好用的性能,如: Canvas绘图拖拽dragwebsocketGeolocationwebworker等...笔者之前说过:一项新技术新的技术计划的提出,肯定是为了解决某个问题的,或者是对某种计划的优化 那么Web Worker这个新技术解决了什么问题?有哪些优化价值呢? 让咱们持续往下看... 官网定义Web Worker 为 Web 内容在后盾线程中运行脚本提供了一种简略的办法。线程能够执行工作而不烦扰用户界面。此外,他们能够应用XMLHttpRequest执行 I/O (只管responseXML和channel属性总是为空)。一旦创立,一个 worker 能够将音讯发送到创立它的 JavaScript 代码,通过将音讯公布到该代码指定的事件处理程序(反之亦然)...... 官网MDN地址:https://developer.mozilla.org...乍一看这段话,如同懂了(Web Worker和线程无关),又如同没懂。然而咱们能看到一个加粗的关键字:线程。 那么,新的问题来了,什么是线程? 当咱们去学习一个知识点A的时候,咱们会发现知识点A是由a1、a2、a3、a4组合而成的,所以咱们要持续下钻,去搞懂a1、a2、a3、a4别离是什么意思。这样能力更好的了解知识点A的含意内容。什么是线程线程根本八股文定义不赘述,大家能够将线程了解成为一个打工仔,每天的工作就是解析翻译并执行代码,像搬砖一样,不过一次只能搬一块砖,前面有砖,不好意思,你等会儿,等我把这个砖搬完了再搬你们。 线程道人朗声道:尔等砖头列队稍后,待老夫一块块搬!(js中的工作队列) js是单线程语言,生来便是!java平时写代码也是单线程的操作,不过单线程有时候不太够用,于是java中也提供了多线程的操作入口,比方:Thread类,Runnable接口,Callable接口;大多数语言都是相似的,python也是的啊,python写代码平时也是复线程,另也提供了threading模块让程序员能够去做多线程操作。为何js要设计成单线程呢?合乎大抵趋势这与js的工作内容无关:js只是用来去做一些用户交互,并出现成果内容。试想,如果js是多线程,线程一将dom元素的背景色改成红色,线程二将dom元素的背景色改为绿色,那么,到底上红色还是绿色呢?不过起初人们发现,某些状况下,前端如果能做多线程的操作,将会大大晋升开发效率于是Web Worker就应运而生了Web Worker能够创立另外的线程去做一些操作,操作不影响js主线程(比方UI渲染)原本只有一个js主线程搬砖,当初又过去好几个js辅助线程一块帮忙搬砖,那必定效率高啊!有点道友问,那如果主线程操作dom,而后在Web worker创立的辅助线程中也去操作dom呢?最终后果听谁的啊???答复:Web work创立的线程中没有document全局文档对象,所以无奈操作dom,另外,也不会这样做的大家这样了解:在理论开发场景中Web Worker次要,少数,利用在前端一些简单中运算而大家都晓得一些简单的运算,基本上交由后端去解决(实际上简单的运算操作,后端也会看状况去开启多线程操作的)这样说来,Web Worker的作用就是把后端进行的多线程操作运算拿到前端了工作中一些数据的加工、计算、组装逻辑操作,经常是由后端来干;然而在某些状况下,如果前端去做的话,效率或者更高一些所以Web Worker这个新技术的价值是什么呢? Web worker创立辅助线程、帮忙前端主线程进行简单耗时的计算一个人手不够用,那就多摇几个人。 Web worker创立的一些辅助线程,别离去帮主线程分担一些简单的、耗时的js运算,这样的话,主线程后续的代码执行就不会阻塞,当辅助线程计算出简单耗时运算后果后,再与主线程通信,将计算出的后果告知主线程。 Web Worker新技术价值,简而言之:晋升前端代码运算执行效率 对于Web worker的原生语法,诸如: // 创立一个Web workervar myWorker = new Worker('worker.js');// 应用Web worker发送音讯worker.postMessage(params)// 应用Web worker接管音讯worker.onmessage = function(e) { console.log(e.data)}// 等等...Web worker的原生语法,笔者不赘述了,大家可自行去MDN上的Web Worker去查看, 为什么不说呢?因为咱们工作中开发代码,基本上都是应用框架,在框架中间接写原生的Web worker有许多的边界异样或者其余的状况须要管制。以vue框架为例,咱们不能间接写Web Worker,须要应用Webpack中的worker-loader插件去解析Web worker,并且在vue.config.js中去做相应配置。 嗯,有些麻烦。 在这个状况下,基于Web Worker进行封装的插件库vue-worker,就闪亮退场了。 简略、好用、便于了解 这里不提Web worker的原生根本语法不是说大家不必看了,看还是要看的,只不过篇幅起因(懒),这里就不赘述了。Web Worker的利用场景如果大家只是写增删改查的业务代码,Web Worker用的确实非常少 工作中那些须要进行简单耗时的计算的逻辑代码,都能够由前端应用Web Worker进行计算。然而简单耗时的计算基本上都是交由后端进行。这个广泛认知下导致其应用的比拟少。大略有以下场景: ...

February 10, 2023 · 7 min · jiezi

关于多线程:多线程永动任务设计与实现

明天教大家撸一个 Java 的多线程永动工作,这个示例的原型是公司自研的多线程异步工作我的项目,我把外面波及到多线程的代码抽离进去,而后进行肯定的革新。 外面波及的知识点十分多,特地适宜有肯定工作教训的同学学习,或者能够间接拿到我的项目中应用。 文章构造非常简单: 1. 性能阐明做这个多线程异步工作,次要是因为咱们有很多永动的异步工作,什么是永动呢?就是工作跑起来后,须要始终跑下去。 比方音讯 Push 工作,因为始终有音讯过去,所以须要始终去生产 DB 中的未推送音讯,就须要整一个 Push 的永动异步工作。 咱们的需要其实不难,简略总结一下: 能同时执行多个永动的异步工作;每个异步工作,反对开多个线程去生产这个工作的数据;反对永动异步工作的优雅敞开,即敞开后,须要把所有的数据生产结束后,再敞开。实现下面的需要,须要留神几个点: 每个永动工作,能够开一个线程去执行;每个子工作,因为须要反对并发,须要用线程池管制;永动工作的敞开,须要告诉子工作的并发线程,并反对永动工作和并发子工作的优雅敞开。2. 多线程工作示例2.1 线程池对于子工作,须要反对并发,如果每个并发都开一个线程,用完就敞开,对资源耗费太大,所以引入线程池: public class TaskProcessUtil { // 每个工作,都有本人独自的线程池 private static Map<String, ExecutorService> executors = new ConcurrentHashMap<>(); // 初始化一个线程池 private static ExecutorService init(String poolName, int poolSize) { return new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setNameFormat("Pool-" + poolName).setDaemon(false).build(), new ThreadPoolExecutor.CallerRunsPolicy()); } // 获取线程池 public static ExecutorService getOrInitExecutors(String poolName,int poolSize) { ExecutorService executorService = executors.get(poolName); if (null == executorService) { synchronized (TaskProcessUtil.class) { executorService = executors.get(poolName); if (null == executorService) { executorService = init(poolName, poolSize); executors.put(poolName, executorService); } } } return executorService; } // 回收线程资源 public static void releaseExecutors(String poolName) { ExecutorService executorService = executors.remove(poolName); if (executorService != null) { executorService.shutdown(); } }}这是一个线程池的工具类,这里初始化线程池和回收线程资源很简略,咱们次要探讨获取线程池。 ...

December 15, 2022 · 3 min · jiezi

关于多线程:重磅来袭爆肝一周整理的多线程&高并发笔记含面试题导图笔记

前言当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,后果就是凉凉;现如今市场,多线程、高并发编程、分布式、负载平衡、集群等能够说是当初高级后端开发求职的必备技能。 很多人领有大厂梦,却因为多线程与高并发败下阵来。实际上,多线程与高并发并不难,明天这份最全的多线程与高并发总结,助你向大厂“开炮”,面试不再被多线程与高并发难倒。 留神:对于多线程与高并发的内容整顿,包含了面试题、学习笔记、应用文档以及Xmind思维图几个局部,须要高清完整版《多线程与高并发》的敌人【间接点击此处】即可获取!!对于学习多线程与高并发的思维脑图Disruptor,根底概念,高频面试加分项,JUC同步工具,线程池,同步容器 一、多线程与高并发(面试题汇合总结)多线程与高并发面试题(根底局部) 你如何确保main()办法所在的线程是Java程序最初完结的线程?ThreadLocal原理 什么是死锁(Deadlock)?如何剖析和防止死锁?什么是Java Timer类?如何创立一个有特定工夫距离的工作?什么是线程池?如何创立一个Java线程池?什么是并发容器的实现?Executors类是什么?说说CountDownLatch与CyclicBarrier区别 多线程与高并发面试题(高级进阶局部) 在静态方法上应用同步时会产生什么事?在一个对象上两个线程能够调用两个不同的同步实例办法吗?Fork/Join框架的了解 什么是死锁volatile 是什么?能够保障有序性吗?CAS?CAS 有什么缺点,如何解决? Thread 类中的start() 和 run() 办法有什么区别?Java中interrupted 和 isInterruptedd办法的区别?如何检测死锁?怎么预防死锁?死锁四个必要条件 多线程与高并发面试答案解析 多线程与高并发的关系区别“高并发和多线程”总是被一起提起,给人感觉两者如同相等,实则高并发 ≠ 多线程 1.多线程 多线程是java的个性,因为当初cpu都是多核多线程的,能够同时执行几个工作,为了进步jvm的执行效率,java提供了这种多线程的机制,以加强数据处理效率。多线程对应的是cpu,高并发对应的是拜访申请,能够用单线程解决所有拜访申请,也能够用多线程同时解决拜访申请。 在过来单CPU时代,单任务在一个工夫点只能执行繁多程序。之后倒退到多任务阶段,计算机能在同一时间点并行执行多任务或多过程。尽管并不是真正意义上的“同一时间点”,而是多个工作或过程共享一个CPU,并交由操作系统来实现多任务间对CPU的运行切换,以使得每个工作都有机会取得肯定的工夫运行。 再起初倒退到多线程技术,使得在一个程序外部能领有多个线程并行执行。一个线程的执行能够被认为是一个CPU在执行该程序。当一个程序运行在多线程下,就如同有多个CPU在同时执行该程序。 总之,多线程即能够这么了解:多线程是解决高并发的一种编程办法,即并发须要用多线程实现。 2.高并发 高并发不是JAVA的专有的货色,是语言无关的狭义的,为提供更好互联网服务而提出的概念。 典型的场景,例如:12306抢火车票,天猫双十一秒杀流动等。该状况的产生会导致系统在这段时间内执行大量操作,例如对资源的申请,数据库的操作等。如果高并发解决不好,不仅仅升高了用户的体验度(申请响应工夫过长),同时可能导致系统宕机,重大的甚至导致OOM异样,零碎进行工作等。 如果要想零碎可能适应高并发状态,则须要从各个方面进行系统优化,包含,硬件、网络、零碎架构、开发语言的选取、数据结构的使用、算法优化、数据库优化等……而多线程只是其中解决办法之一。 对于多线程与高并发的理论利用Java 高并发编程详解:多线程与架构设计 第一局部:多线程根底 次要论述 Thread 的基础知识,具体介绍线程的 API 应用、线程平安、线程间数据通信,以及如何爱护共享资源等内容,它是深刻学习多线程内容的根底。 第二局部:Java ClassLoader 引入了 ClassLoader,这是因为 ClassLoader 与线程不无关系,咱们能够通过 synchronized 关键字,或者 Lock 等显式锁的形式在代码的编写阶段对共享资源进行数据一致性爱护,那么一个 Class 在实现初始化的整个过程到后在办法区(JDK8 当前在元数据空间)其数据结构是怎么确保数据一致性的呢?这就须要对 ClassLoader 有一个比拟全面的意识和理解。第三局部:深刻了解volatile关键字 第三局部具体、深刻地介绍 volatile 关键字的语义,volatile 关键字在 Java 中十分重要,能够说它奠定了 Java 外围并发包的高效运行,在这一部分中,咱们通过实例展现了如何应用 volatile 关键字以及十分具体地介绍了 Java 内存模型等常识。 第四局部:多线程设计架构模式 ...

November 29, 2022 · 1 min · jiezi

关于多线程:Java多线程编程范式一-协作范式

前言原本本篇有个前置文章,然而有点卡文,所以本篇放大了须要的前置内容,浏览本篇须要晓得线程、线程池的概念。 Java中任意一段代码在执行的时候都在一个线程当中。 CountDownLatch 示例假如你须要在某个办法中,前面的操作你委托给了线程池进行解决,然而你心愿提交给线程池的工作处理完毕,办法才接着执行,这也就是线程相互期待: public static void main(String[] args) { // 执行main办法的是main线程 doSomeThing(); // 心愿将requestThread办法委托给线程池解决 // doThing办法在requestThread办法执行之后执行 requestThread(); doThing();}咱们就能够这么写: public class CountDownLatchDemo { /** * 举荐用ThreadPoolExecutor来创立线程池 */ private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(4); public static void main(String[] args) { doSomeThing(); // 心愿将requestThread办法委托给线程池解决 // doThing办法在requestThread办法执行之后执行 // 构造函数代表只有一个工作须要委托给线程池来实现 CountDownLatch countDownLatch = new CountDownLatch(1); EXECUTOR_SERVICE.submit(()->{ try { requestThread(); }catch (Exception e){ // 做日志输入 }finally { //countDown 办法代表实现一个工作 countDownLatch.countDown(); } }); try { // 如果requestThread办法没被执行完,count的调用次数不等于咱们在构造函数中给定的次数 // 调用此行代码的线程会陷入阻塞 countDownLatch.await(); } catch (InterruptedException e) { // 做日志输入 e.printStackTrace(); } doThing(); }} 下面这种是工作实现了接着执行上面的代码,其实咱们也能够反着用,假如在某场景下,多个线程须要期待一个线程的后果,相似于赛跑,主线程是给信号的人,其余线程是运动员: ...

October 22, 2022 · 4 min · jiezi

关于多线程:读懂多线程全靠这500多页Java并发多线程源码笔记

写在后面近年来在大厂的面试中,高并发岂但占比拟多,而且曾经不局限于并发工具的应用,更多的会深刻到底的层实现原理,这样能考查候程序员的内功,看其是否能知其所以然。对于市面上对于Java并发编程的材料感觉有些知识点不是很清晰,于是开展了对Java并发编程原理的探讨。在这收集整理了这些Java并发编程原理整顿成书籍,分享给大家。 目录因为笔记的内容太多,篇幅限度,上面只截取了局部内容展现。有想获取完整版笔记的敌人【间接点击此处】即可获取文档! 内容展现 目前市面的材料也是形形色色,很少有一套零碎的材料,如果有敌人对我下面展现的这套材料感兴趣,须要的敌人【间接点击此处】即可获取! 最初并发编程始终作为大厂考查一名 Java 开发工程师的外围指标,Java 并发通常作为互联网中高并利用设计中的根底模块,把握和了解并发的底层原理无论在工作中或面试中都大有裨益。日常开发工作中,很多人在一些业务编码的时候不会接触到高并发,即使用了很多框架也做了线程与线程之间的隔离和划分或在底层屏蔽了并发的实现,但当要开发一些根底的底层组件时,并发编程肯定是外围的难点。

October 13, 2022 · 1 min · jiezi

关于多线程:面试题多线程精华版

多线程有什么用?当初咱们的电脑的CPU都是多外围的,如果咱们还是采纳单线程的形式去编程,那么咱们电脑上的CPU外围只有一个会被利用上,其余都都会被闲置。为了进步cpu外围的利用率,咱们能够采纳多线程编程。让每个外围在同一时刻都只能有一个线程在下面运行。好比,你雇佣了4个工人,如果你只交代了一个工作,那么这四个工人中,只会有一个工人在工作,其余三个就被闲置了,是资源的节约。如果咱们同时安排四个工作,或者更多的工作,那么这四个工人就都会被利用上。也就进步了咱们的工作效率了。线程和过程有什么区别?一个线程只能属于一个过程;而一个过程能够领有多个线程(至多只有一个);线程依赖于过程而存在。过程是零碎资源分配的最小单位,而线程是CPU调度的最小单位。进行领有独立的内存单元,而多个线程共享进行的内存资源。过程编程调试简略牢靠,然而创立、切换、销毁开销大;而线程编程则恰恰相反。过程之间不会相互影响,一个过程的宕机,并不会影响到其余过程;然而一个过程中的一个线程的挂掉,可能就是导致其余线程也会挂掉。Java实现多线程有哪几种形式?继承Thread类实现多线程。实现Runnable接口实现多线程。应用ExecutorServicde、Callable、Future实现有返回值的多线程。启动线程办法start()和run()有什么区别?只有调用了start()办法,才是真正开启多线程,此时多个线程的run()体内的代码会同时执行。然而如果只是执行run(),那么定义的多个run()体内的代码还只是在同一个线程中按程序挨个执行。 一个线程的生命周期有哪几种状态?它们之间如何流转的?NEW:就是刚刚创立的线程,还没有被调用。RUNNABLE:即曾经进入能够运行的状态,或者曾经正在运行的线程。BLOCKED:还没获取到锁,被阻塞的线程。例如碰到synchronized关键字。WAITING:示意线程处于有限期待的状态,调用了Object.wait()、Thread.sleep()、Thread.join()办法,期待唤醒TIMED_WAITING:示意线程处于无限工夫期待的状态的,调用了Object.wait(long millions)、Thread.sleep(long millions)、Thread.join(long millions)办法TERMINATED:示意线程曾经执行结束了。须要留神的是,线程一旦进行RUNNABLE,就无奈回到NEW状态了。一旦进入了TERMINATED状态,就无奈回到任何其余状态了。 举荐文章:https://learn.lianglianglee.c...Java多线程sleep和wait的区别?应用方面:sleep属于Thread类的办法,而wait属于Object类的办法;sleep()能够在任意中央应用,而wait只能在同步办法或代码块中应用。CUP及锁资源开释: sleep()、wait()都会暂定以后线程,让出CPU的执行工夫。然而sleep()并不会开释锁,而wait()办法会开释锁资源。异样捕捉方面:sleep须要捕捉或抛出异样,而wait()办法则不须要。Java实现多线程的几种办法同步办法:用synchronized关键字润饰的办法。同步代码块:用synchronized关键字润饰的代码块。同步变量:应用volatile润饰的变量重入锁。

September 27, 2022 · 1 min · jiezi

关于多线程:面试题多线程并发编程

基础知识为什么要是应用并发编程晋升多核CPU的利用率,一般来说一台主机上的会有多个CPU外围,咱们能够创立多个线程,实践上讲操作系统能够将多个线程调配给不同的CPU去执行,每个CPU执行线程,这样就进步了CPU的应用效率,如果应用单线程就只能有一个CPU外围被应用。比方当咱们在网上购物时,为了晋升响应速度,须要拆分,减库存,生成订单等等这些操作。就能够进行拆分进行多线程的技术实现,面对简单业务模型,并行程序会比串行程序更适应业务需要,而并发编程更能吻合这种业务拆分。简略来说就是: 充分利用多核CPU的计算能力。不便进行业务拆分,晋升利用性能。多线程利用场景例如:迅雷多线程下载、数据库连接池、分批发送短信等等。 并发编程有什么毛病并发编程的目标就是为了能提供程序的执行效率,提供程序运行速度,然而并发编程并不总是能进步程序运行速度的,而且并发编程可能会遇到很多问题,比方:内存透露、上下文切换、线程平安、死锁等等问题。 并发编程三个必要因素是什么?原子性:即一个不可再被宰割的颗粒,操作要从头到尾一口气执行结束,不能被其余操作所中断。可见性:一个线程对共享变量的批改,另一个线程可能立即看到。有序性:程序执行的程序依照代码的先后顺序执行。(处理器可能会对指令进行重排序)Java程序中怎么保障多线程的运行平安的呢?呈现线程平安问题的起因个别都是三个起因: 线程切换带来的原子性问题;解决办法:应用多线程之间同步synchronized或者应用锁lock。缓存导致的可见性问题;解决办法:synchronized、volatile、lock,能够解决可见性问题编译优化带来的有序性问题;解决办法:happens-Before 规定能够解决有序性问题并行并发、串行有什么区别?并发:多个工作在同一个CPU核上,按细分的工夫片轮流交替执行,从宏观上看这些工作如同是同时进行似的(其实不是)并行:单位工夫内,多个处理器或多核处理器同时解决多个工作,是真正意义上的“同时进行”。串行:有n个工作,由一个线程按程序执行。因为工作、办法都在一个线程执行所以不存在线程不平安状况,也就不存在临界区的问题。做一个形象的比喻:并发 = 俩集体用一台电脑。并行 = 俩集体调配了俩台电脑。串行 = 俩集体排队应用一台电脑。多线程的益处能够进步CPU的利用率。在多线程程序中,一个线程必须期待的时候,CPU能够运行其余的线程而不是期待,这样就大大提高了程序的效率。也就是说容许单个程序创立多个并行执行的线程来实现各自的工作。 多线程的劣势线程也是程序,所以线程须要占用内存,线程越多占用内存也越多。多线程需协调和治理,所以须要CPU工夫跟踪线程线程之间对共享资源的拜访会相互影响,必须解决竞争应用共享资源的问题。线程和过程的区别线程属于过程,线程是过程的一部分。线程不能脱离过程而存在一个过程中的多个线程能够共享一部分雷同的资源,同时还领有局部本人独有的资源例如程序计数器。而过程与过程之间个别是互相独立的,不存在内存资源共享的状况。一个过程中一个线程的解体可能会影响其余线程的运行,而一个零碎中一个过程的解体,大部分状况并不会影响其余过程的长失常运行。什么是上下文切换?在咱们平时的的多线程编程中,大部分状况下,咱们的创立的线程数是大于咱们主机电脑CPU的外围数的。而CPU的一个外围同一个时刻只能执行一个线程,为了可能让其余多余的线程也能被CPU所关照到。咱们就须要让CPU轮流去指向这些多余的线程,给每个线程调配一个工夫片。当一个线程的工夫片被耗费结束后,CPU就会让这个线程进入休眠状态,让曾经进行筹备运行状态的线程取得CPU的使用权。这个从一个线程切换到另外一个线程的过程,就叫做上下文切换。 守护线程和用户线程有什么区别呢?用户过程:就是咱们平时windows电脑上前台所运行的QQ、微信程序,或者是Linux服务器上运行的Java jar服务。守护过程:而守护过程呢,则是附丽其对应的用户过程而存在的。为其用户过程而服务,随着用户过程的沦亡,而覆灭。如何在Linux 上查找哪个线程cpu利用率最高?终端执行top命令,而后shift + p找出cpu利用率最高的过程pid号(如果是排查内存占用问题,则shift+m)依据下面拿到的pid好,执行top -H -p pid号,而后持续按下shift + p,找到cpu占有率最高的线程而后将获取到的线程pid号转换为16进制,应用jstack工具,jstack pid号 > /tmp/t.dat,如jstack 21324 > /tmp/t.dat而后vim方才的文件,找到线程号对应的信息什么是线程死锁死锁是指两个或两个以上的过程(线程)在执行过程中,因为竞争资源或者因为彼此通信而造成的一种阻塞的景象,若无外力作用,它们都将无奈推动上来。此时称零碎处于死锁状态或零碎产生了死锁,这些永远在相互期待的过程(线程)称为死锁过程(线程)。多个线程同时被阻塞,它们中的一个或者全副都在期待某个资源被开释。因为线程被无限期地阻塞,因而程序不可能失常终止。

September 27, 2022 · 1 min · jiezi

关于多线程:并发开篇带你从0到1建立并发知识体系的基石

并发开篇——带你从0到1建设并发常识体系的基石前言在本篇文章当中次要跟大家介绍并发的基础知识,从最根本的问题登程层层深刻,帮忙大家理解并发常识,并且打好并发的根底,为前面深刻学习并发提供保障。本篇文章的篇章构造如下: 并发的需要咱们罕用的软件就可能会有这种需要,对于一种软件咱们可能有多重需要,程序可能一边在运行一边在后盾更新,因而在很多状况下对于一个过程或者一个工作来说可能想要同时执行两个不同的子工作,因而就须要在一个过程当中产生多个子线程,不同的线程执行不同的工作。当初的机器的CPU外围个数个别都有很多个,比方当初个别的电脑都会有4个CPU,而每一个CPU在同一个时刻都能够执行一个工作,因而为了充分利用CPU的计算资源,咱们能够让这多个CPU同时执行不同的工作,让他们同时工作起来,而不是闲暇没有事可做。 还有就是在科学计算和高性能计算畛域有这样的需要,比方矩阵计算,如果一个线程进行计算的话须要很长的工夫,那么咱们就可能应用多核的劣势,让多个CPU同时进行计算,这样一个计算工作的计算工夫就会比之前少很多,比方一个工作单线程的计算工夫为24小时,如果咱们有24个CPU外围,那么咱们的计算工作可能在1-2小时就计算实现了,能够节约十分多的工夫。并发的根底概念在并发当中最常见的两个概念就是过程和线程了,那什么是过程和线程呢? 过程简略的说来就是一个程序的执行,比如说你再windows操作系统当中双击一个程序,在linux当中在命令行执行一条命令等等,就会产生一个过程,总之过程是一个独立的主体,他能够被操作系统调度和执行。而线程必须依赖过程执行,只有在过程当中能力产生线程,当初通常会将线程称为轻量级过程(Light Weight Process)。一个过程能够产生多个线程,二者多个线程之间共享过程当中的某些数据,比方全局数据区的数据,然而线程的本地数据是不进行共享的。 你可能会听过过程是资源分配的根本单位,这句话是怎么来的呢?在下面咱们曾经提到了线程必须依赖于过程而存在,在咱们启动一个程序的时候咱们就会开启一个过程,而这个过程会像操作系统申请资源,比方内存,磁盘和CPU等等,这就是为什么操作系统是申请资源的根本单位。 你可能也听过线程是操作系统调度的根本单位。那这又是为什么呢?首先你须要明确CPU是如何工作的,首先须要明确咱们的程序会被编译成一条条的指令,而这些指令会存在在内存当中,而CPU会从内存当中一一的取出这些指令,而后CPU进行指令的执行,而一个线程通常是执行一个函数,而这个函数也是会被编译成很多指令,因而这个线程也能够被CPU执行,因为线程能够被操作系统调度,将其放到CPU上进行执行,而且没有比线程更小的能够被CPU调度的单位了,因而说线程是操作系统调度的根本单位。 Java实现并发继承Thread类public class ConcurrencyMethod1 extends Thread { @Override public void run() { // Thread.currentThread().getName() 失去以后正在执行的线程的名字 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { for (int i = 0; i < 5; i++) { // 新开启一个线程 ConcurrencyMethod1 t = new ConcurrencyMethod1(); t.start();// 启动这个线程 } }}// 某次执行输入的后果(输入的程序不肯定)Thread-0Thread-4Thread-1Thread-2Thread-3下面代码当中不同的线程须要失去CPU资源,在CPU当中被执行,而这些线程须要被操作系统调度,而后由操作系统放到不同的CPU上,最终输入不同的字符串。 应用匿名外部类实现runnable接口public class ConcurrencyMethod2 extends Thread { public static void main(String[] args) { for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { // Thread.currentThread().getName() 失去以后正在执行的线程的名字 System.out.println(Thread.currentThread().getName()); } }); thread.start(); } }}// 某次执行输入的后果(输入的程序不肯定)Thread-0Thread-1Thread-2Thread-4Thread-3当然你也能够采纳Lambda函数去实现: ...

July 18, 2022 · 4 min · jiezi

关于多线程:Java魔法类Unsafe应用解析

Java魔法类:Unsafe利用解析Unsafe是位于sun.misc包下的一个类,次要提供一些用于执行低级别、不平安操作的办法,如间接拜访零碎内存资源、自主治理内存资源等,这些办法在晋升Java运行效率、加强Java语言底层资源操作能力方面起到了很大的作用。但因为Unsafe类使Java语言领有了相似C语言指针一样操作内存空间的能力,这无疑也减少了程序产生相干指针问题的危险。在程序中适度、不正确应用Unsafe类会使得程序出错的概率变大,使得Java这种平安的语言变得不再“平安”,因而对Unsafe的应用肯定要谨慎。 注:本文对sun.misc.Unsafe公共API性能及相干利用场景进行介绍。根本介绍如下Unsafe源码所示,Unsafe类为一单例实现,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe办法的类为疏导类加载器所加载时才非法,否则抛出SecurityException异样。一:根本介绍如下Unsafe源码所示,Unsafe类为一单例实现,提供静态方法 getUnsafe获取Unsafe实例,当且仅当调用getUnsafe办法的类为疏导类加载器所加载时才非法,否则抛出SecurityException异样。 public final class Unsafe { // 单例对象 private static final Unsafe theUnsafe; private Unsafe() { } @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); // 仅在疏导类加载器`BootstrapClassLoader`加载时才非法 if(!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }}那如若想应用这个类,该如何获取其实例?有如下两个可行计划。 其一,从getUnsafe办法的应用限度条件登程,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相干办法的类A所在jar包门路追加到默认的bootstrap门路中,使得A被疏导类加载器加载,从而通过Unsafe.getUnsafe办法平安的获取Unsafe实例。 java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相干办法的类所在jar包门路其二,通过反射获取单例对象theUnsafe。 private static Unsafe reflectGetUnsafe() { try { // Unsafe类有个成员变量:private static final Unsafe theUnsafe Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { log.error(e.getMessage(), e); return null; }}或者:先获取Class对象,再通过结构器创立Unsafe对象 ...

July 15, 2022 · 2 min · jiezi

关于多线程:多线程原理和常用方法以及Thread和Runnable的区别

多线程原理随机性打印CPU有了两条执行的门路,CPU就有了抉择 ,一会执行main办法 一会执行run办法。 也能够说两个线程,一个main线程 一个run线程 一起申请CPU的执行权(执行工夫)谁抢到了就执行对应的代码 多线程内存图解main办法的第一步创建对象,创建对象开拓堆内存存储在堆内存中(地址值赋值给变量名0x11)mt.run()调用时 run办法被压栈进来 其实是一个单线程的程序(main线程,会先执行完run办法再执行主线程中的去其余办法)mt.start()调用时会开拓一个新的栈空间。执行run办法(run办法就不是在main线程执行,而是在新的栈空间执行,如果再start会再开拓一个栈空间再多一个线程)对cpu而言,cpu就有了抉择的权力 能够执行main办法、也能够执行两个run办法。 多线程益处:多线程执行时,在栈内存中,其实每一个执行线程都有一片本人所属的栈内存空间,多个线程互不影响 进行办法的压栈和弹栈。 Thread类的罕用办法获取线程名称 getName()public static void main(String[] args) { //创立Thread类的子类对象 MyThread mt = new MyThread(); //调用start办法,开启新线程,执行run办法 mt.start(); new MyThread().start(); new MyThread().start(); //链式编程 System.out.println(Thread.currentThread().getName());}/** 获取线程的名称: 1.应用Thread类中的办法getName() String getName() 返回该线程的名称。 2.能够先获取到以后正在执行的线程,应用线程中的办法getName()获取线程的名称 static Thread currentThread() 返回对以后正在执行的线程对象的援用。 * @author zjq */// 定义一个Thread类的子类public class MyThread extends Thread{ //重写Thread类中的run办法,设置线程工作 @Override public void run() { //获取线程名称 //String name = getName(); //System.out.println(name); //链式编程 System.out.println(Thread.currentThread().getName()); }}输入如下: ...

June 15, 2022 · 2 min · jiezi

关于多线程:保障线程安全的设计

目录无状态对象不可变对象线程特有对象线程特有对象可能造成的问题无状态对象有状态和无状态的区别:有状态-会存储数据、无状态-不会存储数据 对象就是操作和数据的封装(对象 = 操作 + 数据),对象所蕴含的数据就被称为该对象的状态,它蕴含对象的实例变量和动态变量中的数据,也有可能蕴含对象中援用其它变量的实例变量或动态变量。如果一个类的实例被多个线程共享不会存在共享状态,那么则称其为无状态对象。 这个类的对象是一个有状态对象class A{ Integer age;}这个类的对象中会援用其它有状态的对象所以它是一个由状态对象@Componentclass B{ @Autowire private A a; }只有操作没有状态 - 无状态class C{ public void test(){ ...... }}自身和援用都是无状态 - 无状态@Componentclass D{ @Autowire private C c;}一个类即便不存在实例变量或动态变量依然可能存在共享状态,如下 enum Singleton{ INSTANCE; private EnumSingleton singletonInstance; Singleton(){ singletonInstance = new EnumSingleton(); } public EnumSingleton getInstance(){ //对singletonInstance做一些配置操作 //例如 singletonInstance.num++; return singletonInstance; }}class SingletonTest{ public void test(){ Singleton st = Singleton.INSTANCE; st.getInstance(); }}enum的INSTANCE只会被实例化一次,所以如果没有正文所对应的操作,这就是一个完满的单例,且其它类对它的援用都是无状态的。然而如果在getInstance办法中对singletonInstance变量有所操作,那么在多线程环境中singletonInstance会呈现线程平安问题。上面的例子这个问题更显著,num变量就是一个共享状态的变量。 enum Singleton{ INSTANCE; private int num; public int doSomething(){ num++; return num; }}class SingletonTest{ public void test(){ Singleton st = Singleton.INSTANCE; st.doSomething(); }}还有一种状况是动态变量的应用,动态变量与类(class)间接关联,不会随着实例的创立而扭转,所以当Test没有实例变量和动态变量的状况下,在办法中通过类名间接操作动态变量,依然会造成线程平安问题,即Test中存在共享状态变量的调用。 ...

May 7, 2022 · 2 min · jiezi

关于多线程:多线程学习第七课

1、AQS1、概念:AQS 核心思想是,如果被申请的共享资源闲暇,则将以后申请资源的线程设置为无效的工作线程,并且将共享资源设置为锁定状态。如果被申请的共享资源被占用,那么就须要一套线程阻塞期待以及被唤醒 时锁调配的机制,这个机制 AQS 是用 CLH 队列锁实现的,行将临时获取不到锁的线程退出到队列中。2、简略的AQS图解: 3、实现案例 4、AQS 应用一个 int 成员变量state来示意同步状态,这个成员变量是volatitlex润饰的,通过内置的 FIFO 队列来实现获取资源线程的排队工作。AQS 应用 CAS 对该同步状态进行原子操作实现对其值的批改。// 定义共享资源状态private volatile int state;// 获取共享资源的状态protected final int getState() { return state; }// 设置共享资源的状态protected final void setState(int newState) { state = newState; }// CAS竞争stateprotected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }以ReentrantLock为例,state初始化为0,示意未锁定状态。A线程lock时,会调用tryAcquire独占该锁并将state+1。尔后,其余线程再tryAcquire时就会失败,直到A线程unlock到state=0(即开释锁)为止,其它线程才有机会获取该锁。当然,开释锁之前,A线程本人是能够反复获取此锁的(state会累加),这就是可重入的概念。但要留神,获取多少次就要开释如许次,这样能力保障state是能回到零态的。

May 5, 2022 · 1 min · jiezi

关于多线程:Java并发编程系列之三JUC概述

上篇文章为解决多线程中呈现的同步问题引入了锁的概念,上篇文章介绍的是Synchronized关键字锁,本篇文章介绍更加轻量级的锁Lock接口及引出JUC的相干常识。 本文不力争阐释分明JUC框架的所有内容,而是站在肯定的高度下,理解Juc下包的设计与实现。 [TOC] 一、LOCK锁概述实现同步的另外一种形式是Lock锁。 Lock锁是一个接口,其所有的实现类为: ReentrantLock(可重入锁)ReentrantReadWriteLock.ReadLock(可重入读写锁中的读锁)ReentrantReadWriteLock.WriteLock(可重入读写锁中的写锁)与synchronized不同的是,应用LOCK锁与其有六个区别 1 synchronized 是Java内置关键字,Lock 是一个接口 2 synchronized 无奈判断是否获取锁,Lock 能够判断是否获取锁 3 synchronized 能够主动开释锁,Lock 必须手动开释锁,如果不开释就会造成死锁 4 同一个锁对象,线程A synchronized获取之后,线程B只能期待,造成阻塞,Lock 并不会期待 5 synchronized 可重入锁,不可中断,非偏心锁,Lock 可重入锁,能够判断,非偏心锁(可设置) 6 synchronized 适宜大量同步代码,Lock 适宜大量同步代码Lock接口位于 java.util.concurrent.locks 包下,在父级的包下有两个包。 java.util.concurrent:包下次要是包含并发相干的接口与类,阻塞队列、线程池等,外面蕴含 59 个类或者接口java.util.concurrent.atomic: 该包下次要包含原子性操作相干的类,比方罕用的AtomicInteger、AtomicBoolean、AtomicIntegerArry等,外面蕴含18个类或者接口其中父级的包java.util .concurrent波及到Java多线程最重要的一部分——JUC编程。 二、JUC概述JUC就是java.util .concurrent工具包的简称。这是一个解决线程的工具包,JDK 1.5开始呈现的,在此包中减少了在并发编程中很罕用的工具类。 用于定义相似于线程的自定义子系统,包含线程池,异步 IO 和轻量工作框架; 还提供了设计用于多线程上下文中的 Collection 实现等; 下图为JUC波及到的所有知识点。 JUC应该包含五个局部的内容。 1、Lock框架① 接口: ConditionCondition为接口类型,它将 Object 监视器办法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合应用,为每个对象提供多个期待 set (wait-set)。其中,Lock 代替了 synchronized 办法和语句的应用,Condition 代替了 Object 监视器办法的应用。能够通过await(),signal()来休眠/唤醒线程。②接口: LockLock为接口类型,Lock实现提供了比应用synchronized办法和语句可取得的更宽泛的锁定操作。此实现容许更灵便的构造,能够具备差异很大的属性,能够反对多个相干的Condition对象。③接口: ReadWriteLockReadWriteLock为接口类型, 保护了一对相干的锁,一个用于只读操作,另一个用于写入操作。只有没有 writer,读取锁能够由多个 reader 线程同时放弃。写入锁是独占的。④抽象类: AbstractOwnableSynchonizerAbstractOwnableSynchonizer为抽象类,能够由线程以独占形式领有的同步器。此类为创立锁和相干同步器(随同着所有权的概念)提供了根底。AbstractOwnableSynchronizer 类自身不治理或应用此信息。然而,子类和工具能够应用适当保护的值帮忙管制和监督拜访以及提供诊断。⑤抽象类(long): AbstractQueuedLongSynchronizerAbstractQueuedLongSynchronizer为抽象类,以 long 模式保护同步状态的一个 AbstractQueuedSynchronizer 版本。此类具备的构造、属性和办法与 AbstractQueuedSynchronizer 完全相同,但所有与状态相干的参数和后果都定义为 long 而不是 int。当创立须要 64 位状态的多级别锁和屏障等同步器时,此类很有用。⑥ 外围抽象类(int): AbstractQueuedSynchonizerAbstractQueuedSynchonizer为抽象类,其为实现依赖于先进先出 (FIFO) 期待队列的阻塞锁和相干同步器(信号量、事件,等等)提供一个框架。此类的设计指标是成为依附单个原子 int 值来示意状态的大多数同步器的一个有用根底。⑦锁罕用类: LockSupportLockSupport为罕用类,用来创立锁和其余同步类的根本线程阻塞原语。LockSupport的性能和"Thread中的 Thread.suspend()和Thread.resume()有点相似",LockSupport中的park() 和 unpark() 的作用别离是阻塞线程和解除阻塞线程。然而park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。⑧锁罕用类: ReentrantLockReentrantLock为罕用类,它是一个可重入的互斥锁 Lock,它具备与应用 synchronized 办法和语句所拜访的隐式监视器锁雷同的一些根本行为和语义,但性能更弱小。⑨锁罕用类: ReentrantReadWriteLockReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包含Lock子类ReadLock和WriteLock。ReadLock是共享锁,WriteLock是独占锁。⑩锁罕用类: StampedLock它是java8在java.util.concurrent.locks新增的一个API。StampedLock管制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个局部组成,锁获取办法返回一个数字作为票据stamp,它用相应的锁状态示意并管制拜访,数字0示意没有写锁被受权拜访。在读锁上分为乐观锁和乐观锁。2、Tools类①工具罕用类: CountDownLatchCountDownLatch为罕用类,它是一个同步辅助类,在实现一组正在其余线程中执行的操作之前,它容许一个或多个线程始终期待。②具罕用类: CyclicBarrierCyclicBarrier为罕用类,其是一个同步辅助类,它容许一组线程相互期待,直到达到某个公共屏障点 (common barrier point)。在波及一组固定大小的线程的程序中,这些线程必须不断地相互期待,此时 CyclicBarrier 很有用。因为该 barrier 在开释期待线程后能够重用,所以称它为循环 的 barrier。)③工具罕用类: PhaserPhaser是JDK 7新增的一个同步辅助类,它能够实现CyclicBarrier和CountDownLatch相似的性能,而且它反对对工作的动静调整,并反对分层构造来达到更高的吞吐量。④ 工具罕用类: SemaphoreSemaphore为罕用类,其是一个计数信号量,从概念上讲,信号量保护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),而后再获取该许可。每个 release() 增加一个许可,从而可能开释一个正在阻塞的获取者。然而,不应用理论的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的口头。通常用于限度能够拜访某些资源(物理或逻辑的)的线程数目。⑤工具罕用类: ExchangerExchanger是用于线程合作的工具类, 次要用于两个线程之间的数据交换。它提供一个同步点,在这个同步点,两个线程能够替换彼此的数据。这两个线程通过exchange()办法替换数据,当一个线程先执行exchange()办法后,它会始终期待第二个线程也执行exchange()办法,当这两个线程达到同步点时,这两个线程就能够替换数据了。3、Collections: 并发汇合并发汇合的类构造关系 ...

April 15, 2022 · 2 min · jiezi

关于多线程:多线程学习第六课

1、对于伪共享1.1、回顾:1、在第三课咱们就学习了多核CPU是如何解决“本地高速缓存”与“共享内存“一致性问题,其中就学了一个重要的概念:Cache Line。2、《Java并发编程的艺术》对于Cache Line的解释:缓存中能够调配的最小存储单位。处理器填写缓存线时会加载整个缓存线,须要应用多个主内存读周期。3、关键字:“会加载整个缓存线”。1.2、伪共享是什么? a、名词:一个缓存行中可能存有多个变量,当多个线程同时批改一个缓存行外面的多个变量时,因为同时只能有一个线程操作缓存行,所以相比将每个变量放到一个缓存行,性能会有所降落,这就是伪共享。b、解释:如上图,每个Cache Line中都可能存在多个变量,这就是下面说的“会加载整个缓存线”。当一个缓存行中的变量存在资源竞争,其余线程拜访其余变量会呈现阻塞的状况,能够了解为多个线程相互阻塞,使效率显著降落。

April 11, 2022 · 1 min · jiezi

关于多线程:Synchronized同步锁

Synchronized 介绍Synchronized 是由JVM实现的一种内置锁,锁的获取和开释都是由JVM隐式实现。Synchronized 是基于底层操作系统的 Mutex Lock 实现的,每次获取和开释锁操作都会带来用户态和内核态的切换,从而减少零碎性能开销。 Synchronized 实现原理通常 Synchronized 实现同步锁的形式有两种,一种是润饰办法,一种是润饰办法块。以下就是通过 Synchronized 实现的两种同步办法加锁的形式: // 关键字在实例办法上,锁为以后实例 public synchronized void method1() { // code } // 关键字在代码块上,锁为括号外面的对象 public void method2() { Object o = new Object(); synchronized (o) { // code } }通过反编译能够看到具体字节码的实现,运行以下反编译命令,就能够输入咱们想要的字节码: javac -encoding UTF-8 SyncTest.java //先运行编译class文件命令javap -v SyncTest.class //再通过javap打印出字节文件通过输入的字节码,你会发现:Synchronized 在润饰同步代码块时,是由 monitorenter 和 monitorexit 指令来实现同步的。进入 monitorenter 指令后,线程将持有 Monitor 对象,退出 monitorenter 指令后,线程将开释该 Monitor 对象。 public void method2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: new #2 3: dup 4: invokespecial #1 7: astore_1 8: aload_1 9: dup 10: astore_2 11: monitorenter //monitorenter 指令 12: aload_2 13: monitorexit //monitorexit 指令 14: goto 22 17: astore_3 18: aload_2 19: monitorexit 20: aload_3 21: athrow 22: return Exception table: from to target type 12 14 17 any 17 20 17 any LineNumberTable: line 18: 0 line 19: 8 line 21: 12 line 22: 22 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 17 locals = [ class com/demo/io/SyncTest, class java/lang/Object, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4再来看以下同步办法的字节码,你会发现:当 Synchronized 润饰同步办法时,并没有发现 monitorenter 和 monitorexit 指令,而是呈现了一个 ACC_SYNCHRONIZED 标记。这是因为 JVM 应用了 ACC_SYNCHRONIZED 拜访标记来辨别一个办法是否是同步办法。 ...

April 9, 2022 · 3 min · jiezi

关于多线程:多线程学习第三课

1、Volatile 实现过程 a、Volatile润饰的变量,会生成Lock前缀的操作指令,Lock前缀指令会引起处理器缓存回写到内存。b、当CPU 1批改volatile变量,零碎内存中值和其余CPU中的缓存生效是即时性的。c、红色箭头的嗅探操作:嗅探一个处理器来检测其余处理器打算写内存地址,而这个地址以后处于共享状态,那么正在嗅探的处理器将使它的缓存行有效,在下次访问雷同内存地址时,强制执行缓存行填充。(相似一个Listener操作)

March 16, 2022 · 1 min · jiezi

关于多线程:多线程学习第二课

1、 思考题a、sleep和wait的差异 答:1)最次要是sleep办法没有开释锁,而wait办法开释了锁,使得其余线程能够应用同步控制块或者办法(锁代码块和办法锁)。 2)wait是Object办法,sleep是Thread特有的办法b、为什么说线程启动后不肯定会立即执行 答:须要期待CPU的调度,如果CPU没有闲暇内核,那线程将期待执行。c、应用wait和notify的时候 须要有做些什么 答:必须在同步办法中应用wait和notify,因为执行wait和notify的前提条件是必须持有同步办法的monitor的所有权,运行上面任何一个办法都会抛出非法monitor状态异样IllegalMonitorStateException2、线程有哪些办法 1)currentThread 获取以后线程 2)isAlive 是否存活 3)sleep 暂停以后线程执行 4)getId 获取以后线程的惟一标识 5)interrupt 终止以后线程执行 6)this.interrupted 判断以后线程是否中断,具备分明中断状态的作用 7)this.isInterrupted 判断Thread线程是否中断 8)suspend/resume 暂停和复原线程 8)yield 放弃以后cpu资源,将它让给其余工作 9)setPriority 设置线程执行的优先级 1-10 10)join 办法只会使主线程(或者说调用t.join()的线程)进入期待池并期待t线程执行结束后才会被唤醒。并不影响同一时刻处在运行状态的其余线程。3、线程的状态转换 4、课后作业1、给一个文件文件外面有10万条记录每一行是一个0-100000之间的随机数 咱们通过启动一个线程 来统计下这个文件外面大于 80000 数字有多少个。 并统计下耗时 public static void main(String[] args) { List<Integer> data = getData(); Thread t1 = new Thread(new Runnable(){ @Override public void run() { System.out.println("开始统计......"); long start= System.currentTimeMillis(); Integer count = 0; for (Integer item : data) { if (item >= 80000) { count ++; } } long end =System.currentTimeMillis(); System.out.println("统计完结,耗时:" + (end-start) + "毫秒"); System.out.println("统计后果count=" +count); } }); t1.start(); } /** * 获取 10万个0-100000之间的随机数 * @author yy.xiong **/ private static List<Integer> getData(){ Integer min = 0; Integer max = 100000; List<Integer> data = Lists.newArrayList(); for (int i=1; i<=100000; i++){ int item = (int)(min+Math.random()*max); data.add(item); } return data; } // 执行后果: 开始统计...... 统计完结,耗时:5毫秒 统计后果count=20056 Process finished with exit code 02、用wait和notify 的例子 启动两个线程 :1号线程 打印【 步骤1】 而后进入期待 期待完结打印 【步骤4】 2 号线程打印【 步骤2】 而后sleep 3秒 而后打印【 步骤3】而后唤醒1号线程 ...

March 14, 2022 · 1 min · jiezi

关于多线程:ThreadPoolExecutor构造方法的七个参数和拒绝策略详解

为什么须要调用ThreadPoolExecutor构造方法去创立线程池,因为阿里巴巴开发手册如是说: 那咱们先理解下ThreadPoolExecutor构造方法的七个参数代表什么。ThreadPoolExecutor类的源码如下: /** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }其实源码中办法上的正文曾经很分明了(浏览源码正文是个很好的习惯),咱们这里简略翻译下:corePoolSize: 外围线程数,即始终保留在线程池当中的线程数量,即便没有工作须要解决。maximumPoolSize: 最大线程数,以后线程池中容许创立的最大线程数。keepAliveTime:存活工夫,大于外围线程数的线程期待新工作的最长工夫,这个工夫内没有新工作须要解决,则销毁这个线程unit:存活工夫的单位,TumeUnit类,有NANOSECONDS,MICROSECONDS,MILLISECONDS,SECONDS,MINUTES,HOURS,DAYS选项workQueue:工作队列。用于在执行工作之前保留工作的队列。 此队列将仅保留由 execute 办法提交的 Runnable 工作。当有新工作来时,首先判断沉闷的线程是否小于外围线程数,如果小于,则新增线程直到等于外围线程数,再后续则判断当前任务队列是否曾经满员,如果未满,退出队列等待执行,如果已满则开启新的线程。如果线程数达到最大线程数,则执行回绝策略(第七个参数)threadFactory:执行器创立新线程时应用的工厂,默认DefaultThreadFactoryhandler:回绝策略RejectedExecutionHandler。默认AbortPolicy咱们能够看到实现RejectedExecutionHandler的实现类有四个:见名知意咱们简略了解下:AbortPolicy:停止策略,针对满员后的新工作间接抛出RejectedExecutionException异样CallerRunsPolicy: 由以后线程次所在的线程去执行被回绝的新工作。DiscardPolicy:抛弃策略,空办法什么都不干,等于间接疏忽了新工作,不执行然而也不抛出异样。DiscardOldestPolicy:抛弃最早的策略。当工作被回绝是,会摈弃工作队列中最旧的工作也就是最先退出队列的,再把这个新工作增加进去。e.getQueue().poll(); ...

March 8, 2022 · 4 min · jiezi

关于多线程:多线程学习第一课

1、课程三问 - 学前:a、为什么须要多线程? 同一个过程外部可能存在多个不同的task,这些task须要共享过程的数据,然而这些task操作的数据又有肯定的独立性,不须要多个task依照时许执行,因而就产生了线程的概念。b、多线程是什么 多线程是绝对于过程说的,多个线程独特组成了过程,线程是过程的一个个粒度单元,他们彼此独立。c、多线程有什么劣势、带来什么问题 1、长处 因为多线程之间不须要依照时许进行,所以多线程能够升高工夫复杂度。反对并发编程。 2、毛病 线程平安问题、频繁的上下文切换。2、学习中纳闷点:a、线程是否是最小粒度?b、频繁的上下文切换带来了什么问题?c、多线程是否肯定比过程快? 3、解惑:a、线程是否是最小粒度? 答:起初想了想,这个问题还挺傻的。因为子线程也能够领有本人的子线程,那线程应该是过程的最小粒度。上图三个线程独特组成了一个过程,Thread 1也能够有本人的子线程。b、频繁的上下文切换带来了什么问题? 答:如上图:1->2->3->4->5,形容了一个线程a切换到另外一个线程b的过程。如果两个线程独特拜访了并批改了过程公共数据变量x,那在a批改了x变量后,还须要同步到b线程的公有缓存L1/L2。频繁的切换,这就带来了缓存一致性问题。痛死切换是CPU内核的切换,也就带来了性能的耗费。所以上下文切换是一个拿性能换取工夫的过程。c、多线程是否肯定比过程快? 答:不是的,如果一个过程只有很少的过程数,那可能上下文切换的工夫大于单线程执行的工夫了。4、总结:a、多线程进步了多核cpu的利用率,同时带来了性能的耗费以及线程平安、资源竞争、死锁等问题。b、线程是cpu内核调度的最小单元。

March 7, 2022 · 1 min · jiezi

关于多线程:线程池基本介绍与使用

线程池根本介绍与应用咱们晓得,在Java中,创建对象,仅仅是在 JVM 的堆里调配一块内存而已;而创立一个线程,却须要调用操作系统内核的 API,而后操作系统要为线程调配一系列的资源,这个老本就很高了,所以线程是一个重量级的对象,应该防止频繁创立和销毁。 所以Java中提供了线程池,其本质就是一个包容多个线程的容器,其中的线程能够重复应用,省去了频繁创立线程对象的操作,无需重复创立线程而耗费过多资源。 JDK线程池相干类JDK中提供的无关线程池的相干类及其关系如下图: Executor接口:线程池的形象接口,只蕴含一个execute办法。ExecutorService子接口:提供了无关终止线程池和Future返回值的一些办法。AbstractExecutorService抽象类:提供了ExecutorService的一些默认实现。ThreadPoolExecutor类:JDK提供的线程池的实现类。Executors类:线程池工厂类,提供了几种线程池的工厂办法。上面次要介绍JDK提供的线程池实现类 - ThreadPoolExecutor类。 ThreadPoolExecutor类Java 提供的线程池相干的工具类中,最外围的是 ThreadPoolExecutor。 构造方法ThreadPoolExecutor 的构造函数非常复杂,如上面代码所示,这个最齐备的构造函数有 7 个参数。 ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 上面一一介绍下参数的含意。 corePoolSize默认状况下,在创立了线程池后,线程池中的线程数为0,当有工作来之后,才会去创立一个线程来执行工作当线程池中的线程数目达到corePoolSize后,就进行线程创立,转而会把工作放到工作队列当中期待。调用prestartAllCoreThreads()或者prestartCoreThread()办法,能够预创立线程,即在没有工作到来之前就创立corePoolSize个线程或者一个线程maxPoolSize当线程数大于或等于外围线程,且工作队列已满时,线程池会创立新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且工作队列已满,则已超出线程池的解决能力,线程池会回绝解决工作而抛出异样。keepAliveTime & unit当线程闲暇工夫达到keepAliveTime,单位unit时,该线程会退出,直到线程数量等于corePoolSize。 workQueue工作队列,一个阻塞队列,用来存储期待执行的工作,倡议workQueue不要应用无界队列,尽量应用有界队列。防止大量工作期待,造成OOM。反对有界的阻塞队列有ArrayBlockingQueue 和 LinkedBlockingQueue。 threadFactory线程工厂,通过这个参数你能够自定义如何创立线程,例如你能够给线程指定一个有意义的名字。 handler回绝策略。如果线程池中所有的线程都在繁忙,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交工作,线程池就会回绝接管,能够通过 handler 这个参数来指定回绝的策略。ThreadPoolExecutor 曾经提供了以下 4 种策略: ThreadPoolExecutor.AbortPolicy:默认的回绝策略。抛弃工作并抛出RejectedExecutionException异样。ThreadPoolExecutor.DiscardPolicy:抛弃工作,然而不抛出异样。ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列期待最久的工作,而后从新尝试执行工作(反复此过程)ThreadPoolExecutor.CallerRunsPolicy:间接在execute办法的调用线程中运行被回绝的工作。罕用办法提交工作办法 void execute(Runnable command):提交工作给线程池执行,工作无返回值Future<?> submit(Runnable task):因为Runnable接口没有返回值,所以Future返回值执行get()办法返回值为null,作用只是期待,相似于join<T> Future<T> submit(Runnable task, T result):因为Runnable没有返回值,所以额定提供了一个参数,作为返回值。<T> Future<T> submit(Callable<T> task):提交工作给线程池执行,可能返回执行后果。其余办法 void allowCoreThreadTimeOut(boolean value):是否容许外围线程超时,默认false。shutdown():敞开线程池,期待工作都执行完shutdownNow():敞开线程池,不期待工作执行完,并返回期待执行的工作列表。getTaskCount():线程池已执行和未执行的工作总数getCompletedTaskCount():已实现的工作数量getPoolSize():线程池以后的线程数量getActiveCount():以后线程池中正在执行工作的线程数量线程池工作的执行流程线程池工作的个别执行流程图如下图所示: 线程池初始化示例上面是一个线程池初始化的示例,仅供参考 // 初始化示例private static final ThreadPoolExecutor pool;static { ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("po-detail-pool-%d").build(); pool = new ThreadPoolExecutor( 4, 8, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(512), threadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.allowCoreThreadTimeOut(true);}初始化参数含意解释: ...

March 7, 2022 · 1 min · jiezi

关于多线程:多线程并行并发线程安全一文解

目录1、什么是线程、多线程、并行、并发?2、为什么应用多线程?3、怎么创立线程?4、怎么保障线程平安?5、线程如何调度的?6、线程分类?7、其它一、什么是线程、多线程?首先咱们先理解下,程序、过程:程序:是一组计算机能辨认和执行的指令,运行于电子计算机上,满足人们某种需要的信息化工具。过程:正在运行的一个应用程序,是一个动静的过程,有它本身的产生,存在和沦亡的过程。(领有独立的内存空间)(例如:运行一个word.exe软件,就是一个过程)线程:过程可进一步细化为线程,是一个程序外部的一条执行办法,有本人独立的工作内存(栈)和共享内存(堆)。(从宏观角度上了解线程是并行运行的,然而从宏观角度上剖析却是串行运行的,即一个线程一个线程的去运行,当零碎只有一个CPU时,线程会以某种程序执行多个线程,咱们把这种状况称之为线程调度。工夫片即CPU调配给各个程序的运行工夫(很小的概念))多线程:指的是这个过程运行时产生了不止一个线程。并行:指两个或多个事件在同一时刻点产生。(计算机系统中有多个CPU,则这些能够并发执行的程序便可被调配到多个处理器上,实现多任务并行执行,即利用每个处理器来解决一个可并发执行的程序,这样,多个程序便能够同时执行,因为是宏观的,所以大家在应用电脑的时候感觉就是多个程序是同时执行的。)并发:指两个或多个事件在同一时间段内产生。(在单CPU零碎中,每一时刻却仅能有一道程序执行(工夫片),故宏观上这些程序只能是分时地交替执行。)二、为什么要应用多线程?用线程只有一个目标,那就是更好的利用cpu的资源,因为所有的多线程代码都能够用单线程来实现。三、怎么创立线程?1、继承Thread类步骤:1):定义一个类A继承于java.lang.Thread类.2):在A类中笼罩Thread类中的run办法.3):咱们在run办法中编写须要执行的操作---->run办法里的,线程执行体.4):在main办法(线程)中,创立线程对象,并启动线程.留神:启动线程,必须用start()办法,这样才能够创立线程,不可间接调用run()办法,这样无奈创立线程,只是你在main办法调用了run()办法,都只是main线程执行,没有创立新线程。生产者与消费者例子(继承Thread版): package com.example.gxw.Thread; import android.view.Window; /** * * 创立三个窗口卖票,总票数为100张,应用继承自Thread形式 * 用动态变量保障三个线程的数据独一份 * * 存在线程的平安问题,有待解决 * * */ public class ThreadDemo extends Thread{ public static void main(String[] args){ window t1 = new window(); window t2 = new window(); window t3 = new window(); t1.setName("售票口1"); t2.setName("售票口2"); t3.setName("售票口3"); t1.start(); t2.start(); t3.start(); } } class window extends Thread{ //将其加载在类的动态区,所有线程共享该动态变量 private static int ticket = 100; @Override public void run() { while(true){ if(ticket>0){ System.out.println(getName()+"以后售出第"+ticket+"张票"); ticket--; }else{ break; } } } }2、实现Runnable接口步骤:1):定义一个类A实现于java.lang.Runnable接口,留神A类不是线程类.2):在A类中笼罩Runnable接口中的run办法.3):咱们在run办法中编写须要执行的操作(run办法里的,线程执行体).4):在main办法(线程)中,创立线程对象, 并将A实现类做参传给线程结构器,而后通过start()办法启动线程.生产者与消费者例子(实现Runnable版): ...

February 19, 2022 · 3 min · jiezi

关于多线程:Spring的Async注解实现异步方法

@Async注解Spring3开始提供了@Async注解,该注解能够标注在办法或者类上,从而能够不便的实现办法的异步调用。调用者在调用异步办法时将立刻返回,办法的理论执行将提交给指定的线程池中的线程执行。 @Async注意事项: @Async标注在类上时,示意该类的所有办法都是异步办法。@Async注解的办法肯定要通过依赖注入调用(因为要通过代理对象调用),不能间接通过this对象调用,否则不失效。@Async应用示例1、Spring中启用@Async创立一个配置类,并加上@EnableAsync注解即可 @Configuration@EnableAsyncpublic class SpringAsyncConfig{}2、应用@Async创立异步办法执行类 @Servicepublic class AsyncService { // 无返回值的异步办法 @Async public void noReturnMethod() { String tName = Thread.currentThread().getName(); System.out.println("current thread name : " + tName); System.out.println("noReturnMethod end"); } // 有返回值的异步办法 @Async public Future<String> withReturnMethod() { String tName = Thread.currentThread().getName(); System.out.println("current thread name : " + tName); return new AsyncResult<>("aaa"); }}创立调用异步办法的类 @RestController@RequestMapping("/api/async/test/")public class AsyncController { @Autowired AsyncService asyncService; // 无返回值 @GetMapping("/noReturn") public String noReturn() { asyncService.noReturnMethod(); return "success"; } // 有返回值 @GetMapping("/withReturn") public String withReturn() { Future<String> future = asyncService.withReturnMethod(); try { String res = future.get();// 阻塞获取返回值 System.out.println("res = " + res); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } return "success"; }}Spring定义的线程池类Spring 曾经定义的线程池类有如下一些: ...

February 9, 2022 · 2 min · jiezi

关于多线程:进程通信方式

https://cloud.tencent.com/dev...过程通信: 每个过程各自有不同的用户地址空间,任何一个过程的全局变量在另一个过程中都看不到,所以过程之间要替换数据必须通过内核,在内核中开拓一块缓冲区,过程A把数据从用户空间拷到内核缓冲区,过程B再从内核缓冲区把数据读走,内核提供的这种机制称为过程间通信。1.匿名管道通信匿名管道( pipe ):管道是一种半双工的通信形式,数据只能单向流动,而且只能在具备亲缘关系的过程间应用。过程的亲缘关系通常是指父子过程关系。2.通过匿名管道实现过程间通信的步骤如下: 父过程创立管道,失去两个⽂件描述符指向管道的两端父过程fork出子过程,⼦过程也有两个⽂件描述符指向同⼀管道。父过程敞开fd[0],子过程敞开fd[1],即⽗过程敞开管道读端,⼦过程敞开管道写端(因为管道只反对单向通信)。⽗过程能够往管道⾥写,⼦过程能够从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了过程间通信。具体可参考文章:过程间的通信形式——pipe(管道)2 高级管道通信高级管道(popen):将另一个程序当做一个新的过程在以后程序过程中启动,则它算是以后程序的子过程,这种形式咱们成为高级管道形式。3 有名管道通信有名管道 (named pipe) : 有名管道也是半双工的通信形式,然而它容许无亲缘关系过程间的通信。4 音讯队列通信音讯队列( message queue ) : 音讯队列是由音讯的链表,寄存在内核中并由音讯队列标识符标识。音讯队列克服了信号传递信息少、管道只能承载无格局字节流以及缓冲区大小受限等毛病。5 信号量通信信号量( semophore ) : 信号量是一个计数器,能够用来管制多个过程对共享资源的拜访。它常作为一种锁机制,避免某过程正在访问共享资源时,其余过程也拜访该资源。因而,次要作为过程间以及同一过程内不同线程之间的同步伎俩。6 信号信号 ( sinal ) : 信号是一种比较复杂的通信形式,用于告诉接管过程某个事件曾经产生。7 共享内存通信共享内存( shared memory ) :共享内存就是映射一段能被其余过程所拜访的内存,这段共享内存由一个过程创立,但多个过程都能够拜访。共享内存是最快的 IPC 形式,它是针对其余过程间通信形式运行效率低而专门设计的。它往往与其余通信机制,如信号两,配合应用,来实现过程间的同步和通信。8 套接字通信套接字( socket ) : 套接口也是一种过程间通信机制,与其余通信机制不同的是,它可用于不同机器间的过程通信。

January 17, 2022 · 1 min · jiezi

关于多线程:The-art-of-multipropcessor-programming-读书笔记硬件基础2

本系列是 The art of multipropcessor programming 的读书笔记,在原版图书的根底上,联合 OpenJDK 11 以上的版本的代码进行了解和实现。并依据集体的查资料以及了解的经验,给各位想更深刻了解的人分享一些集体的材料硬件根底处理器和线程(processors and threads)多处理器(multiprocessor)包含多个硬件处理器,每个都能执行一个顺序程序。当探讨多处理器架构的时候,根本的工夫单位是指令周期(cycle):即处理器提取和执行一条指令须要的工夫。 线程是一个顺序程序,是一个软件形象。上下文切换(context switch)指的是处理器能够执行一个线程一段时间之后去执行另一个线程。处理器能够因为各种起因撤销一个线程或者从调度中删除该线程: 线程收回了一个内存申请,而该申请须要一段时间能力实现线程曾经运行了足够长的工夫,该让别的线程执行了。当线程被从调度中删除时,他可能从新在另一个处理器上执行。 互连线(interconnect)目前常见的三种服务器根本互联构造: SMP(symmetric multiprocessing,对称多解决)NUMA(nonuniform memory access,非统一内存拜访)SMP 指多个 CPU 对称工作,无主次或从属关系。各 CPU 共享雷同的物理内存。每个 CPU 拜访内存中的任何地址所需工夫是雷同的,因而 SMP 也被称为统一存储器拜访构造(即 UMA:Uniform Memory Access)。个别 SMP 架构中,CPU 和内存之间存在高速缓存。并且,处理器和主存都有用来负责发送和监听总线上播送信息的总线管制单元(bus controller)。整体构造如下图所示: 这种构造最为容易实现,然而随着处理器的增多,总线并不能扩大导致总线终将过载。 在 NUMA 系统结构中,与 SMP 相同,一系列节点通过点对点网络相互连贯,有点像一个小型的局域网,每个节点蕴含若干个处理器和本地内存。一个节点的本地存储对于其余节点也是能够拜访的,当然,拜访本人的本地内存要快于拜访其余节点的内存。网络比总线简单,须要更加简单的协定,然而带来了扩展性。如下图所示: 从程序员的角度看,无论底层是 SMP 还是 NUMA,互连线都是无限的资源。写代码的时候,要思考这一点防止应用过多的互联线资源。 内存(memory)所有处理器共享内存,通常会被形象成为一个很大的“字”(words)数组,数组下标即为地址(address)。字长度和平台相干,当初多为 64 位,地址的最大长度也是这么长。64 位能示意的内存就曾经很大了。 处理器拜访内存的流程,简略概括包含: 处理器通过给内存发送一个蕴含要读取的地址的音讯,来获取内存上对应地址的值处理器通过给内存发送一个蕴含要写入的地址和值的音讯,数据写入后,内存回复一个确认音讯。高速缓存(Cache)缓存命中率如果处理器始终间接从内存中读取,处理器间接拜访内存耗费工夫很长,可能须要几百个指令周期,这样效率会很低。个别须要引入若干个高速缓存(Cache):与处理器紧挨着的小型存储器,位于处理器和内存之间。 当须要读取一个地址的值时,拜访高速缓存看是否存在:存在代表命中(hit),间接读取。不存在被称为缺失(miss)。同样的,如果须要写一个值到一个地址,这个地址在缓存中存在也就不须要拜访内存了。 咱们个别比较关心高速缓存中命中的申请比例,也就是缓存命中率 局部性与缓存行大部分程序都体现出较高的局部性(locality): 如果处理器读或写一个内存地址,那么它很可能很快还会读或写同一个地址。如果处理器读或写一个内存地址,那么它很可能很快还会读或写左近的地址。针对局部性,高速缓存个别会一次操作不止一个字,而是一组邻近的字,称为缓存行。 多级高速缓存古代处理器中个别不止一级缓存,而是多级缓存,从离处理器最近到最远别离是 L1 Cache,L2 Cache 和 L3 Cache: L1 Cache 通常和处理器位于同一个芯片,离处理器最近,拜访仅须要 1~3 个指令周期L2 Cache 通常和处理器位于同一个芯片,处于边缓地位,拜访须要通过更远的铜线,甚至更多的电路,从而减少了延时,个别在 8 ~ 11 个指令周期左右L3 Cache L1/L2 为每个处理器公有的,这样导致对于很多雷同的数据,也只能每个处理器独有的缓存各保留一份。所以须要思考引入一个所有处理器共用的缓存,这就是 L3 缓存。L3 缓存的材质以及布线都和 L1/L2 不同,须要更长的工夫拜访,个别在 20 ~ 25 个指令周期左右高速缓存内存无限,在同一时刻只有一部分内存单元被搁置在高速缓存中,因而咱们须要缓存替换策略。如果替换策略能够替换任何缓存行,则该高速缓存是全相联(fully associative)的。相同,如果只能替换一个特定的缓存行,他就是间接映射(direct mapped)的。如果取其折中,即容许应用一组大小为 k 的汇合中任一缓存行来替换,则称为k 级组相联(k-way set associative)的。 ...

October 8, 2021 · 1 min · jiezi

关于多线程:深入分析3种线程池执行任务的逻辑方法

摘要:联合ThreadPoolExecutor类的源码深度剖析线程池执行工作的整体流程。本文分享自华为云社区《【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行工作的外围流程》,作者: 冰 河。 ThreadPoolExecutor类中存在一个workers工作线程汇合,用户能够向线程池中增加须要执行的工作,workers汇合中的工作线程能够间接执行工作,或者从工作队列中获取工作后执行。ThreadPoolExecutor类中提供了整个线程池从创立到执行工作,再到沦亡的整个流程办法。本文,就联合ThreadPoolExecutor类的源码深度剖析线程池执行工作的整体流程。 在ThreadPoolExecutor类中,线程池的逻辑次要体现在execute(Runnable)办法,addWorker(Runnable, boolean)办法,addWorkerFailed(Worker)办法和回绝策略上,接下来,咱们就深入分析这几个外围办法。 execute(Runnable)办法execute(Runnable)办法的作用是提交Runnable类型的工作到线程池中。咱们先看下execute(Runnable)办法的源码,如下所示。 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; c = ctl.get(); } //如果线程池处于RUNNING状态,则将工作增加到阻塞队列中 if (isRunning(c) && workQueue.offer(command)) { //再次获取线程池的状态和线程池中线程的数量,用于二次查看 int recheck = ctl.get(); //如果线程池没有未处于RUNNING状态,从队列中删除工作 if (! isRunning(recheck) && remove(command)) //执行回绝策略 reject(command); //如果线程池为空,则向线程池中增加一个线程 else if (workerCountOf(recheck) == 0) addWorker(null, false); } //工作队列已满,则新增worker线程,如果新增线程失败,则执行回绝策略 else if (!addWorker(command, false)) reject(command);}整个工作的执行流程,咱们能够简化成下图所示。 ...

September 3, 2021 · 3 min · jiezi

关于多线程:多线程学习锁

前言本篇文章将对基于AbstractQueuedSynchronizer实现的锁进行学习,同时对LockSupport和Condition的应用进行整顿和剖析。内容参考了《Java并发编程的艺术》第5章。在之前的多线程学习-队列同步器中曾经对AbstractQueuedSynchronizer的原理进行了详尽剖析,如果不相熟AbstractQueuedSynchronizer,能够先查阅该篇文章。 参考资料:《Java并发编程的艺术》 注释一. 重入锁重入锁,即ReentrantLock,继承于Lock接口,提供锁重入性能。重入锁与不可重入锁的区别在于,重入锁反对曾经获取锁的线程反复对锁资源进行获取,Java中的synchronized关键字能够隐式的反对锁重入性能,思考如下一个例子。 public class HelloUtil { public static synchronized void sayHello() { System.out.print("Hello "); sayWorld(); } public static synchronized void sayWorld() { System.out.println("World"); }}已知拜访由synchronized关键字润饰的静态方法时须要先获取办法所在类的Class对象作为锁资源,所以当A线程调用HelloUtil的sayHello()办法时,须要获取的锁资源为HelloUtil类的Class对象,此时B线程再调用HelloUtil的sayHello()或sayWorld()办法时会被阻塞,然而A线程却能够在sayHello()办法中再调用sayWorld()办法,即A线程在曾经获取了锁资源的状况下又获取了一次锁资源,这就是synchronized关键字对锁重入的反对。 联合下面的例子,曾经对重入锁有了直观的意识,上面将剖析ReentrantLock是如何实现重入锁的。ReentrantLock的类图如下所示。 ReentrantLock有三个动态外部类,其中Sync继承于AbstractQueuedSynchronizer,而后FairSync和NonfairSync继承于Sync,因而Sync,FairSync和NonfairSync均是ReentrantLock组件中的自定义同步器,且FairSync提供偏心获取锁机制,NonfairSync提供非偏心获取锁机制。偏心和非偏心获取锁机制当初暂且不谈,上面先看一下Sync,FairSync和NonfairSync实现了哪些办法,如下所示。 NonfairSync和FairSync提供获取锁机制的不同就在于其实现的lock()和tryAcquire()办法的不同,具体是应用哪一种获取锁机制,是在创立ReentrantLock时指定的,ReentrantLock的构造函数如下所示。 public ReentrantLock() { sync = new NonfairSync();}public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}由上述可知,ReentrantLock默认应用非偏心获取锁机制,而后能够在构造函数中依据传入的fair参数决定应用哪种机制。当初先对下面的探讨做一个大节:ReentrantLock是可重入锁,即曾经获取锁资源的线程能够反复对锁资源进行获取,ReentrantLock外部有三个自定义同步器,别离为Sync,NonfairSync和FairSync,其中NonfairSync和FairSync能别离提供非偏心获取锁机制和偏心获取锁机制,具体应用哪一种获取锁机制,须要在ReentrantLock的构造函数中指定。 接下来联合NonfairSync和FairSync的lock()和tryAcquire()办法的源码,对非偏心获取锁机制和偏心获取锁机制进行阐明。 NonfairSync的lock()办法如下所示。 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);}非偏心获取锁调用lock()办法时会先将state以CAS形式从0设置为1,设置胜利示意竞争到了锁,因而非偏心获取锁意味着同时获取锁资源时会存在竞争关系,不能满足先到先获取的准则。如果将state以CAS形式从0设置为1失败时,会调用模板办法acquire(),已知acquire()办法会调用tryAcquire()办法,而NonfairSync的tryAcquire()办法会调用其父类Sync的nonfairTryAcquire()办法,上面看一下其实现。 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //以后线程如果是获取到锁资源的线程,则将state字段加1 //以后线程如果不是获取到锁资源的线程,则返回false而后退出同步队列 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;}在nonfairTryAcquire()办法中次要是对获取锁资源的线程进行判断,如果以后线程就是曾经获取到锁资源的线程,那么就会将state加1,因为每次都是将state加1,所以能够反复获取锁资源。 ...

August 24, 2021 · 4 min · jiezi

关于多线程:双重检查锁原来是这样演变来的你了解吗

在看Nacos的源代码时,发现多处都应用了“双重查看锁”的机制,算是十分好的实际案例。这篇文章就着案例来剖析一下双重查看锁的应用以及劣势所在,目标就是让你的代码格调更加高一个档次。 同时,基于单例模式,解说一下双重查看锁的演变过程。 Nacos中的双重查看锁在Nacos的InstancesChangeNotifier类中,有这样一个办法: private final Map<String, ConcurrentHashSet<EventListener>> listenerMap = new ConcurrentHashMap<String, ConcurrentHashSet<EventListener>>();private final Object lock = new Object();public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) { String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters); ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key); if (eventListeners == null) { synchronized (lock) { eventListeners = listenerMap.get(key); if (eventListeners == null) { eventListeners = new ConcurrentHashSet<EventListener>(); listenerMap.put(key, eventListeners); } } } eventListeners.add(listener);}该办法的次要性能就是对监听器事件进行注册。其中注册的事件都存在成员变量listenerMap当中。listenerMap的数据结构是key为String,value为ConcurrentHashSet的Map。也就是说,一个key对应一个汇合。 针对这种数据结构,在多线程的状况下,Nacos解决流程如下: 通过key获取value值;判断value是否为null;如果value值不为null,则间接将值增加到Set当中;如果为null,就须要创立一个ConcurrentHashSet,在多线程时,有可能会创立多个,因而要应用锁。通过synchronized锁定一个Object对象;在锁内再获取一次value值,如果仍然是null,则进行创立。进行后续操作。上述过程,在锁定前和锁定之后,做了两次判断,因而称作”双重查看锁“。应用锁的目标就是防止创立多个ConcurrentHashSet。 Nacos中的实例略微简单一下,上面以单例模式中的双重查看锁的演变过程。 未加锁的单例这里间接演示单例模式的懒汉模式实现: public class Singleton { private static Singleton instance; private Singleton() { } public Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }这是一个最简略的单例模式,在单线程下运行良好。但在多线程下会呈现显著的问题,可能会创立多个实例。 ...

August 17, 2021 · 2 min · jiezi

关于多线程:ThreadLocal

ThreadLocal二、工作流程参考:https://www.cnblogs.com/xzwbl...Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的外部类),它是一个Map,他的key是ThreadLocal实例对象。 // ThreadLocal.class// ThreadLocal values pertaining to this thread. This map is maintained // ThreadLocalMap 是一个定制化的 HashMapThreadLocal.ThreadLocalMap threadLocals = null;key是ThreadLocal实例对象 当为ThreadLocal类的对象set值时,首先取得以后线程的ThreadLocalMap类属性,而后以ThreadLocal类的对象为key,设定value。get值时则相似。没有则间接创立新的ThreadLocalMap赋值给threadLocals ThreadLocalMap getMap(Thread t) { return t.threadLocals;}void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}ThreadLocal变量的流动范畴为某线程,是该线程“专有的,单独霸占”的,对该变量的所有操作均由该线程实现!也就是说,ThreadLocal 不是用来解决共享对象的多线程拜访的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程本人应用的对象,其余线程是不须要拜访的,也拜访不到的。当线程终止后,这些值会作为垃圾回收。

August 16, 2021 · 1 min · jiezi

关于多线程:多线程线程池源码3

线程池的源码解读就先告一段落了(其实总感觉缺了什么货色,然而又找不到),本篇文章就简略总结下之前讲的流程及一些用法。 1 线程池流程图通过两篇文章,可能离开来看每一部分都能看懂,然而总的一个流程没有串联起来,上面看下整体的一个流程图 2 综合例子接下来再通过一个综合例子对一些知识点进行回顾 2.1 自定义ThreadPoolExecutor首先自定义ThreadPoolExecutor,而后重写beforeExecute() 和afterExecute() 办法 public class MyThreadPoolExecutor extends ThreadPoolExecutor { //省略必须要实现的父类的构造函数,4个 @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println("【before execution】" + r.toString()); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println(r.toString() + "【after execution】"); }}留神,是省略了构造函数,个别IDE都会提醒报错,至多须要实现一个2.2 自定义RejectedExecutionHandler自定义回绝策略,当初没有具体的需要,说好听的是自定义回绝策略,不好听就是单纯地硬实现而已。 public class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("task is rejected"); }}甚至对工作都没做任何解决,常识单纯的控制台输入了一句话。 2.3 监控线程池状态的线程在一开始也遍及过了线程池中相干的一些参数,通过上面这个监督线程能更加直观的理解这些参数 public class MonitorThread implements Runnable { private ThreadPoolExecutor executor; public MonitorThread(ThreadPoolExecutor executor) { this.executor = executor; } private boolean monitor = true; public void stopMonitor() { monitor = false; } @Override public void run() { while (monitor) { System.out.println( String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s", this.executor.getPoolSize(), this.executor.getCorePoolSize(), this.executor.getActiveCount(), this.executor.getCompletedTaskCount(), this.executor.getTaskCount(), this.executor.isShutdown(), this.executor.isTerminated(), this.executor.getRejectedExecutionHandler())); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }}2.4 自定义的线程预计过了这么久了,各位小伙伴也都遗记最后线程初体验开始创立的类了,这里再反复一遍。 ...

August 10, 2021 · 2 min · jiezi

关于多线程:关于线程的执行顺序可能真的只是你以为的你以为

摘要:明天,咱们就一起来看看线程到底是如何执行的,它的程序又是怎么的?本文分享自华为云社区《线程的执行程序与你想的不一样!!》,作者:冰 河 。 一、线程的执行程序是不确定的调用Thread的start()办法启动线程时,线程的执行程序是不确定的。也就是说,在同一个办法中,间断创立多个线程后,调用线程的start()办法的程序并不能决定线程的执行程序。 例如,这里,看一个简略的示例程序,如下所示。 package io.binghe.concurrent.lab03;/** * @author binghe * @version 1.0.0 * @description 线程的程序,间接调用Thread.start()办法执行不能确保线程的执行程序 */public class ThreadSort01 { public static void main(String[] args){ Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); }); thread1.start(); thread2.start(); thread3.start(); }}在ThreadSort01类中别离创立了三个不同的线程,thread1、thread2和thread3,接下来,在程序中依照程序别离调用thread1.start()、thread2.start()和thread3.start()办法来别离启动三个不同的线程。 那么,问题来了,线程的执行程序是否依照thread1、thread2和thread3的程序执行呢?运行ThreadSort01的main办法,后果如下所示。 thread1thread2thread3再次运行时,后果如下所示。 thread1thread3thread2第三次运行时,后果如下所示。 thread2thread3thread1能够看到,每次运行程序时,线程的执行程序可能不同。线程的启动程序并不能决定线程的执行程序。 二、如何确保线程的执行程序1.确保线程执行程序的简略示例在理论业务场景中,有时,后启动的线程可能须要依赖先启动的线程执行实现能力正确的执行线程中的业务逻辑。此时,就须要确保线程的执行程序。那么如何确保线程的执行程序呢? 能够应用Thread类中的join()办法来确保线程的执行程序。例如,上面的测试代码。 package io.binghe.concurrent.lab03;/** * @author binghe * @version 1.0.0 * @description 线程的程序,Thread.join()办法可能确保线程的执行程序 */public class ThreadSort02 { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); }); thread1.start(); //实际上让主线程期待子线程执行实现 thread1.join(); thread2.start(); thread2.join(); thread3.start(); thread3.join(); }}能够看到,ThreadSort02类比ThreadSort01类,在每个线程的启动办法上面增加了调用线程的join()办法。此时,运行ThreadSort02类,后果如下所示。 ...

July 26, 2021 · 2 min · jiezi

关于多线程:springboot多线程

springboot多线程 新建AsyncTaskConfig,开启@EnableAsync新建IAsyncService接口,及其实现类,新建办法,并开启 @AsyncAsyncService,调用多线程办法AsyncTaskConfig @Configuration@EnableAsyncpublic class AsyncTaskConfig implements AsyncConfigurer { // ThredPoolTaskExcutor的解决流程 // 当池子大小小于corePoolSize,就新建线程,并解决申请 // 当池子大小等于corePoolSize,把申请放入workQueue中,池子里的闲暇线程就去workQueue中取工作并解决 // 当workQueue放不下工作时,就新建线程入池,并解决申请,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做回绝解决 // 当池子的线程数大于corePoolSize时,多余的线程会期待keepAliveTime长时间,如果无申请可解决就自行销毁 @Override @Bean public Executor getAsyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); //设置外围线程数 threadPool.setCorePoolSize(10); //设置最大线程数 threadPool.setMaxPoolSize(15); //线程池所应用的缓冲队列 threadPool.setQueueCapacity(20); //期待工作在关机时实现--表明期待所有线程执行完 threadPool.setWaitForTasksToCompleteOnShutdown(true); // 等待时间 (默认为0,此时立刻进行),并没期待xx秒后强制进行 threadPool.setAwaitTerminationSeconds(60); // 线程名称前缀 threadPool.setThreadNamePrefix("mds-async-task-"); // 初始化线程 threadPool.initialize(); return threadPool; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; }// @Bean("doSomethingExecutor")// public Executor doSomethingExecutor() {// ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// // 外围线程数:线程池创立时候初始化的线程数// executor.setCorePoolSize(10);// // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过外围线程数的线程// executor.setMaxPoolSize(20);// // 缓冲队列:用来缓冲执行工作的队列// executor.setQueueCapacity(500);// // 容许线程的闲暇工夫60秒:当超过了外围线程之外的线程在闲暇工夫达到之后会被销毁// executor.setKeepAliveSeconds(60);// // 线程池名的前缀:设置好了之后能够不便咱们定位解决工作所在的线程池// executor.setThreadNamePrefix("do-something-");// // 缓冲队列满了之后的回绝策略:由调用线程解决(个别是主线程)// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// executor.initialize();// return executor;// }}IAsyncService ...

July 22, 2021 · 2 min · jiezi

关于多线程:Redis-60-新特性带你-100-掌握多线程模型

Redis 官网在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新个性,所以备受关注。 码老湿,提供了啥个性呀?晓得了我能加薪么?次要个性如下: 多线程解决网络 IO;客户端缓存;细粒度权限管制(ACL);RESP3 协定的应用;用于复制的 RDB 文件不在有用,将立即被删除;RDB 文件加载速度更快;其中备受关注的就是「多线程模型 + 客户端缓存」,咱们只有把握了新个性原理,能力判断什么时候应用 6.0 版本,如何用的更好更快,不踩坑。 本篇先从 Redis 多线程模型开始,至于客户端缓存、等且听下回分解。 最初,点击下方卡片关注「码哥字节」能加薪。 码老湿,Redis 6.0 之前为什么不应用多线程?官网回答: 应用 Redis 时,简直不存在 CPU 成为瓶颈的状况, Redis 次要受限于内存和网络。在一个一般的 Linux 零碎上,Redis 通过应用pipelining 每秒能够解决 100 万个申请,所以如果应用程序次要应用 O(N) 或O(log(N)) 的命令,它简直不会占用太多 CPU。应用了单线程后,可维护性高。多线程模型尽管在某些方面体现优异,然而它却引入了程序执行程序的不确定性,带来了并发读写的一系列问题,减少了零碎复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis 通过 AE 事件模型以及 IO 多路复用等技术,解决性能十分高,因而没有必要应用多线程。 单线程机制让 Redis 外部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等『线程不平安』的命令都能够无锁进行。 在《Redis 为什么这么快?》码哥有具体介绍快的原理。 Redis 6.0 之前单线程指的是 Redis 只有一个线程干活么?非也,Redis 在解决客户端的申请时,包含获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个程序串行的主线程解决,这就是所谓的「单线程」。 其中执行命令阶段,因为 Redis 是单线程来解决命令的,所有每一条达到服务端的命令不会立即执行,所有的命令都会进入一个 Socket 队列中,当 socket 可读则交给单线程事件散发器一一被执行。 ...

July 21, 2021 · 1 min · jiezi

关于多线程:多线程线程池源码2

时隔上一篇技术文章更新差不多有3个星期了,起因的话在上一篇文章中写啦。废话不多说,开始咱们的线程池源码的第二轮浏览。 回顾简略回顾下上一篇线程池源码中波及的两个办法,一个是execute() 执行工作的入口,还有一个是addWorker() 最艰深地了解就是是否须要增加新线程。而在addWoker() 的开端有这样一段代码 if (workerAdded) { t.start(); workerStarted = true;}显著地看到这里通过start() 办法开启了多线程,而如果想要看线程的执行逻辑,就须要去到对应类中查看run办法,这里的t就是Worker 类外面的一个成员变量,所以重点要看Worker 类中的run() 办法。 runWorker()run() 办法的源码如图所示,最初是到了runWorker() 间接来看runWorker的源码 开始是一个循环,要么执行worker自带的第一个工作(firstTask),要么通过getTask() 获取工作有工作首先得保障线程池是失常的,以下两种状况均调用wt.interrupt() 给线程设置中断标记位 线程池处于STOP状态,也就是不承受新工作,也不执行队列中的工作如果线程的标记位曾经为true,那么分明标记位,此时的线程池状态为STOP状态,这里看起来可能比拟顺当,有了第一种状况为什么还要第二种,不了解的能够先略过,前面会讲的。失常状况下是调用beforeExecute() 和afterExecute() 包裹者task.run()看一下是如何自定义前置和后置执行逻辑 因为是换电脑写了,所以例子可能和前一篇文章的不齐全一样,然而表白的是同一个意思public class ThreadPoolExamples { public static void main(String[] args) { ThreadPoolExecutor executor = new MyThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); MyThread myThread = new MyThread(); executor.execute(myThread); }}class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); }}class MyThreadPoolExecutor extends ThreadPoolExecutor { @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println("【" + Thread.currentThread().getName() + " custom before execute】"); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println("【" + Thread.currentThread().getName() + " is done】"); } MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); }}首先就是要创立本人的MyThreadPoolExecutor 类,继承ThreadPoolExecutor ,而后重写beforeExecute() 和afterExecute() 定义本人的逻辑即可,看下测试后果 ...

July 21, 2021 · 1 min · jiezi

关于多线程:多线程线程池源码一

上一篇文章讲了无关线程池的一些简略的用法,这篇文章次要是从源码的角度进一步带大家理解线程池的工作流程和工作原理。 首先先来回顾下如何应用线程池开启线程 private static void createThreadByThreadPoolExecutor() { ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); executor.execute(myThread); }能够看到其实没有其它非凡的中央,除了构建线程池的代码,其它最终要的就是executor.execute(myThread) 行代码了。 筹备工作在多线程系列的第一篇文章中提到了线程和过程的状态,线程池同样也有状态,如下: Running: 容许承受新的工作,并且解决队列中的工作Shutdown: 不承受新的工作,然而依然会解决队列中的工作Stop: 不承受新的工作,不解决队列中的工作,而且中端在运行的工作Tidying: 所有的工作都曾经终端,并且工作线程数归0,该状态下的线程都会调用terminated() 函数Terminated: 当terminated() 函数调用完后就进入了此状态首先须要晓得4个概念,Worker 、workers 、workQueue 和 task. 在线程池中有个比拟重要的类,那就是Worker ,能够看到其实现了Runnable接口(其实就是工作线程),继承了AbstractQueuedSynchronizer类(俗称AQS,在多线程中是很重要的类) workers 就是Worker 的一个汇合,private final HashSet<Worker> workers = new HashSet<Worker>();task :须要执行的工作,也就是execute() 中的参数,实现了Runnable接口workQueue 就是工作队列,就是上一篇文章中线程池构造函数中的工作队列,外面存储的就是须要执行的工作,队列是实现BlockingQueue接口的类,有以下这些实现 execute()本办法传进去的类是须要实现Runnable接口的,作为一个command 传进去 遇到新的工作后 如果工作线程数 < 外围线程数,那么间接加1个worker如果线程池是失常的工作状态,并且工作队列可能增加工作,此时须要第二轮判断 ...

July 3, 2021 · 1 min · jiezi

关于多线程:多线程线程池基本知识

上篇文章讲了下线程的创立及一些罕用的办法,然而在应用的时候,大多数是采纳了线程池来治理线程的创立,运行,销毁等过程。本篇将着重讲线程池的根底内容,包含通过线程池创立线程,线程池的根本信息等。 创立线程后期筹备本大节所有代码都是在CreateThreadByPool 类上,该类还有一个外部类MyThread 实现了Runnable 接口。首先先把根本的代码给写进去 public class CreateThreadByPool { public static void main(String[] args) { }}class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " processing"); process(); System.out.println(Thread.currentThread().getName() + " end"); } private void process() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return String.format("MyThread{%s}", Thread.currentThread().getName()); }}先来大略回顾一下,当咱们想创立10个线程的时候的代码一般形式是怎么的 private static void createThreadByNormalWay() { for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); }}在能看到的代码中,是应用了start() 本人间接开启了线程,然而如果用线程池形式来呢 ...

June 30, 2021 · 2 min · jiezi

关于多线程:多线程预加载让网站打开速度再快一点

代码: <script src="https://cdn.jsdelivr.net/npm/instantclick@3.1.0-2/instantclick.js" data-no-instant></script><script data-no-instant>InstantClick.init('mousedown');</script> 放在footter.php文件中,放在</body>之前即可。(程序:typecho) 新窗口链接有效的,能让你的网站二次减速。

June 23, 2021 · 1 min · jiezi

关于多线程:多线程基础理论知识

上一个系列是SpringCloud入门系列,当前必定会写一期进阶系列,然而目前更新的是多线程系列。 多线程的重要性不必多说,高并发在当初的生存无处不在。618,双11,12306,反对的并发量那都不晓得是多大,说再多遍也不嫌多,是十分十分十分倾佩这些团队的。 本篇文章重点是带大家理解下过程和线程方面的基础理论常识,波及到的概念都是简略然而又很实用的,一些可能没波及到的概念也会在前面的文章中写进去。 过程过程的简略概念在过程模型中,计算机所有可运行的软件,通常也包含操作系统,被组织成若干顺序进程,简称过程(process),一个过程就是一个正在执行程序的示例。大白话来说,一个过程就相当于咱们启动的我的项目,或者说关上工作管理器的时候看到的这些利用都是过程。一个过程次要包含程序计数器、寄存器和变量的以后值。 这里还须要讲下的一个概念就是守护过程 。 停留在后盾解决的过程称之为守护过程。过程的状态过程的状态有三个状态,就绪、运行、阻塞。就绪就是能运行,然而还没运行,CPU被其它过程占用了;运行就是该时刻过程曾经理论占用CPU在运行了;阻塞基本上等于暂停了,除非有外界因素烦扰,不然该过程就不能运行了。 拿做核酸来讲,医护人员就是CPU,正在做核酸的人就是运行态,在前面排队的就是就绪态,没有人逼他本人不想做基本没有来排队就是阻塞态。 值得注意的是,这三种状态是能够相互转换的 运行 》》 阻塞: 当零碎发现过程无奈再运行上来的时候,或者认为终止过程后就会产生该转换。运行 《 》 就绪: 这两个转换大多数时候是一体的,次要是由零碎过程调度程序决定的,过程对于调度的变动基本上是感知不到的。当零碎认为这个过程曾经长时间占用CPU了,那么会依据肯定的算法 重新分配CPU的工夫片,此时就会随同着状态的转变。阻塞 》》就绪: 当过程期待的一个内部事件产生时就会产生次转换,艰深就是,比方大规模核酸检测,社区上门揭示,就会去排队。过程间的通信形式过程间的通信形式有8种,然而遗记在哪本书上看到的这8种的概念,网上尽管有解释,然而不太权威,就没写进去,各位小伙伴临时就先理解下是哪8种即可。 无名管道,有名管道,高级管道,音讯队列,信号量,信号,共享内存和套接字。 线程线程基本概念对于线程,查了很久也没有一个明确的概念,甚至再《古代操作系统中》也是含糊的概念,迷你过程(称为线程)。 用Java程序艰深的来说呢,就是一个程序就是一个过程,而后main办法就是该过程的主线程,而后在之后咱们会创立多个线程。 咱们晓得,每个过程都有一个地址空间和一个控制线程,这里的主线程是不是控制线程有待考量。线程的状态相较于过程而言,线程的状态有五种。 New: new是指新建了一个线程,然而还未启动。对应到代码就是只是new了一个Thread后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值Runnable: 当线程调用了start()办法后,该线程就处于就绪状态,在期待cpu工夫片。在虚拟机的体现就是Java虚构机会为其创立办法调用栈和程序计数器,期待调度运行Running: 顾名思义就是程序处于运行状态,对应到代码就是处于就绪状态的线程取得了CPU,开始执行run()办法的线程执行体,则该线程处于运行状态。Blocked: 就是咱们常常听到的阻塞状态,是指线程因为某种原因放弃了cpu使用权,即让出了cpu timeslice(工夫片),临时进行运行。这种状态会始终维持到线程进入可运行(runnable)状态,才有机会再次获取到cpu工夫片,从而再次转到运行(running)状态Dead: 线程完结后的状态就是死亡状态对于阻塞状态而言,分为3种: 期待阻塞: 运行中的线程中的线程调用了object.wait()办法,JVM会把该线程放入期待队列中,使得本线程进入阻塞状态。同步阻塞: 运行中线程在获取对象的同步锁时,若该同步锁被别的线程占用,大白话就是说拿不到锁,JVM会把该线程放入锁池(lock pool)中。其它阻塞: 运行中的线程执行Thread.sleep(long ms)后者t.join()办法,亦或是收回了I/O申请时,JVM会把线程置为阻塞状态。当sleep()状态超时、join()期待线程终止或者超时、或者I/O处理完毕时,线程从新转入可运行(runnable)状态。对于线程的Dead状态而言,形式有以下三种: 失常完结,就是run()或者call()办法执行实现异样完结,线程在运行的过程中抛出一个未捕捉的Exception或Error调用stop()办法,间接调用该线程的stop()办法来完结线程,然而这种办法容易导致死锁,所以个别不倡议应用 线程间的通信形式线程间的通信形式次要是由3种形式,共享内存、消息传递和管道流。 共享内存:java外面个别是应用volatile共享内存消息传递:java外面会应用的形式如wait/notify , join 等办法。管道流:管道输出/输入流的模式其它过程和线程的区别多过程就是操作系统中同时运行的多个程序,多线程在同一个过程中同时运行的多个工作。 基本区别:过程是操作系统资源分配的根本单位,而线程是处理器任务调度和执行的根本单位 资源开销:每个过程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程能够看做轻量级的过程,同一类线程共享代码和数据空间,每个线程都有本人独立的运行栈和程序计数器(PC),线程之间切换的开销小。 蕴含关系:如果一个过程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是过程的一部分,所以线程也被称为轻权过程或者轻量级过程。 内存调配:同一过程的线程共享本过程的地址空间和资源,而过程之间的地址空间和资源是互相独立的 影响关系:一个过程解体后,在保护模式下不会对其余过程产生影响,然而一个线程解体整个过程都死掉。所以多过程要比多线程强壮。 执行过程:每个独立的过程有程序运行的入口、程序执行序列和程序进口。然而线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行管制,两者均可并发执行 线程不平安在《Java并发编程实战》中有这么一句话 当多个线程拜访一个类时,如果不必思考这些线程在运行时环境下的调度和交替进行,并且不须要额定的同步及调用方代码不用作其它的协调,这个类的行为依然是正确的,那么成这个类是线程平安的。艰深一点来说,要想代码线程平安,其实就是保障状态的拜访时不出错的,对象的状态个别状况下指的是数据。然而数据大多数状况都是共享,可变的。 顾名思义,共享指的是线程之间是能够拜访到这个变量,可变是是指数据的值是能够被更改的,不是写死的。 资源什么是资源,在《古代操作系统》中的定义是 咱们把这里须要排他性应用的对象称为资源。资源能够是硬件设施(如蓝光驱动器)或者是一组信息(如数据库中一个加锁的记录)......简略来说,资源就是随着工夫的推移,必须能取得、应用以及开释的任何货色。对于这个资源,在java程序外面是锁呢,还是指的共享变量呢,我集体感觉更像是锁,不晓得各位小伙伴怎么看。 并行和并发并发是指同一个时间段内多个线程在执行工作,个别是交替执行;并行是指同一个时刻下多个线程同时工作。 在操作系统的档次来看,如果多个线程能同时被多个CPU执行,这样就是并行。并发是多个线程被一个CPU依照某一算法切换执行。 创作不易,如果对你有帮忙,欢送点赞,珍藏和分享啦! 上面是集体公众号,有趣味的能够关注一下,说不定就是你的宝藏公众号哦,根本2,3天1更技术文章!!!

June 21, 2021 · 1 min · jiezi

关于多线程:Java线程池的使用及工作原理

前言在日常开发过程中总是以单线程的思维去编码,没有思考到在多线程状态下的运行状况。由此引发的后果就是申请过多,利用无奈响应。为了解决申请过多的问题,又衍生出了线程池的概念。通过“池”的思维,从而正当的解决申请。本文记录了Java中线程池的应用及工作原理,如有谬误,欢送斧正。 什么是线程池?线程池是一种用于实现计算机程序并发执行的软件设计模式。线程池保护多个线程,期待由调度程序分配任务以并发执行,该模型进步了性能,并防止了因为为短期工作频繁创立和销毁线程而导致的执行提早。线程池要解决什么问题?说到线程池就肯定要从线程的生命周期讲起。 ](/img/bVcSinY) 从图中能够理解无论工作执行多久,每个线程都要经验从生到死的状态。而应用线程池就是为了防止线程的反复创立,从而节俭了线程的New至Runnable, Running至Terminated的工夫;同时也会复用线程,最小化的节俭系统资源,于此同时进步了响应速度。 线程池的应用线程池的创立应用ThreadPoolExecutor并配置7个参数实现线程池的创立 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)corePoolSize:线程池中外围线程的最大值maximumPoolSize:线程池中最大线程数keepAliveTime:非核心线程闲暇的存活工夫大小unit:keepAliveTime的单位,罕用的有秒、分钟、小时等workQueue:阻塞队列类型threadFactory:线程工厂,用于配置线程的名称,是否为守护线程等handler:线程池的回绝策略罕用阻塞队列ArrayBlockingQueue底层基于数组的实现的有界阻塞队列 LinkedBlockingQueue底层基于单链表的阻塞队列,可配置容量,不配置容量默认为Integer.MAX_VALUE 线程工厂在《阿里巴巴Java开发手册》中强制要求指定线程的名称](/img/bVcSinX) 因为工作是应用hutool比拟多,外面也蕴含对ThreadFactory的封装,能够很不便的指定名称 ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNamePrefix("myThread-").build();回绝策略当线程池内工作线程数大于maximumPoolSize时,线程就不再接受任务,执行对应的回绝策略;目前反对的回绝策略有四种: AbortPolicy(默认):抛弃工作并抛出RejectedExecutionException异样CallerRunsPolicy:由调用者解决DiscardOldestPolicy:抛弃队列中最后面的工作,并从新入队列DiscardPolicy:抛弃工作但不抛出异样线程池的执行逻辑// 创立线程工厂ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNamePrefix("myThread-").build();// 创立线程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());execute()办法// 组合值;保留了线程池的工作状态和工作线程数private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); public void execute(Runnable command) { // 工作为空 抛出NPE if (command == null) throw new NullPointerException(); // 获取线程池状态 int c = ctl.get(); // 如果工作线程数小于外围线程数就创立新线程 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // 如果线程池处于Running状态,就把工作放在队列尾部 if (isRunning(c) && workQueue.offer(command)) { // 从新查看线程池状态 int recheck = ctl.get(); // 如果线程池不是Running状态,就移除方才增加的工作,并执行回绝策略 if (! isRunning(recheck) && remove(command)) reject(command); // 是Running状态,就增加线程 else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 增加工作失败,执行回绝策略 else if (!addWorker(command, false)) reject(command); }// addWorker()实现线程的创立执行流程 ...

May 26, 2021 · 1 min · jiezi

关于多线程:JAVA并发编程Synchronized与Lock的区别以及Lock的使用

1.Synchronized与Lock的区别2.Condition的根底概念3.应用Condition循环程序打印ABC 1.Condition的根底概念咱们可能对于Condition类都比拟生疏,所以咱们从咱们比拟相熟的Synchronized开始比照着学习。咱们都晓得Synchronized都有下图这三个应用办法:首先是咱们已知的最纯熟的synchronized关键字,他是保障线程同步用的,而后是Thread.notify()(唤醒所有正在期待中的线程),Thread.wait()(将该线程退出期待队列)。然而咱们的Lock接口也有相似的三个办法:其中,lock()办法对应着synchronize的sync,Lock接口下的condition(咱们将在下文进行介绍)中有等同于notify()的signal(),和wait()的await()。 所以总结地说,Synchronized和Lock有以下区别。1.原始形成:synchronized是关键字,属于JVM层面lock是具体类,是api层面 2.应用办法synchronized不须要用户区手动开释锁,除非运行结束或者抛异样lock须要用户手动去开释锁,就有可能呈现死锁景象,个别配合try finally来开释锁。 3.加锁是否偏心synchronized:非偏心lock:非偏心(构造方法可抉择,默认非偏心) 4.期待是否可中断synchronized不可中断,除非执行结束或者抛出异样reentrantlock可中断,可设置超时办法,设置interrupt办法 5,绑定conditionsynchronized不能绑定condition用来实现分组所须要的唤醒的线程们,能够准确唤醒,而不是像synchronized随机唤醒一个,或者唤醒全副的线程。 2.Condition的根底概念从上文刚刚简略的介绍能够看出,condition是用来准确唤醒某一个线程的,接下来咱们就来系统地介绍一下condition:Cindition,它是用来代替传统的Object的wait()、notify()实现线程间的合作,相比应用Object的wait()、notify(),应用Condition的await()、signal()这种形式实现线程间合作更加平安和高效。因而通常来说比拟举荐应用Condition,阻塞队列实际上是应用了Condition来模仿线程间合作。 3.应用Condition循环程序打印ABC既然Condition是用来准确唤醒线程的,那么咱们接下来实现一个小Demo,创立三个线程,让他们按程序循环打印ABC,A打印5次,B打印10次,C打印15次,并且应用Condition准确唤醒: 咱们将要用到一下变量: //一个信号量 1代表打印A的线程应该被唤醒,2代表B,3代表C private int number = 1; //一把锁 private Lock lock = new ReentrantLock(); //c1,c2,c3用来准确唤醒的线程 private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition();接下来咱们定义三个办法: public void print5() { } public void print10() { } public void print15() { }咱们将会启动多个线程,别离运行print5(),print10(),print15()办法,通过condition管制,在print5()完结后,启动print10(),print10()完结后,启动print15()办法,始终循环。 接下别离来看一下三个函数的函数体: public void print5() { //加锁,每次只有一个线程能运行 lock.lock(); try { //number作为信号量,为1的时候就应该执行print5()办法 while (number != 1) { //如果不是1,此线程就期待 c1.await(); } //执行打印办法 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //接下来应该轮到print10()办法启动了 number = 2; //唤醒print10()所领有的的线程 c2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10() { //加锁,每次只有一个线程能运行 lock.lock(); try { //number作为信号量,为2的时候就应该执行print10()办法 while (number != 2) { //如果不是2,此线程就期待 c2.await(); } for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //接下来应该轮到print15()办法启动了 number = 3; //唤醒print15()所领有的的线程 c3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15() { //加锁,每次只有一个线程能运行 lock.lock(); try { while (number != 3) { //如果不是3,此线程就期待 c3.await(); } for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //接下来又回到print5()办法启动了 number = 1; //唤醒print5()所在线程 c1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }从正文咱们能够看出,通过Lock加锁,能够每次只让一个办法进行,通过number信号量和三个condition,能够准确地唤醒对应地线程,接下来,咱们别离创立线程,来执行一下这三个办法: ...

May 15, 2021 · 2 min · jiezi

关于多线程:Disruptor高性能队列实现原理

一、Disruptor简介Disruptor是英国外汇交易公司LMAX开发的一个低提早高性能=无锁的有界循环数组。基于Disruptor开发的零碎单线程能撑持每秒600万订单,目前曾经开源的并发框架。Log4j2底层应用的并发框架Disruptor设计特点 环形数据结构:底层应用的是数组而非连表元素地位定位:数组的长度是2^n,下标是递增的,能够通过位运算疾速定位无锁设计,生产者或消费者须要先申请地位,申请胜利当前能力读写,申请过程中通过CAS保障线程平安。用于解决单机多线程之间的数据交换,而非相似于kafka的分布式队列。二、JDK里的队列解决方案队里有界性锁底层构造ArrayBlockingQueue有界有锁数组LinkedBlockingQueue有界有锁链表ConcurrentLinkedQueue无界无锁链表在高并发且要求较高的稳定性的零碎场景下,非了避免生产者速度过快,只能选有界队列;同时,为了缩小Java的垃圾回收对系统性能的影响尽量抉择“数组”作为队列的底层构造,符合条件只有一个:ArrayBlockingQueue 2.1 ArrayBlockingQueue的问题加锁:不加锁的性能 > CAS操作的性能 > 加锁的性能。 2.1.2 伪共享伪共享:缓存零碎中是以缓存行(cache line)为单位存储的,当多线程批改相互独立的变量时,如果这些变量共享同一个缓存行,就会无心中影响彼此的性能,CPU 和主内存之间有好几层缓存,间隔CPU越近,缓存空间越小,速度越快。CPU运算时,优先从最近的缓存寻找数据,找不到时再往下层去找。 缓存系中以 缓存行(cache line) 为单位存储,一个缓存行有64字节,能够存储8个long类型数据。当cpu拜访一个long类型的数组,当数组中的一个值被加载到缓存中,它会额定加载另外7个。当数组的一个值生效,则整个缓存行生效,它将换出其余7个值。ArrayBlockingQueue有三个成员变量: takeIndex:须要被取走的元素下标putIndex:可被元素插入的地位的下标count:队列中元素的数量这三个变量很容易放到一个缓存行中,然而之间批改没有太多的关联。所以每次批改,都会使之前缓存的数据生效,从而不能齐全达到共享的成果。 public class ArrayBlockingQueue<E> { /** The queued items */ final Object[] items; /** items index for next take, poll, peek or remove */ int takeIndex; /** items index for next put, offer, or add */ int putIndex; /** Number of elements in the queue */ int count;}伪共享解决思路:增大数组元素的距离使得由不同线程存取的元素位于不同的缓存行上,以空间换工夫。// value1和value2可能会产生伪共享class ValueNoPadding { protected volatile long value1 = 0L; protected volatile long value2 = 0L;}// value1和value2两头插入无用值 p1~p14 class ValuePadding { protected long p1, p2, p3, p4, p5, p6, p7; protected volatile long value1 = 0L; protected long p9, p10, p11, p12, p13, p14; protected volatile long value2 = 0L;}三、DisruptorRingBufferringBuffer是一个环,用做在不同线程间传递数据的空间ringBuffer领有一个序号,整个序号是递增的,用于指向下一个可用元素。队列空间在创立时就固定不再扭转,可用升高GC的压力应用示例筹备数据容器 ...

May 9, 2021 · 2 min · jiezi

关于多线程:JAVA并发编程CountDownLatchCyclicBarrierSemaphore

1.CountDownLatch根底概念和应用2.CyclicBarrier根底概念和应用3.Semaphore根底概念和应用 1.CountDownLatch根底概念和应用艰深的来说,CountDownLatch的性能就是让一些线程期待直到另外的一些线程全副运行完结之后,再开始运行。举个例子:一个教室每天都要安顿一个同学值日关门,这个同学肯定要等到其他同学全副来到之后(期待其它线程全副执行结束),能力把门关上(才会往下执行)。 咱们用代码来证实一下上述的例子: 首先,咱们要用到三个重要的办法: //构造方法的数字示意,要等五个线程运行结束后,才会持续往下执行CountDownLatch countDownLatch = new CountDownLatch(5);//须要期待的线程减一(相当于一个同学来到了老师)countDownLatch.countDown();//期待其它所有线程执行结束,再往下执行(再往下执行关门动作)countDownLatch.await(); CountDownLatch countDownLatch = new CountDownLatch(5); for (int i = 1; i <= 5; i++) { new Thread(() -> { System.out.println("第" + Thread.currentThread().getName() + "个同学来到教室"); //一个同学来到后,须要期待的线程就减一 countDownLatch.countDown(); }, i + "").start(); } //到这儿期待 countDownLatch.await(); new Thread(() -> { System.out.println("班长来到教室"); }).start();执行后果为: 能够看出,countDownLatch期待其它线程执行结束做的是减法操作。 2.CyclicBarrier根底概念和应用如果说countDownLatch是做减法,CyclicBarrier就是做加法。举个例子:集齐七颗龙珠号召神龙!(期待七个线程运行结束,而后执行最终的线程。) 同样地咱们要用到两个办法: //构造函数能够传指定线程的数量,以及线程数量达到后的实现办法,可用lambda表达式实现。 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("号召神龙!"); }); //当一个线程执行结束后,调用这个办法示意曾经执行的线程+1(已收集的龙珠+1) cyclicBarrier.await();咱们将举例用代码实现一次: //设置期待的线程总数和实现之后的办法内容 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("号召神龙!"); }); for (int i = 1; i <= 7; i++) { final int num = i; new Thread(() -> { System.out.println(Thread.currentThread().getName() + "收集到第" + num + "颗龙珠"); try { //实现线程数+1 cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }, i + "").start(); }运行后果为: ...

May 6, 2021 · 1 min · jiezi

关于操作系统:面试官什么是死锁怎么排查死锁怎么避免死锁

忽然发现我的图解零碎缺了「死锁」的内容,这就来补下。 在面试过程中,死锁也是高频的考点,因为如果线上环境真多产生了死锁,那真的出小事了。 这次,咱们就来系统地聊聊死锁的问题。 死锁的概念;模仿死锁问题的产生;利用工具排查死锁问题;防止死锁问题的产生;死锁的概念在多线程编程中,咱们为了避免多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有胜利取得到锁的线程,能力操作共享资源,获取不到锁的线程就只能期待,直到锁被开释。 那么,当两个线程为了爱护两个不同的共享资源而应用了两个互斥锁,那么这两个互斥锁利用不当的时候,可能会造成两个线程都在期待对方开释锁,在没有外力的作用下,这些线程会始终互相期待,就没方法持续运行,这种状况就是产生了死锁。 举个例子,小林拿了小美房间的钥匙,而小林在本人的房间里,小美拿了小林房间的钥匙,而小美也在本人的房间里。如果小林要从本人的房间里进来,必须拿到小美手中的钥匙,然而小美要进来,又必须拿到小林手中的钥匙,这就造成了死锁。 死锁只有同时满足以下四个条件才会产生: 互斥条件;持有并期待条件;不可剥夺条件;环路期待条件;互斥条件互斥条件是指多个线程不能同时应用同一个资源。 比方下图,如果线程 A 曾经持有的资源,不能再同时被线程 B 持有,如果线程 B 申请获取线程 A 曾经占用的资源,那线程 B 只能期待,直到线程 A 开释了资源。 持有并期待条件持有并期待条件是指,当线程 A 曾经持有了资源 1,又想申请资源 2,而资源 2 曾经被线程 C 持有了,所以线程 A 就会处于期待状态,然而线程 A 在期待资源 2 的同时并不会开释本人曾经持有的资源 1。 不可剥夺条件不可剥夺条件是指,当线程曾经持有了资源 ,在本人应用完之前不能被其余线程获取,线程 B 如果也想应用此资源,则只能在线程 A 应用完并开释后能力获取。 环路期待条件环路期待条件指都是,在死锁产生的时候,两个线程获取资源的程序形成了环形链。 比方,线程 A 曾经持有资源 2,而想申请资源 1, 线程 B 曾经获取了资源 1,而想申请资源 2,这就造成资源申请期待的环形图。 模仿死锁问题的产生Talk is cheap. Show me the code. 上面,咱们用代码来模仿死锁问题的产生。 首先,咱们先创立 2 个线程,别离为线程 A 和 线程 B,而后有两个互斥锁,别离是 mutex_A 和 mutex_B,代码如下: ...

April 1, 2021 · 4 min · jiezi

关于后端:什么是线程安全一文带你深入理解

前言欢送来到操作系统系列,采纳图解 + 大白话的模式来解说,让小白也能看懂,帮忙大家疾速科普入门。 上篇文章有介绍过过程与线程的基础知识,过程下领有多个线程,尽管多线程间通信非常不便(同过程),然而却带来了线程平安问题,本篇次要就是介绍操作系统中是用什么办法解决多线程平安,废话不多说,进入注释吧。 博主心愿读者阅读文章后能够养成思考与总结的习惯,只有这样能力把常识消化成本人的货色,而不是单纯的去记忆内容纲要 小故事带薪蹲坑,置信都是大伙都爱做的事件,阿星也不例外,然而我司所在的楼层的坑位较少,粥少僧多,非常懊恼。 阿星(线程A)每次去厕所(共享资源),门都是锁着的,阐明有共事在外面占着坑(线程B持有锁),只能无奈的在里面乖乖的等着,不久后冲水声响起,共事爽完进去(线程B开释锁),阿星一个健步进入厕所把门锁住(线程A持有锁),享受属于本人的空间,晚来的其余共事只能乖乖排队,一切都是那么颠三倒四。 假如门锁坏了,颠三倒四就不存在了,上厕所不再是享受,而是高度缓和,避免门忽然被关上,更蹩脚的是,开门时,是个妹子,这下不仅仅是线程平安问题,还有数组越界了。 故事说完,扯了那么多,就是想阐明,在多线程环境里,对共享资源进行操作,如果多线程之间不做正当的合作(互斥与同步),那么肯定会产生翻车现场。 竞争条件因为多线程共享过程资源,在操作系统调度过程内的多线程时,必然会呈现多线程竞争共享资源问题,如果不采取有效的措施,则会造成共享资源的凌乱! 来写个小例子,创立两个线程,它们别离对共享变量 i 自增 1 执行 1000 次,如下代码 失常来说,i 变量最初的值是 2000 ,可是并非如此,咱们执行下代码看看后果 后果:2000后果:1855运行了两次,后果别离是1855、2000,咱们发现每次运行的后果不同,这在计算机里是不能容忍的,尽管是小概率呈现的谬误,然而小概率它肯定是会产生的。 汇编指令为了搞明确到底产生了什么事件,咱们必须要理解汇编指令执行,以 i 加 1 为例子,汇编指令的执行过程如下 好家伙,一个加法动作,在 C P U 运行,理论要执行 3 条指令。 当初模仿下线程A与线程B的运行,假如此时内存变量 i 的值是 0,线程A加载内存的 i 值到寄存器,对寄存器 i 值加 1,此时 i 值是 1,正筹备执行下一步寄存器 i 值回写内存,工夫片应用完了,产生线程上下文切换,保留线程的公有信息到线程管制块T C P。 操作系统调度线程B执行,此时的内存变量 i 仍然还是 0,线程B执行与线程A一样的步骤,它很侥幸,在工夫片应用完前,执行完了加 1,最终回写内存,内存变量 i 值是 1。 线程B工夫片应用完后,产生线程上下文切换,回到线程A上次的状态继续执行,寄存器中的 i 值回写内存,内存变量再次被设置成 1。 按理说,最初的 i 值应该是 2,然而因为不可控的调度,导致最初 i 值是 1,上面是线程A与线程B的流程图 ...

March 23, 2021 · 2 min · jiezi

关于多线程:一个线程能否调用两次start方法

 欢送大家搜寻“小猴子的技术笔记”关注我的公众号,支付丰盛面试材料和学习材料。 公众号回复“电子书”支付超多、超全电子书籍。 公众号回复“分布式”支付分布式学习视频。 我写了一个收费的图片压缩工具:“http://images.houry.top/index” 欢送大家应用。 我写了一个netty弹幕零碎:“http://bullet-screen.houry.top:8080/index“ 对于线程我的笔记中有专门的一栏在进行解说,因而对于线程的一些概念明天在这里也就不过多进行论述了。那么一个线程可能调用两次“start()”办法吗?如果调用了会产生什么呢?这个问题的考点又是什么呢? 咱们都晓得构建一个线程能够先实现“Runnable”接口,而后用“Thread”类进行结构一个线程,就如上面这样的简略例子: public class TwoStartTest implements Runnable { @Override public void run() { System.out.println("只调用了一次start办法"); } public static void main(String[] args) { Thread thread = new Thread(new TwoStartTest()); thread.start(); }}只调用了一次start办法 运行程序之后发现程序可能失常输入。那如果将下面的程序进行批改调用两次"start()"之后再次运行会有什么后果呢? public class TwoStartTest implements Runnable { @Override public void run() { System.out.println("调用了两次start办法"); } public static void main(String[] args) { Thread thread = new Thread(new TwoStartTest()); thread.start(); thread.start(); }}Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:708) at com.monkeybrother.thread.atomic.TwoStartTest.main(TwoStartTest.java:17)调用了两次start办法 咱们能够看到程序抛出了异样,然而也打印出了“调用了两次start办法”这个输入语句。由此可知,如果反复调用“start()”办法,只会执行一次重写过的“run()”办法中的代码逻辑并且会抛出“IllegalThreadStateException”这个异样。 ...

March 12, 2021 · 1 min · jiezi

关于多线程:工作三年小胖连-waitnotifynotifyAll-都不会用真的菜

前几篇温习了下线程的创立形式、线程的状态、Thread 的源码这几篇文章,这篇讲讲 Object 几个跟线程获取开释锁相干的办法:wait、notify、notifyAll。 wait 办法源码解析因为 wait() 是 Object 类的 native 办法,在 idea 中,它长这样: public final native void wait(long timeout) throws InterruptedException;看不了源码,那只能看源码的正文,正文太长,我摘取一些要害的点进去: 1、Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.The current thread must own this object's monitor.2、In other words,waits should always occur in loops.like this one:synchronized(obj) { while (condition does not hold) obj.wait(timeout); // Perform action appropriate to condition}3、@throws IllegalArgumentExceptionif the value of timeout isnegative.@throws IllegalMonitorStateExceptionif the current thread is notthe owner of the object 's monitor.@throws InterruptedExceptionif any thread interrupted the current thread before or while the current thread was waitingfor a notification.The interrupted status of the current thread is cleared when this exception is thrown.正文中提到几点: ...

March 1, 2021 · 2 min · jiezi

关于多线程:工作三年小胖居然问我创建线程有几种方式

一个问题哈喽,我是狗哥。话不多说,金三银四,很多同学马上就要加入春招了。而多线程必定是面试必问的,开篇之前,问大家一个问题:创立线程到底有几种形式? 根底答案(答复谬误):两种,继承 Thread 和 实现 Runnable进阶答案(答复谬误):多种,继承 Thread 、实现 Runnable、线程池创立、Callable 创立、Timer 创立等等置信以上答案很多同学都能答出来。但它们都是谬误的,其实创立线程的形式只有一种。为什么?狗哥你丫逗我么?横看竖看,至多也得两种呀。别急,放下刀。且听我缓缓剖析: 第一种:继承 Thread首先是继承 Thread,创立线程最经典的办法,这种办法很常见啦。刚入门的时候,狗哥写过不晓得多少遍了。它的写法是这样的: public class MyThread extends Thread { @Override public void run() { System.out.println("通过集成 Thread 类实现线程"); }}// 如何应用new MyThread().start()如代码所示:继承 Thread 类,并重写了其中的 run () 办法,之后间接调用 start() 即可实现多线程。置信下面这种形式你肯定十分相熟,并且常常在工作中应用它们。 第二种:实现 Runnable也是最罕用的办法,写法如下: public class MyRunnable implements Runnable { @Override public void run() { System.out.println("通过实现 Runnable 形式实现线程"); }}// 应用// 1、创立MyRunnable实例MyRunnable runnable = new MyRunnable();//2.创立Thread对象//3.将MyRunnable放入Thread实例中Thread thread = new Thread(runnable);//4.通过线程对象操作线程(运行、进行)thread.start();如代码所示,这种办法其实是定义一个线程执行的工作(run 办法外面的逻辑)并没有创立线程。它首先通过 MyRunnable类实现 Runnable 接口,而后重写 run () 办法,之后还要把这个实现了 run () 办法的实例传到 Thread 类中才能够实现多线程。 ...

February 17, 2021 · 3 min · jiezi

关于多线程:悲观锁与乐观锁

何为乐观锁每次都假如最坏的状况,每次拿数据都认为他人会批改,所以每次在拿数据时都会进行加锁操作。Java中synchronized和ReentrantLock等独占锁就是乐观锁思维的实现。 何为乐观锁每次都假如最好的状况,每次拿数据都认为他人不会批改,所以每次在拿数据时都不会上锁,但如果进行更新操作,便会判断其他人是否在此期间曾经进行过更新的操作,能够应用版本号和CAS算法(Compare And Swap,比拟与替换算法)来实现。 两种锁的应用场景乐观锁多应用于多写的场景下乐观锁多应用于多读的场景下 两种锁的代码实现乐观锁实现计数器应用synchronized实现: public class Counter{ private int count; public synchronized int count(){ return ++count; }}应用Lock实现: public class Counter{ private int count; private Lock lock = new ReetrantLock(); public int count(){ lock.lock(); try{ return ++count; }finally{ lock.unlock(); } }}乐观锁实现计数器public class Counter{ private AtomicInteger ai = new AtomicInteger(); public int count(){ return ai.incrementAndGet(); //AtomicInteger类应用到了CAS算法 }}

February 11, 2021 · 1 min · jiezi

关于多线程:在nodejs中创建cluster

简介在后面的文章中,咱们讲到了能够通过worker_threads来创立新的线程,能够应用child_process来创立新的子过程。本文将会介绍如何创立nodejs的集群cluster。 cluster集群咱们晓得,nodejs的event loop或者说事件响应处理器是单线程的,然而当初的CPU基本上都是多核的,为了充分利用古代CPU多核的个性,咱们能够创立cluster,从而使多个子过程来共享同一个服务器端口。 也就是说,通过cluster,咱们能够应用多个子过程来服务解决同一个端口的申请。 先看一个简略的http server中应用cluster的例子: const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;if (cluster.isMaster) { console.log(`主过程 ${process.pid} 正在运行`); // 衍生工作过程。 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`工作过程 ${worker.process.pid} 已退出`); });} else { // 工作过程能够共享任何 TCP 连贯。 // 在本例子中,共享的是 HTTP 服务器。 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); }).listen(8000); console.log(`工作过程 ${process.pid} 已启动`);}cluster详解cluster模块源自于lib/cluster.js,咱们能够通过cluster.fork()来创立子工作过程,用来解决主过程的申请。 cluster中的eventcluster继承自events.EventEmitter,所以cluster能够发送和接管event。 cluster反对7中event,别离是disconnect,exit,fork,listening,message,online和setup。 在解说disconnect之前,咱们先介绍一个概念叫做IPC,IPC的全称是Inter-Process Communication,也就是过程间通信。 ...

January 31, 2021 · 2 min · jiezi

关于多线程:在nodejs中创建child-process

简介nodejs的main event loop是单线程的,nodejs自身也保护着Worker Pool用来解决一些耗时的操作,咱们还能够通过应用nodejs提供的worker_threads来手动创立新的线程来执行本人的工作。 本文将会介绍一种新的执行nodejs工作的形式,child process。 child processlib/child_process.js提供了child_process模块,通过child_process咱们能够创立子过程。 留神,worker_threads创立的是子线程,而child_process创立的是子过程。在child_process模块中,能够同步创立过程也能够异步创立过程。同步创立形式只是在异步创立的办法前面加上Sync。 创立进去的过程用ChildProcess类来示意。 咱们看下ChildProcess的定义: interface ChildProcess extends events.EventEmitter { stdin: Writable | null; stdout: Readable | null; stderr: Readable | null; readonly channel?: Pipe | null; readonly stdio: [ Writable | null, // stdin Readable | null, // stdout Readable | null, // stderr Readable | Writable | null | undefined, // extra Readable | Writable | null | undefined // extra ]; readonly killed: boolean; readonly pid: number; readonly connected: boolean; readonly exitCode: number | null; readonly signalCode: NodeJS.Signals | null; readonly spawnargs: string[]; readonly spawnfile: string; kill(signal?: NodeJS.Signals | number): boolean; send(message: Serializable, callback?: (error: Error | null) => void): boolean; send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean; send(message: Serializable, sendHandle?: SendHandle, options?: MessageOptions, callback?: (error: Error | null) => void): boolean; disconnect(): void; unref(): void; ref(): void; /** * events.EventEmitter * 1. close * 2. disconnect * 3. error * 4. exit * 5. message */ ... }能够看到ChildProcess也是一个EventEmitter,所以它能够发送和承受event。 ...

January 25, 2021 · 3 min · jiezi

关于多线程:AtomicStampedReference源码分析

 欢送大家搜寻“小猴子的技术笔记”关注我的公众号,文章实时同步。有问题能够及时和我交换。 之前的文章曾经介绍过CAS的操作原理,它尽管可能保证数据的原子性,但还是会有一个ABA的问题。 那么什么是ABA的问题呢?假如有一个共享变量“num”,有个线程A在第一次进行批改的时候把num的值批改成了33。批改胜利之后,紧接着又立即把“num”的批改回了22。另外一个线程B再去批改这个值的时候并不能感知到这个值被批改过。 换句话说,他人把你账户外面的钱拿进去去投资,在你发现之前又给你还了回去,那这个钱还是原来的那个钱吗?你老婆出轨之后又回到了你身边,还是你原来的那个老婆吗? 为了模仿ABA的问题,我启动了两个线程拜访一个共享的变量。将上面的代码拷贝到编译器中,运行进行测试: public class ABATest { private final static AtomicInteger num = new AtomicInteger(100); public static void main(String[] args) { new Thread(() -> { num.compareAndSet(100, 101); num.compareAndSet(101, 100); System.out.println(Thread.currentThread().getName() + " 批改num之后的值:" + num.get()); }).start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); num.compareAndSet(100, 200); System.out.println(Thread.currentThread().getName() + " 批改num之后的值:" + num.get()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); }} 第一个线程先进行批改把数值从100批改为101,而后在从101批改回100,这个过程其实是发成了ABA的操作。第二个线程期待3秒(为了是让第一个线程执行结束,第二个线程在执行)之后进行值从100批改为200。依照咱们的了解,第一个线程曾经批改过原来的值了,那么第二个线程就不应该批改胜利。然而如果你运行上面的测试用例的话,你会发现它是能够进行批改胜利的,请看运行后果: Thread-0 批改num之后的值:100Thread-1 批改num之后的值:200 尽管后果是合乎咱们的预期的:数值被胜利地进行了批改,然而批改的过程却是不合乎咱们的预期的。 ...

January 25, 2021 · 2 min · jiezi

关于多线程:高并发ReadWriteLock怎么和缓存扯上关系了

写在后面在理论工作中,有一种十分广泛的并发场景:那就是读多写少的场景。在这种场景下,为了优化程序的性能,咱们常常应用缓存来进步利用的拜访性能。因为缓存非常适合应用在读多写少的场景中。而在并发场景中,Java SDK中提供了ReadWriteLock来满足读多写少的场景。本文咱们就来说说应用ReadWriteLock如何实现一个通用的缓存核心。 本文波及的知识点有: 文章已收录到: https://github.com/sunshinelyz/technology-binghe https://gitee.com/binghe001/technology-binghe 读写锁说起读写锁,置信小伙伴们并不生疏。总体来说,读写锁须要遵循以下准则: 一个共享变量容许同时被多个读线程读取到。一个共享变量在同一时刻只能被一个写线程进行写操作。一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。这里,须要小伙伴们留神的是:读写锁和互斥锁的一个重要的区别就是:读写锁容许多个线程同时读共享变量,而互斥锁不容许。所以,在高并发场景下,读写锁的性能要高于互斥锁。然而,读写锁的写操作是互斥的,也就是说,应用读写锁时,一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。 读写锁反对偏心模式和非偏心模式,具体是在ReentrantReadWriteLock的构造方法中传递一个boolean类型的变量来管制。 public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this);}另外,须要留神的一点是:在读写锁中,读锁调用newCondition()会抛出UnsupportedOperationException异样,也就是说:读锁不反对条件变量。 缓存实现这里,咱们应用ReadWriteLock疾速实现一个缓存的通用工具类,总体代码如下所示。 public class ReadWriteLockCache<K,V> { private final Map<K, V> m = new HashMap<>(); private final ReadWriteLock rwl = new ReentrantReadWriteLock(); // 读锁 private final Lock r = rwl.readLock(); // 写锁 private final Lock w = rwl.writeLock(); // 读缓存 public V get(K key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } // 写缓存 public V put(K key, V value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } }}能够看到,在ReadWriteLockCache中,咱们定义了两个泛型类型,K代表缓存的Key,V代表缓存的value。在ReadWriteLockCache类的外部,咱们应用Map来缓存相应的数据,小伙伴都都晓得HashMap并不是线程平安的类,所以,这里应用了读写锁来保障线程的安全性,例如,咱们在get()办法中应用了读锁,get()办法能够被多个线程同时执行读操作;put()办法外部应用写锁,也就是说,put()办法在同一时刻只能有一个线程对缓存进行写操作。 ...

January 22, 2021 · 2 min · jiezi

关于多线程:nodejs中使用workerthreads来创建新的线程

[toc] nodejs中应用worker_threads来创立新的线程 简介之前的文章中提到了,nodejs中有两种线程,一种是event loop用来相应用户的申请和解决各种callback。另一种就是worker pool用来解决各种耗时操作。 nodejs的官网提到了一个可能应用nodejs本地woker pool的lib叫做webworker-threads。 惋惜的是webworker-threads的最初一次更新还是在2年前,而在最新的nodejs 12中,根本无法应用。 而webworker-threads的作者则举荐了一个新的lib叫做web-worker。 web-worker是构建于nodejs的worker_threads之上的,本文将会具体解说worker_threads和web-worker的应用。 worker_threadsworker_threads模块的源代码源自lib/worker_threads.js,它指的是工作线程,能够开启一个新的线程来并行执行javascript程序。 worker_threads次要用来解决CPU密集型操作,而不是IO操作,因为nodejs自身的异步IO曾经十分弱小了。 worker_threads中次要有5个属性,3个class和3个次要的办法。接下来咱们将会一一解说。 isMainThreadisMainThread用来判断代码是否在主线程中运行,咱们看一个应用的例子: const { Worker, isMainThread } = require('worker_threads');if (isMainThread) { console.log('在主线程中'); new Worker(__filename);} else { console.log('在工作线程中'); console.log(isMainThread); // 打印 'false'。}下面的例子中,咱们从worker_threads模块中引入了Worker和isMainThread,Worker就是工作线程的主类,咱们将会在前面具体解说,这里咱们应用Worker创立了一个工作线程。 MessageChannelMessageChannel代表的是一个异步双向通信channel。MessageChannel中没有办法,次要通过MessageChannel来连贯两端的MessagePort。 class MessageChannel { readonly port1: MessagePort; readonly port2: MessagePort; }当咱们应用new MessageChannel()的时候,会主动创立两个MessagePort。 const { MessageChannel } = require('worker_threads');const { port1, port2 } = new MessageChannel();port1.on('message', (message) => console.log('received', message));port2.postMessage({ foo: 'bar' });// Prints: received { foo: 'bar' } from the `port1.on('message')` listener通过MessageChannel,咱们能够进行MessagePort间的通信。 ...

January 21, 2021 · 5 min · jiezi

关于多线程:CopyOnWriteArrayList-读写分离弱一致性

为什么会有CopyOnWriteArrayList?咱们晓得ArrayList和LinkedList实现的List都是非线程平安的,于是就有了Vector,它是基于ArrayList的线程平安汇合,但Vector无论是add办法还是get办法都加上了synchronized润饰,当多线程读写List必须排队执行,很显然这样效率比拟是低下的,那有没有一种方法让效率晋升,让当读List的时候线程是异步的,当写List是同步的呢?答案是CopyOnWriteArrayList,他是读写拆散的,益处是进步线程拜访效率,上面咱们比照下CopyOnWriteArrayList和Vector执行效率。 import java.util.Vector;import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.CountDownLatch;/** * @author :jiaolian * @date :Created in 2021-01-18 15:28 * @description:平安list性能比照 * @modified By: * 公众号:叫练 */public class SafeListTest { private static Vector<String> safeList = new Vector<>(); //private static CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>(); private static CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { //初始化 safeList.add("叫练"); MySerive fishSerive = new MySerive(); long start = System.currentTimeMillis(); new Thread(()->{ fishSerive.read(); countDownLatch.countDown(); },"叫练读线程").start(); new Thread(()->{ fishSerive.write(); countDownLatch.countDown(); },"叫练写线程").start(); countDownLatch.await(); System.out.println("破费:"+(System.currentTimeMillis()-start)); } private static class MySerive { //读 public void read() { for (int i=0 ;i<1000000; i++) { safeList.get(0); } } //写 public void write() { for (int i=0 ;i<100000; i++) { safeList.add("叫练"); } } }}如上代码:当平安汇合用Vector时,执行时长是100毫秒,当平安汇合用CopyOnWriteArrayList时,执行时长是5000毫秒,神码?你不是说CopyOnWriteArrayList的效率要高么?但执行状况CopyOnWriteArrayList执行的时长居然是Vector的50倍!通过翻看源码,咱们发现当CopyOnWriteArrayList写元素时是通过备份数组的形式实现的,当多线程同步强烈,数据量较大时会不停的复制数组,内存节约重大。这就是时过长的起因!然而咱们还是认可读写拆散思维! ...

January 18, 2021 · 1 min · jiezi

关于多线程:StringBuilder与StringBuffer

StringBuilderpackage com.keytech.task;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;//线程不平安public class StringExample1 { public static Integer clientTotal=5000; public static Integer threadTotal=200; public static StringBuilder stringBuilder=new StringBuilder(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { executorService.execute(()->{ try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size"+stringBuilder.length()); } private static void update() { stringBuilder.append("1"); }}//size:4999main函数中输入的后果不为预期的5000,并且每次后果可能会不统一,因而StringBuilder是线程不安全类StringBufferpackage com.keytech.task;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;//线程平安public class StringExample2 { public static Integer clientTotal=5000; public static Integer threadTotal=200; public static StringBuffer stringBuffer=new StringBuffer(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(threadTotal); for (int i = 0; i < clientTotal; i++) { executorService.execute(()->{ try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+stringBuffer.length()); } private static void update() { stringBuffer.append("1"); }}//size:5000StringBuffer每次输入的后果与预期后果统一,因而它是线程平安的类StringBuffer应用synchronized保障线程平安 @Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }总结通过以上两个例子能够晓得,StringBuffer为线程安全类,StringBuilder为线程不安全类。StringBuffer在办法的实现上应用了synchronized关键字对办法进行同步,因而是线程平安的,而StringBuilder则没有进行非凡的同步或并发解决。 ...

January 6, 2021 · 1 min · jiezi

关于多线程:Java线程封闭

把对象封装到一个线程里,只有一个线程能够看到该对象,那么就算这个对象不是线程平安的,也不会呈现任何线程问题,因为它只能在一个线程中被拜访。Ad-hoc线程关闭:程序控制实现,十分软弱,最蹩脚,疏忽。堆栈关闭:简略的说就是局部变量,无并发问题。多线程拜访同一个办法时,办法中的局部变量会被拷贝一份到线程栈中。办法的局部变量不是被多线程共享的,不会呈现线程平安问题,能用局部变量就不要用全局变量,全局变量容易产生并发问题,留神全局变量不是全局常量。ThreadLocal线程关闭:Java中提供一个ThreadLocal类来实现线程关闭,这个类使线程中的某个值与保留值的对象关联起来ThreadLocalThreadLocal类提供的办法 外围的五个操作:创立,创立并赋初始值,赋值,取值,删除创立:private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;创立并赋初始值private final static ThreadLocal<String> threadLocal=new ThreadLocal<String>(){ @Override protected String initialValue() { return "入门小站"; }};赋值threadLocal.set("入门小站");取值threadLocal.get();删除threadLocal.remove();实现原理首先ThreadLocal是一个泛型类,保障能够承受任何类型的对象。一个线程内能够存在多个ThreadLocal,ThreadLocal外部保护了一个Map,这个Map不是HashMap,而是ThreadLocal实现的一个ThreadLocalMap的动态外部类。咱们应用的get(),set()办法其实是调用了这个ThreadLocalMap类对应的get(),set()。 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}调用ThreadLocal的set办法时,先获取以后的线程Thread t = Thread.currentThread();,而后获取以后线程保护的ThreadLocalMap。如果ThreadLocalMap不存在则初始化。ThreadLocalMap的map.set(this, value);第一个参数是this,this指的是以后的ThreadLocal,就是下面代码外面的threadLocal变量。 ...

January 5, 2021 · 1 min · jiezi

关于多线程:Java线程安全策略

线程安全策略创立后状态不能被批改的对象叫做不可变对象. 不可变的对象天生就是线程平安的. 不可变对象的常量(变量)是在构造函数中创立的,既然它们的状态永远无奈被扭转,那么它们永远就是线程平安的。不可变对象须要满足的条件对象创立当前其状态就不能批改。对象的所有域都是fina类型。对象是正确创立的(在对象创立期间,this援用没有逸出)并发编程实际中,this援用逃逸("this"escape)是指对象还没有结构实现,它的this援用就被公布进来了finalfinal关键字:类,办法,变量。润饰类不能被继承,final类中的成员属性能够依据须要设置成final,然而final类中所有的成员办法都被隐式的指定为final,个别不倡议将类设置成final.润饰办法锁定办法不能被继承类批改润饰变量根本数据类型变量,初始化后就不能被批改。援用类型变量,在初始化后就不能指向别的援用。//线程不平安package com.rumenz.task.single;import com.google.common.collect.Maps;import java.util.Map;public class ImmutableExample1 { private final static Integer a=1; private final static Integer b=2; //指向的援用不能被批改,然而map外面的值能够批改 private final static Map<Integer,Integer> map= Maps.newHashMap(); static { map.put(1, 1); } public static void main(String[] args) { //a=10; 编译期报错 //b=20; 编译期报错 map.put(2, 2); //线程不平安 }}Collectionsjava提供Collections工具类,在类中提供了多种不容许批改的办法。Collections.unmodifiableXXX:Collection、List、Set、Map... //线程平安package com.rumenz.task.single;import com.google.common.collect.Maps;import java.util.Collections;import java.util.Map;public class ImmutableExample1 { private final static Integer a=1; private final static Integer b=2; //指向的援用不能被批改,然而map外面的值能够批改 private static Map<Integer,Integer> map= Maps.newHashMap(); static { map.put(1, 1); //解决后map是不能够被批改的 map= Collections.unmodifiableMap(map); } public static void main(String[] args) { //容许操作,然而操作会报错 map.put(2, 2); }}Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) at com.rumenz.task.single.ImmutableExample1.main(ImmutableExample1.java:31)Collections.unmodifiableMap源码public class Collections { public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) { return new UnmodifiableMap<>(m); } private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable { @Override public boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Override public boolean replace(K key, V oldValue, V newValue) { throw new UnsupportedOperationException(); } }}Guava谷歌的Guava中提供相似Java中的CollectionsImmutableXXX:Collection、List、Set、Map... ...

January 4, 2021 · 1 min · jiezi

关于多线程:Java安全的发布对象

平安公布对象在动态初始化函数中初始化一个对象援用将对象的援用保留到volatile类型域或者AtomicReference对象中将对象的援用保留到某个正确结构对象的final类型域中将对象的援用保留到一个由锁爱护的域中Spring 框架中,Spring治理的类都是单例模式。如何保障一个实例只被初始化一次,且线程平安?通过不同单例的写法,具体形容平安公布对象的四种办法:在动态初始化函数中初始化一个对象的援用(不举荐)package com.rumenz.task.single;//线程平安//饿汉模式//动态代码块初始化public class SingletonExample { private SingletonExample(){ //初始化操作 } private static SingletonExample singletonExample=null; static { singletonExample=new SingletonExample(); } public static SingletonExample getInstance(){ return singletonExample; }}//或者package com.rumenz.task.single;//线程平安//饿汉模式//动态代码块初始化public class SingletonExample { private SingletonExample(){ //初始化操作 } private static SingletonExample singletonExample=new SingletonExample(); public static SingletonExample getInstance(){ return singletonExample; }}毛病:用不必都会初始化对象,如果初始化工作较多,加载速度会变慢,影响零碎性能。将对象的援用保留到volatile类型或AtomicReference对象中(举荐)package com.rumenz.task.single;//线程平安//懒汉模式public class SingletonExample1 { private SingletonExample1() { //初始化操作 } // 1、memory = allocate() 调配对象的内存空间 // 2、ctorInstance() 初始化对象 // 3、instance = memory 设置instance指向刚调配的内存 // 单例对象 volatile + 双重检测机制 -> 禁止指令重排 private volatile static SingletonExample1 singletonExample1=null; //动态工厂办法 public static SingletonExample1 getInstance(){ if(singletonExample1==null){ //双重检测 synchronized(SingletonExample1.class){ //同步锁 if(singletonExample1==null){ singletonExample1=new SingletonExample1(); } } } return singletonExample1; }}长处:按需加载毛病:第一次初始化的时候可能会比较慢通过synchronized(不举荐)package com.rumenz.task.single;public class SingletonExample3 { //公有构造函数 private SingletonExample3(){ //初始化操作 } private static SingletonExample3 singletonExample3=null; //动态的工厂办法 public static synchronized SingletonExample3 getSingletonExample3(){ if(singletonExample3==null){ singletonExample3=new SingletonExample3(); } return singletonExample3; }}毛病:每次进入getSingletonExample3都会加锁,消耗资源,故不举荐应用。枚举(举荐)package com.rumenz.task.single;public class SingletonExample4 { //公有构造函数 private SingletonExample4(){ //初始化 } public static SingletonExample4 getSingletonExample4(){ return Singleton.INSTANCE.getSingleton(); } private enum Singleton{ INSTANCE; private SingletonExample4 singleton; Singleton(){ singleton=new SingletonExample4(); } public SingletonExample4 getSingleton(){ return singleton; } }}长处:人造线程平安,可避免反射生成实例,举荐应用 ...

January 3, 2021 · 1 min · jiezi

关于多线程:Java多线程学习笔记三-甚欢篇

使人有乍交之欢,不若使其无久处之厌 《小窗幽记》很多时候,咱们须要的都不是再多一个线程,咱们须要的线程是许多个,咱们须要让他们配合。同时咱们还有一个欲望就是复用线程,就是将线程当做一个工人来看,咱们委托线程执行工作,执行实现之后,并不沦亡,而是在存活一段时间,因为咱们可能还须要向线程委托工作,这也就是线程池的思维。本篇讲线程合作和线程池。忽然发现初遇、相识、甚欢,这几个题目,对于多线程来说,有些不够用了,多线程的体系有些宏大。wait 和 notify、notifyAllJava多线程学习笔记(一) 初遇篇,咱们曾经介绍了多线程罕用的API(废除的接口不做介绍),然而咱们还有两个比拟重要的没有介绍,即wait(期待)、notify(告诉),这是事实世界中比拟常见的动作,比方你的女朋友说周末想跟你去看电影,而后你灰溜溜的去你女朋友家等她,而后你女朋友说让你等下(wait),她见心上人须要画个妆,画完妆之后,跟你说我画好了(notify),咱们出门吧。像上面这样: public class RomaticDateThreadDemo implements Runnable { // 画妆标记位 private boolean flag; private static final String THREAD_GIRLFRIEND = "女朋友"; public RomaticDateThreadDemo(boolean flag) { this.flag = flag; } @Override public void run() { synchronized (this) { // RomaticDateThreadDemo 的构造函数给的是false if (!flag) { flag = true; String currentThreadName = Thread.currentThread().getName(); if (THREAD_GIRLFRIEND.equals(currentThreadName)) { // 输入这句话阐明女朋友线程先进来 System.out.println("你死定了,敢让你女朋友等"); } else { // 假如男孩子线程先进来 System.out.println("...........女朋友正在化妆中.................,请等一会儿"); try { //期待女朋友唤醒,wait代表开释对象锁(开释许可证) this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } else { String currentThreadName = Thread.currentThread().getName(); if (THREAD_GIRLFRIEND.equals(currentThreadName)) { // 走到这里,阐明男孩子线程率先被线程调度器选中 System.out.println("..........要画十秒的妆............."); try { TimeUnit.SECONDS.sleep(5); // 唤醒 this.notify(); System.out.println(currentThreadName+"说:咱们走吧"); } catch (InterruptedException e) { e.printStackTrace(); } } else { // 走到这里,阐明女朋友线程率先被线程调度器选中 System.out.println(currentThreadName+"说:咱们走吧"); this.notify(); } } } }}public class WaitDemo { public static void main(String[] args) { RomaticDateThreadDemo romaticDateThreadDemo = new RomaticDateThreadDemo(false); Thread you = new Thread(romaticDateThreadDemo); you.setName("你"); Thread girlFriend = new Thread(romaticDateThreadDemo); girlFriend.setName("女朋友"); you.start(); girlFriend.start(); }}然而notify、wait、notifyAll(唤醒所有处于期待中的线程)没有位于Thread类下,所有的类都具备这三个办法,那么请问Java的设计者是怎么做到呢?让所有的类都具备了这三个办法呢?当然是在Object类中做了,而且做成public,咱们晓得所有的类都继承自Object。因为wait办法在调用的时候是开释了以后线程持有的锁,那么咱们能够大抵得出一个论断,wait、notify、notifyAll只能配合synchronized应用,显式锁是通过调用unlock办法来实现开释锁的,而咱们在Object看到,wait、notify、notifyAll是一个native(java和其余语言通信的一种伎俩,因为操作系统大多都采纳C、C++编写而成,而一些文件的操作只能通过调用操作系统提供的接口实现,所以Java调用C、C++就通过native这种机制来实现调用操作系统的接口)办法。那么为什么呢? 为什么要讲这三个原属于线程的办法放在所有类中呢? ...

January 3, 2021 · 3 min · jiezi

关于多线程:Java多线程之有序性

有序性在Java内存模型中,容许编译器和处理器对指令进行重排序,然而重排序过程不会影响单线程执行的后果,会影响到多线程并发执行后果的正确性volatile,synchronized,Lock通过volatile,synchronized,Lock保障肯定的有序性,synchronized,Lock保障每一时刻只有一个线程能够执行同步代码块,相当于让线程程序执行同步代码,从而保障有序性。另外,JVM具备一些先天的有序性,即不须要额定的伎俩就能保障有序性,即Happens-before准则,如果两个操作的执行秩序,没有方法通过Happens-before准则推导进去,虚拟机进行随便的重排序,那么久不能保障有序性。 Happens-before1.如果一个操作Happens-before另外一个操作,那么第一个操作的执行后果绝对第二个操作可见,并且第一个操作的执行程序在第二个操作之前执行。 2.两个操作之间存在Happens-before关系,并不意味着肯定要按Happens-before准则制订的程序执行。如果重排序之后的执行后果与Happens-before关系执行的后果统一,那么这种重排序就不非法。 上面是Happens-before的规定程序秩序规定:一个线程内,依照代码程序,书写在后面的操作后行产生于书写在前面的操作;锁定规定:一个unLock操作后行产生于前面对同一个锁额lock操作;volatile变量规定:对一个变量的写操作后行产生于前面对这个变量的读操作;传递规定:如果操作A后行产生于操作B,而操作B又后行产生于操作C,则能够得出操作A后行产生于操作C;线程启动规定:Thread对象的start()办法后行产生于此线程的每个一个动作;线程中断规定:对线程interrupt()办法的调用后行产生于被中断线程的代码检测到中断事件的产生;线程终结规定:线程中所有的操作都后行产生于线程的终止检测,咱们能够通过- - Thread.join()办法完结、Thread.isAlive()的返回值伎俩检测到线程曾经终止执行;对象终结规定:一个对象的初始化实现后行产生于他的finalize()办法的开始;

January 2, 2021 · 1 min · jiezi

关于多线程:多线程之线程可见性synchronized

synchronized的规定线程解锁前,必须把共享变量刷新到主内存线程加锁前将清空工作内存共享变量的值,须要从主存中获取共享变量的值。加锁(synchronized 同步)的性能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性。咱们不仅心愿避免某个线程正在应用对象状态而另一个线程在同时批改该状态,而且还心愿确保当一个线程批改了对象状态后,其余线程可能看到该变动。而线程的同步恰好也可能实现这一点。内置锁能够用于确保某个线程以一种可预测的形式来查看另一个线程的执行后果。为了确保所有的线程都能看到共享变量的最新值,能够在所有执行读操作或写操作的线程上加上同一把锁。下图示例了同步的可见性保障。 当线程 A 执行某个同步代码块时,线程 B 随后进入由同一个锁爱护的同步代码块,这种状况下能够保障,当锁被开释前,A 看到的所有变量值(锁开释前,A 看到的变量包含 y 和 x)在 B 取得同一个锁后同样能够由 B 看到。换句话说,当线程 B 执行由锁爱护的同步代码块时,能够看到线程 A 之前在同一个锁爱护的同步代码块中的所有操作后果。如果在线程 A unlock M 之后,线程 B 才进入 lock M,那么线程 B 都能够看到线程 A unlock M 之前的操作,能够失去 i=1,j=1。如果在线程 B unlock M 之后,线程 A 才进入 lock M,那么线程 B 就不肯定能看到线程 A 中的操作,因而 j 的值就不肯定是 1。synchronized线程可见性平安案例package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class SynchronizedTestOne { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Rumenzz r=new Rumenzz(); //线程1 executorService.execute(()->{ r.setAge(200); }); //线程2 executorService.execute(()->{ System.out.println(r.getAge()); }); executorService.shutdown(); }}class Rumenzz{ private Integer age=0; public synchronized Integer getAge() { return age; } public synchronized void setAge(Integer age) { this.age = age; }}以上代码是线程平安的,输入0或200,因为线程1和线程2的执行程序不一样。为了保障后果的一致性,须要控制线程的执行程序。package com.keytech.task;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @className: SynchronizedTestOne * @description: TODO 类形容 * @author: mac * @date: 2021/1/1 **/public class SynchronizedTestOne { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Rumenzz r=new Rumenzz(); CountDownLatch c=new CountDownLatch(1); executorService.execute(()->{ try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(r.getAge()); }); executorService.execute(()->{ try { Thread.sleep(5000); c.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } r.setAge(200); }); //敞开线程池 executorService.shutdown(); }}class Rumenzz{ private Integer age=0; public synchronized Integer getAge() { return age; } public synchronized void setAge(Integer age) { this.age = age; }}线程平安输入200 ...

January 1, 2021 · 1 min · jiezi

关于多线程:Java多线程之可见性之volatile

可见性一个线程对主内存的批改能够及时被其它线程察看到导致共享变量在线程间不可见的起因线程穿插执行指令重排序加上线程穿插执行共享变量更新后的值没有在工作内存与主存间及时更新保障可见性和原子性对于可见性Java提供了synchonized和volatilevolatile通过退出内存屏障和禁止重排序优化来实现,保障可见性不保障原子性对volatile变量进行写操作时,会在写操作后退出一条store屏障指令,将工作内存变量值刷新到主内存。 对volatile变量进行读操作时,会在读操作前退出一条load屏障指令,从主内存读取共享变量。 通过下面两点,任何时候,不同线程总能看到该变量的最新值.所有的操作都是CPU级别的。并不是说应用了volatile就线程平安了package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class VolatileTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static volatile Integer count=0; public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { executorService.execute(()->{ try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); } executorService.shutdown(); System.out.println("count:"+count); } private static void update(){ count++; }}//count:4988尽管应用了volatile,然而线程不平安。起因:update是非原子性的。 private static void update() { count++; //分3步 //1.取出以后count值 //2.count + 1 //3.count 从新写回主存 }假如同时有两个线程进行操作,两个线程同时执行到第一步(从内存中读取最新值)失去一样的最新的后果,而后进入第二步(+1操作)并进行第三步(从新写回主存)。只管第一步获取的值是一样的,然而同时将+1后的操作写回主存,这样就会丢掉某个+1的操作,这样就会呈现线程不平安问题总结volatile进行多线程加是线程不平安的,不适宜计数volatile不具备原子性volatile的应用场景对变量的写操作不依赖以后值该变量没有蕴含在其它变量的不变式子中volatile适宜作为状态的标记量volatile boolean flag = false;//线程1context = loadContext();flag = true;//线程2while(!flag){ sleep();}todo(context); ...

December 31, 2020 · 1 min · jiezi

关于多线程:母鸡下蛋实例多线程通信生产者和消费者waitnotify和conditionawaitsignal条件队列

简介多线程通信始终是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考查多线程的功底,明天咱们以理论生存中母鸡下蛋案例用代码分析下实现过程。母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿进去这个过程,母鸡在鸡窝下蛋,是生产者,叫练捡出鸡蛋,叫练是消费者,一进一出就是线程中的生产者和消费者模型了,鸡窝是放鸡蛋容器。事实中还有很多这样的案例,如医院叫号。上面咱们画个图示意下。 一对一生产和生产:一只母鸡和叫练wait/notifypackage com.duyang.thread.basic.waitLock.demo;import java.util.ArrayList;import java.util.List;/** * @author :jiaolian * @date :Created in 2020-12-30 16:18 * @description:母鸡下蛋:一对一生产者和消费者 * @modified By: * 公众号:叫练 */public class SingleNotifyWait { //装鸡蛋的容器 private static class EggsList { private static final List<String> LIST = new ArrayList(); } //生产者:母鸡实体类 private static class HEN { private String name; public HEN(String name) { this.name = name; } //下蛋 public void proEggs() throws InterruptedException { synchronized (EggsList.class) { if (EggsList.LIST.size() == 1) { EggsList.class.wait(); } //容器增加一个蛋 EggsList.LIST.add("1"); //鸡下蛋须要劳动能力持续产蛋 Thread.sleep(1000); System.out.println(name+":下了一个鸡蛋!"); //告诉叫练捡蛋 EggsList.class.notify(); } } } //人对象 private static class Person { private String name; public Person(String name) { this.name = name; } //取蛋 public void getEggs() throws InterruptedException { synchronized (EggsList.class) { if (EggsList.LIST.size() == 0) { EggsList.class.wait(); } Thread.sleep(500); EggsList.LIST.remove(0); System.out.println(name+":从容器中捡出一个鸡蛋"); //告诉叫练捡蛋 EggsList.class.notify(); } } } public static void main(String[] args) { //发明一个人和一只鸡 HEN hen = new HEN("小黑"); Person person = new Person("叫练"); //创立线程执行下蛋和捡蛋的过程; new Thread(()->{ try { for (int i=0; i<Integer.MAX_VALUE;i++) { hen.proEggs(); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); //叫练捡鸡蛋的过程! new Thread(()->{ try { for (int i=0; i<Integer.MAX_VALUE;i++) { person.getEggs(); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); }}如下面代码,咱们定义EggsList类来装鸡蛋,HEN类示意母鸡,Person类示意人。在主函数中创立母鸡对象“小黑”,人对象“叫练”, 创立两个线程别离执行下蛋和捡蛋的过程。代码中定义鸡窝中最多只能装一个鸡蛋(当然能够定义多个)。具体过程:“小黑”母鸡线程和“叫练”线程线程竞争锁,如果“小黑”母鸡线程先获取锁,发现EggsList鸡蛋的个数大于0,示意有鸡蛋,那就调用wait期待并开释锁给“叫练”线程,如果没有鸡蛋,就调用EggsList.LIST.add("1")示意生产了一个鸡蛋并告诉“叫练”来取鸡蛋并开释锁让“叫练”线程获取锁。“叫练”线程调用getEggs()办法获取锁后发现,如果鸡窝中并没有鸡蛋就调用wait期待并开释锁告诉“小黑”线程获取锁去下蛋,如果有鸡蛋,阐明“小黑”曾经下蛋了,就把鸡蛋取走,因为鸡窝没有鸡蛋了,所以最初也要告诉调用notify()办法告诉“小黑”去下蛋,咱们察看程序的执行后果如下图。两个线程是死循环程序会始终执行上来,下蛋和捡蛋的过程中用到的锁的是EggsList类的class,“小黑”和“叫练”竞争的都是对立把锁,所以这个是同步的。这就是母鸡“小黑”和“叫练”沟通的过程。 ...

December 31, 2020 · 5 min · jiezi

关于多线程:AtomicStampedReference解决CAS的ABA问题

AtomicStampReference解决CAS的ABA问题什么是ABAABA问题:指CAS操作的时候,线程将某个变量值由A批改为B,然而又改回了A,其余线程发现A并未扭转,于是CAS将进行值替换操作,实际上该值曾经被扭转过,这与CAS的核心思想是不合乎的ABA解决方案每次变量更新的时候,把变量的版本号进行更新,如果某变量被某个线程批改过,那么版本号肯定会递增更新,从而解决ABA问题AtomicReference 演示ABA问题package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;public class AtomicIntegerTest { private static AtomicReference<Integer> count=new AtomicReference<>(10); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ boolean b = count.compareAndSet(10, 12); if(b){ System.out.println(Thread.currentThread().getName()+"批改胜利count="+count.get()); } boolean c =count.compareAndSet(12, 10); if(c){ System.out.println(Thread.currentThread().getName()+"批改胜利count="+count.get()); } }); executorService.execute(()->{ boolean b = count.compareAndSet(10, 100); if(b){ System.out.println(Thread.currentThread().getName()+"批改胜利count="+count.get()); } }); executorService.shutdown(); }}//pool-1-thread-1批改胜利count=12//pool-1-thread-1批改胜利count=10//pool-1-thread-2批改胜利count=100pool-1-thread-1将count由10批改成12,又将count从12改成10。 pool-1-thread-2将count从10胜利改成100。呈现了ABA的问题。AtomicStampedReference解决ABA的问题以计数器的实现为例,计数器通常用来统计在线人数,在线+1,离线-1,是ABA的典型场景。package com.keytech.task;import java.util.concurrent.atomic.AtomicStampedReference;public class CounterTest { private AtomicStampedReference<Integer> count=new AtomicStampedReference<Integer>(0,0); public int getCount(){ return count.getReference(); } public int increment(){ int[] stamp=new int[1]; while (true){ Integer value = count.get(stamp); int newValue=value+1; boolean b = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1); if(b){ return newValue; } } } public int decrement(){ int[] stamp=new int[1]; while(true){ Integer value=count.get(stamp); int newValue=value-1; boolean b = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1); if(b){ return newValue; } } }}调用计数器package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;public class AtomicIntegerTest { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(200); CounterTest counterTest=new CounterTest(); for (int i = 0; i < 5000; i++) { executorService.execute(()->{ try{ semaphore.acquire(); counterTest.increment(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); executorService.execute(()->{ try{ semaphore.acquire(); counterTest.decrement(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); } executorService.shutdown(); System.out.println(counterTest.getCount()); }}//输入0AtomicBoolean保障高并发下只执行一次package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicBoolean;public class AtomicBooleanTest { private static AtomicBoolean isHappen=new AtomicBoolean(false); public static int clientTotal=5000; public static int threadTotal=200; public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { executorService.execute(()->{ try { semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); } executorService.shutdown(); } private static void update(){ if(isHappen.compareAndSet(false, true)){ System.out.println("只执行一次"); } }}//只执行一次 ...

December 30, 2020 · 2 min · jiezi

关于多线程:AtomicReference原子性引用

AtomicReferenceAtomicReference类提供了一个能够原子读写的对象援用变量。 原子意味着尝试更改雷同AtomicReference的多个线程(例如,应用比拟和替换操作)不会使AtomicReference最终达到不统一的状态。 AtomicReference甚至有一个先进的compareAndSet()办法,它能够将援用与预期值(援用)进行比拟,如果它们相等,则在AtomicReference对象内设置一个新的援用。AtomicStampReference 平安的批改一个变量的值package com.keytech.task;import org.junit.platform.commons.logging.LoggerFactory;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;/** * @className: AtomicIntegerTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/29 **///线程平安public class AtomicIntegerTest { private static AtomicReference<Integer> count=new AtomicReference<>(0); public static void main(String[] args) { //如果期望值是0,则批改成2 count.compareAndSet(0, 2); //ok //如果期望值是1,则批改成4 count.compareAndSet(1, 4); //no ok //如果期望值是2,则批改成8 count.compareAndSet(2, 8); //ok System.out.println(count.get()); }}//输入8如果AtomicReference<T>中T是一个自定义的对象,线程平安?public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile V value; /** * Creates a new AtomicReference with the given initial value. * * @param initialValue the initial value */ public AtomicReference(V initialValue) { value = initialValue; } /** * Creates a new AtomicReference with null initial value. */ public AtomicReference() { } /** * 不须要平安防护 */ public final V get() { return value; } /** * 设值值不须要进行对象平安防护 */ public final void set(V newValue) { value = newValue; } /** * 很显著调用的是csa操作 * 比拟对象是否雷同,进行设值 * 设值胜利返回true,否则返回false */ public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } /** * 设置新的值并且返回旧的值 * 原子操作 */ @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); }}compareAndSet采纳CAS保障并发AtomicReference 所提供的某些办法能够进行原子性操作,如compareAndSet、getAndSet,这仅仅是对援用进行原子性操作 ...

December 29, 2020 · 4 min · jiezi

关于多线程:线程安全之原子性AtomicAtomicIntegerLongAdderAtomicLong

线程安全性当多线程拜访某个类时,不论运行环境采纳何种调度形式或者这些过程将如何交替执行,并且在主调代码中不须要任何的同步或者协同,这个类都能体现出正确的行为,那么这个类就是线程平安的.原子性提供互斥拜访,同一时刻只有一个线程对它进行拜访.Atomic包位于java.util.concurrent.atomic,AtomicXXX : CAS、Unsafe.compareAndSwapXXXCAS(Compare and swap)比拟和替换是设计并发算法用的的一项技术,比拟和替换是用一个期望值和一个变量的以后值进行比拟,如果变量的值和期望值相等,那么就用一个新值替换变量的值. 案例线程平安package com.keytech.task;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.LongAdder;/** * @className: AtomicTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/27 **/public class AtomicTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static AtomicInteger count=new AtomicInteger(0); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore= new Semaphore(threadTotal); for (int i = 0; i <clientTotal ; i++) { executorService.execute(()->{ try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); } executorService.shutdown(); System.out.println("count:"+count); } private static void update(){ count.incrementAndGet(); }}getAndAddInt源码public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}compareAndSwapInt(this, stateOffset, expect, update)这个办法的作用就是通过cas技术来预测stateOffset变量的初始值是否是expect,如果是,那么就把stateOffset变量的值变成update,如果不是,那么就始终自旋转,始终到stateOffset变量的初始值是expect,而后在在批改stateOffset变量的值变成updateLongAddr线程平安package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.LongAdder;/** * @className: LongAddrTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/28 **/public class LongAddrTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static LongAdder count=new LongAdder(); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } } executorService.shutdown(); System.out.println("count"+count); } private static void update(){ count.increment(); }}AtomicLong线程平安package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicLong;/** * @className: AtomicLongTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/28 **/public class AtomicLongTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static AtomicLong count=new AtomicLong(); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { try{ semaphore.tryAcquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } } executorService.shutdown(); System.out.println("count"+count); } private static void update(){ count.incrementAndGet(); }}LongAddr与AtomicLong的区别AtomicLong的原理是依附底层的cas来保障原子性的更新数据,在要增加或者缩小的时候,会应用死循环不断地cas到特定的值,从而达到更新数据的目标。如果竞争不强烈,批改胜利几率很高,否则失败概率很高,在失败几率很高的状况下,这些原子操作就会进行屡次的循环操作尝试,因而性能会受到影响。 ...

December 28, 2020 · 2 min · jiezi

关于多线程:CountDownLatch和Semaphore使用场景

CountDownLatchCountDownLatch位于java.util.concurrent包下,利用它能够实现相似计数器的性能。比方有一个工作A,它要等到其它3工作实现能力执行,此时就能够用CountDownLatch来实现。 假如计数器的值为2,线程A调用await()办法之后,A线程就进入了期待状态,之后其它线程中执行countDown(),计数器就会-1,该操作线程继续执行,当计数器从2编程0,线程A继续执行。package com.keytech.task;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import java.util.concurrent.CountDownLatch;import java.util.concurrent.Executor;import java.util.concurrent.Executors;class TaskApplicationTests { //指标:炒菜 //1.洗菜 5秒 //2.买盐 3秒 public static void main(String[] args) throws InterruptedException { Executor executor=Executors.newFixedThreadPool(2); CountDownLatch countDownLatch=new CountDownLatch(2); long now = System.currentTimeMillis(); //洗菜5秒 executor.execute(()->{ try{ Thread.sleep(5000); }catch (Exception e){ e.printStackTrace(); }finally { if(countDownLatch!=null){ countDownLatch.countDown(); } } }); //买盐3秒 executor.execute(()->{ try{ Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); }finally { if(countDownLatch!=null){ countDownLatch.countDown(); } } }); countDownLatch.await(); System.out.println("能够炒菜了"+(System.currentTimeMillis()-now)); }}//能够炒菜了5082 Semaphore ...

December 26, 2020 · 1 min · jiezi

关于多线程:JVM与计算机之间的关系

计算机内存硬件架构 CPU,一台古代计算机领有两个或多个CPU,其中一些CPU还有多核,从这一点能够看出,在一个有两个或多个CPU的古代计算机上,同时运行多个线程是十分有可能的,而且每个CPU在某一个时刻,运行一个线程是必定没有问题的,这意味着,如果Java程序是多线程的,在Java程序中,每个CPU上一个线程是可能同时并发执行的。CPU Refisters(寄存器),每个CPU都蕴含一系列的寄存器,它们是CPU内存的根底,CPU在寄存器中执行操作的速度远大于在主存上执行的速度,这是因为CPU拜访寄存器的速度远大于主存。Cache(高速缓存),因为计算机的存储设备与处理器运算速度之间有着几个数量级的差距,所以古代计算机系统都不得不退出一层读写速度尽可能靠近处理器运算速度的高级缓存来作为内存与处理器之间的缓冲,将运算须要应用到的数据复制到缓存中,让运算能疾速的进行,当运算完结后,在从缓存同步到内存中。这样处理器就无需期待迟缓的内存读写,CPU拜访缓存层的速度快于拜访主存的速度,但通常比拜访外部寄存器的速度要慢。 Main Memory(主存),随机存取存储器(random access memory,RAM)又称作“随机存储器",一个计算机蕴含一个主存,所有的CPU都能够拜访主存,主存通常比CPU中的缓存大得多。JVM和计算机之间的关系 JVM 与 Computer 内存架构存在差别,硬件内存并无辨别栈与堆,对于硬件而言,所有的栈和堆都散布在主内存中,可能会呈现在高速缓存、寄存器中。内存模型形象构造

December 24, 2020 · 1 min · jiezi

关于多线程:Java-内存模型Java-Memory-ModelJMM

为了屏蔽各种硬件和操作系统的内存拜访差别,JVM制订了一套JMM内存模型来实现同一套Java程序在不同平台上实现一样的运行成果。也就是一次编译到处运行跨平台的成果。 JVM内存调配概念 JVM两个重要的概念:堆(Heap)和栈(Stack) Java中Heap是运行时数据区,有垃圾收集器负责,它的劣势的是动静分配内存,生命周期不用当时通知编译器,在运行时动静分配内存,JVM垃圾收集器会主动回收不再应用的数据.毛病是:因为是在运行时调配的内存,所以存取速度绝对较慢。Java中的Stack比Heap存取速度快,仅次于寄存器,Stack中的数据能够是共享的。然而栈的毛病是生命周期在编译器就曾经确定,不足灵活性,次要放一些根本类型的变量。 JMM要求调用栈和局本变量(本地变量)放在Stack上,对象放在Heap上。一个局部变量能够援用一个对象,而这个对象是放在Heap上。一个类可能有办法,办法中的局部变量也是放在线程栈上,即便这些办法所属的对象仍然在Heap上。一个对象的成员变量可能会随着这个对象寄存在Heap上,不论这个成员变量是根本类型还是援用类型,动态成员变量追随类的定义一起放在Heap上。寄存在堆上对象,能够被持有这个对象的线程拜访。 当一个线程能够拜访一个对象,它能够拜访该对象的成员变量,如果两个线程同时调用一个对象的同一个办法,将会都拜访该对象的成员变量,然而每个线程都有了该成员变量的公有拷贝。

December 23, 2020 · 1 min · jiezi

关于多线程:synchronized用法原理和锁优化升级过程面试

简介多线程始终是面试中的重点和难点,无论你当初处于啥级别段位,对synchronized关键字的学习防止不了,这是我的心得体会。上面咱们以面试的思维来对synchronized做一个零碎的形容,如果有面试官问你,说说你对synchronized的了解?你能够从synchronized应用层面,synchronized的JVM层面,synchronized的优化层面3个方面做零碎答复,说不定面试官会对你另眼相看哦!文章会有大量的代码是不便了解的,如果你有工夫肯定要入手敲下加深了解和记忆。如果这篇文章能对您能有所帮忙是我创作路上最大快慰。 synchronized应用层面大家都晓得synchronized是一把锁,锁到底是什么呢?举个例子,你能够把锁了解为厕所门上那把锁的惟一钥匙,每个人要进去只能拿着这把钥匙能够去开这个厕所的门,这把钥匙在一时刻只能有一个人领有,有钥匙的人能够重复出入厕所,在程序中咱们叫做这种反复出入厕所行为叫锁的可重入。它能够润饰静态方法,实例办法和代码块 ,那上面咱们一起来看看synchronized用于同步代码锁表白的意思。 对于一般同步办法,锁的是对象实例。对于动态同步办法,锁的是类的Class对象。对于同步代码块,锁的是括号中的对象。先说下同步和异步的概念。 同步:交替执行。异步:同时执行。举个例子比方吃饭和看电视两件事件,先吃完饭后再去看电视,在工夫维度上这两件事是有先后顺序的,叫同步。能够一边吃饭,一边看刷剧,在工夫维度上是不分先后同时进行的,饭吃完了电视也看了,就能够去学习了,这就是异步,异步的益处是能够提高效率,这样你就能够节省时间去学习了。 上面咱们看看代码,代码中有做了很具体的正文,能够复制到本地进行测试。如果有synchronized根底的童鞋,能够跳过锁应用层面的解说。 /** * @author :jiaolian * @date :Created in 2020-12-17 14:48 * @description:测试静态方法同步和一般办法同步是不同的锁,包含synchronized润饰的动态代码块用法; * @modified By: * 公众号:叫练 */public class SyncTest { public static void main(String[] args) { Service service = new Service(); /** * 启动上面4个线程,别离测试m1-m4办法。 */ Thread threadA = new Thread(() -> Service.m1()); Thread threadB = new Thread(() -> Service.m2()); Thread threadC = new Thread(() -> service.m3()); Thread threadD = new Thread(() -> service.m4()); threadA.start(); threadB.start(); threadC.start(); threadD.start(); } /** * 此案例阐明了synchronized润饰的静态方法和一般办法获取的不是同一把锁,因为他们是异步的,相当于是同步执行; */ private static class Service { /** * m1办法synchronized润饰静态方法,锁示意锁定的是Service.class */ public synchronized static void m1() { System.out.println("m1 getlock"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m1 releaselock"); } /** * m2办法synchronized润饰静态方法,锁示意锁定的是Service.class * 当线程AB同时启动,m1和m2办法是同步的。能够证实m1和m2是同一把锁。 */ public synchronized static void m2() { System.out.println("m2 getlock"); System.out.println("m2 releaselock"); } /** * m3办法synchronized润饰的一般办法,锁示意锁定的是Service service = new Service();中的service对象; */ public synchronized void m3() { System.out.println("m3 getlock"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m3 releaselock"); } /** * 1.m4办法synchronized润饰的同步代码块,锁示意锁定的是以后对象实例,也就是Service service = new Service();中的service对象;和m3一样,是同一把锁; * 2.当线程CD同时启动,m3和m4办法是同步的。能够证实m3和m4是同一把锁。 * 3.synchronized也能够润饰其余对象,比方synchronized (Service.class),此时m4,m1,m2办法是同步的,启动线程ABD能够证实。 */ public void m4() { synchronized (this) { System.out.println("m4 getlock"); System.out.println("m4 releaselock"); } } }}通过下面的测试,你能够能会有疑难,锁既然是存在的,那它存储在什么中央?答案:对象外面。上面咱们用代码来证实下。 ...

December 21, 2020 · 2 min · jiezi

关于多线程:Java中多线程安全问题实例分析

案例package com.duyang.thread.basic.basethread;/** * @author :jiaolian * @date :Created in 2020-12-16 14:02 * @description:线程不平安剖析 * @modified By: * 公众号:叫练 */public class ThreadUnsafe { public static void main(String[] args) { Thread task = new Task(); Thread threadA = new Thread(task,"A"); Thread threadB = new Thread(task,"B"); Thread threadC = new Thread(task,"C"); Thread threadD = new Thread(task,"D"); Thread threadE = new Thread(task,"E"); threadA.start(); threadB.start(); threadC.start(); threadD.start(); threadE.start(); } private static class Task extends Thread { int count = 5; @Override public void run() { /** * jvm分3步骤; * 1.获取count(从主内存获取值) * 2.count减1(在各自寄存器实现) * 3.保留count(刷新到主内存) * * 说下可能执行的过程... * A线程获取cpu的count值为5,A线程先减去1,保留count值为4刷新到主内存,此时还没有执行System.out.println count * 切换到B线程,此时B线程的count值为4,因为B线程是从主内存取的,B线程count值减去1为3,此时刷新到主内存,主内存值变为3 * 切换到A线程,执行System.out.println count=3 * 切换到B线程,执行System.out.println count=3 * 状况就是这样的 * */ count--; System.out.println(Thread.currentThread().getName() + " "+count); } }}可能的后果后果失去下图(论断1图) ...

December 17, 2020 · 1 min · jiezi

关于多线程:图文并茂带你搞懂多线程和多进程

什么是线程什么是线程?线程与过程与有什么关系?这是一个十分形象的问题,也是一个特地广的话题,波及到十分多的常识。我不能确保能把它讲的话,也不能确保讲的内容全副都正确。即便这样,我也心愿尽可能地把他讲艰深一点,讲的明确一点,因为这是个始终困扰我很久的,错综复杂的常识畛域,心愿通过我的了解揭开它一层一层神秘的面纱。 任务调度线程是什么?要了解这个概念,须要先理解一下操作系统的一些相干概念。 大部分操作系统(如Windows、Linux)的任务调度是采纳工夫片轮转的抢占式调度形式,也就是说一个工作执行一小段时间后强制暂停去执行下一个工作,每个工作轮流执行。工作执行的一小段时间叫做工夫片,工作正在执行时的状态叫运行状态,工作执行一段时间后强制暂停去执行下一个工作,被暂停的工作就处于就绪状态期待下一个属于它的工夫片的到来。 这样每个工作都能失去执行,因为CPU的执行效率十分高,工夫片十分短,在各个工作之间疾速地切换,给人的感觉就是多个工作在“同时进行”,这也就是咱们所说的并发(别感觉并发有多浅近,它的实现很简单,但它的概念很简略,就是一句话:多个工作同时执行)。多任务运行过程的示意图如下: 图 1:操作系统中的任务调度 过程咱们都晓得计算机的外围是CPU,它承当了所有的计算工作;而操作系统是计算机的管理者,它负责工作的调度、资源的调配和治理,统领整个计算机硬件;应用程序侧是具备某种性能的程序,程序是运行于操作系统之上的。 过程是一个具备肯定独立性能的程序在一个数据集上的一次动静执行的过程,是操作系统进行资源分配和调度的一个独立单位,是利用程序运行的载体。 过程是一种形象的概念,素来没有对立的规范定义。过程个别由程序、数据汇合和过程管制块三局部组成。 程序用于形容过程要实现的性能,是管制过程执行的指令集;数据汇合是程序在执行时所须要的数据和工作区;程序控制块(Program Control Block,简称PCB),蕴含过程的形容信息和管制信息,是过程存在的惟一标记。 过程具备的特色: 动态性:过程是程序的一次执行过程,是长期的,有生命期的,是动静产生,动静沦亡的; 并发性:任何过程都能够同其余过程一起并发执行; 独立性:过程是零碎进行资源分配和调度的一个独立单位; 结构性:过程由程序、数据和过程管制块三局部组成。 线程在晚期的操作系统中并没有线程的概念,过程是能领有资源和独立运行的最小单位,也是程序执行的最小单位。 任务调度采纳的是工夫片轮转的抢占式调度形式,而过程是任务调度的最小单位,每个过程有各自独立的一块内存,使得各个过程之间内存地址互相隔离。 起初,随着计算机的倒退,对CPU的要求越来越高,过程之间的切换开销较大,曾经无奈满足越来越简单的程序的要求了。于是就创造了线程,线程是程序执行中一个繁多的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的根本单位。 一个过程能够有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在过程的内存空间)。一个规范的线程由线程ID、以后指令指针(PC)、寄存器和堆栈组成。而过程由内存空间(代码、数据、过程空间、关上的文件)和一个或多个线程组成。 过程与线程的区别后面讲了过程与线程,但可能你还感觉迷糊,感觉他们很相似。确实,过程与线程有着千头万绪的关系,上面就让咱们一起来理一理: 1.线程是程序执行的最小单位,而过程是操作系统分配资源的最小单位; 2.一个过程由一个或多个线程组成,线程是一个过程中代码的不同执行路线; 3.过程之间互相独立,但同一过程下的各个线程之间共享程序的内存空间(包含代码段、数据集、堆等)及一些过程级的资源(如关上文件和信号),某过程内的线程在其它过程不可见; 4.调度和切换:线程上下文切换比过程上下文切换要快得多。 线程与过程关系的示意图: 图 2:过程与线程的资源共享关系  图 3:单线程与多线程的关系 总之,线程和过程都是一种形象的概念,线程是一种比过程更小的形象,线程和过程都可用于实现并发。 在晚期的操作系统中并没有线程的概念,过程是能领有资源和独立运行的最小单位,也是程序执行的最小单位。它相当于一个过程里只有一个线程,过程自身就是线程。 所以线程有时被称为轻量级过程(Lightweight Process,LWP)。 图 4:晚期的操作系统只有过程,没有线程 起初,随着计算机的倒退,对多个工作之间上下文切换的效率要求越来越高,就形象出一个更小的概念——线程,个别一个过程会有多个(也可是一个)线程。 图 5:线程的呈现,使得一个过程能够有多个线程 多线程与多核下面提到的工夫片轮转的调度形式说一个工作执行一小段时间后强制暂停去执行下一个工作,每个工作轮流执行。很多操作系统的书都说“同一时间点只有一个工作在执行”。那有人可能就要问双核处理器呢?难道两个核不是同时运行吗? 其实“同一时间点只有一个工作在执行”这句话是不精确的,至多它是不全面的。那多核处理器的状况下,线程是怎么执行呢?这就须要理解内核线程。 多核(心)处理器是指在一个处理器上集成多个运算外围从而进步计算能力,也就是有多个真正并行计算的解决外围,每一个解决外围对应一个内核线程。 内核线程(Kernel Thread, KLT)就是间接由操作系统内核反对的线程,这种线程由内核来实现线程切换,内核通过操作调度器对线程进行调度,并负责将线程的工作映射到各个处理器上。个别一个解决外围对应一个内核线程,比方单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。 当初的电脑个别是双核四线程、四核八线程,是采纳超线程技术将一个物理解决外围模仿成两个逻辑解决外围,对应两个内核线程,所以在操作系统中看到的CPU数量是理论物理CPU数量的两倍,如你的电脑是双核四线程,关上“工作管理器性能”能够看到4个CPU的监视器,四核八线程能够看到8个CPU的监视器。   图 6:双核四线程在Windows8下查看的后果 超线程技术就是利用非凡的硬件指令,把一个物理芯片模仿成两个逻辑解决外围,让单个处理器都能应用线程级并行计算,进而兼容多线程操作系统和软件,缩小了CPU的闲置工夫,进步的CPU的运行效率。 这种超线程技术(如双核四线程)由处理器硬件的决定,同时也须要操作系统的反对能力在计算机中体现进去。 程序个别不会间接去应用内核线程,而是去应用内核线程的一种高级接口——轻量级过程(Light Weight Process,LWP),轻量级过程就是咱们通常意义上所讲的线程(咱们在这称它为用户线程),因为每个轻量级过程都由一个内核线程反对,因而只有先反对内核线程,能力有轻量级过程。 用户线程与内核线程的对应关系有三种模型:一对一模型、多对一模型、多对多模型,在这以4个内核线程、3个用户线程为例对三种模型进行阐明。 一对一模型对于一对一模型来说,一个用户线程就惟一地对应一个内核线程(反过来不肯定成立,一个内核线程不肯定有对应的用户线程)。这样,如果CPU没有采纳超线程技术(如四核四线程的计算机),一个用户线程就惟一地映射到一个物理CPU的线程,线程之间的并发是真正的并发。一对一模型使用户线程具备与内核线程一样的长处,一个线程因某种原因阻塞时其余线程的执行不受影响;此处,一对一模型也能够让多线程程序在多处理器的零碎上有更好的体现。 但一对一模型也有两个毛病:1.许多操作系统限度了内核线程的数量,因而一对一模型会使用户线程的数量受到限制;2.许多操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率降落。   图 7:一对一模型 多对一模型多对一模型将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行,因而绝对一对一模型,多对一模型的线程切换速度要快许多;此外,多对一模型对用户线程的数量简直无限度。但多对一模型也有两个毛病:1.如果其中一个用户线程阻塞,那么其它所有线程都将无奈执行,因为此时内核线程也随之阻塞了;2.在多处理器零碎上,处理器数量的减少对多对一模型的线程性能不会有显著的减少,因为所有的用户线程都映射到一个处理器上了。   图 8:多对一模型 多对多模型多对多模型联合了一对一模型和多对一模型的长处,将多个用户线程映射到多个内核线程上。多对多模型的长处有:1.一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行;2.多对多模型对用户线程的数量没有限度;3.在多处理器的操作系统中,多对多模型的线程也能失去肯定的性能晋升,但晋升的幅度不如一对一模型的高。 在当初风行的操作系统中,大都采纳多对多的模型。   图 9:多对多模型 查看过程与线程一个应用程序可能是多线程的,也可能是多过程的,如何查看呢?在Windows下咱们只须关上工作管理器就能查看一个应用程序的过程和线程数。按“Ctrl+Alt+Del”或右键快捷工具栏关上工作管理器。 查看过程数和线程数: ...

December 5, 2020 · 1 min · jiezi

关于多线程:SleepWaitNotifyNofityAllSynchronized

1、wait的用法/** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p> * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (&lt;condition does not hold&gt;) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { wait(0);}参考wait的注解, 当调用wait的时候必须要持有监视器锁,不然会跑出非法监视器异样为什么wait()和notify()须要搭配synchonized关键字应用 ...

November 28, 2020 · 2 min · jiezi

关于多线程:JAVA多线程设计模式pdf

关注“Java后端技术全栈” 回复“面试”获取全套面试材料 作为一名JAVA开发者,“设计模式”这个词应该不会生疏。 设计模式(Design pattern)是软件开发人员在软件开发过程中面临的个别问题的解决方案。 这些解决方案是泛滥软件开发人员通过相当长的一段时间的试验和谬误总结进去的。 设计模式代表了最佳的实际,通常被有教训的面向对象的软件开发人员所采纳。 毫无疑问,设计模式于己于别人于零碎都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。 我的项目中正当地使用设计模式能够完满地解决很多问题,每种模式在事实中都有相应的原理来与之对应,每种模式都形容了一个在咱们四周一直反复产生的问题,以及该问题的外围解决方案,这也是设计模式能被广泛应用的起因。 最近很多小伙伴问我要一些 设计模式 相干的材料,于是我翻箱倒柜,找到了这本十分经典的电子书——《JAVA多线程设计模式》。 材料介绍 《JAVA多线程设计模式》通过浅显易懂的文字与实例来介绍JAVA线程相干的设计模式概念,并且通过理论的JAVA程序范例和UML图示来一一讲解,书中有代码的重要局部加上标注使读者更加容易解读,再配合泛滥的阐明图解,无论对于初学者还是程序设计高手来说,这都是一本学习和意识设计模式十分难得的好书。 如何获取? 1.辨认二维码并关注公众号「Java后端技术全栈」; 2.在公众号后盾回复关键字「961」

November 20, 2020 · 1 min · jiezi

关于多线程:线程池运用不当的一次线上事故

在高并发、异步化等场景,线程池的使用能够说无处不在。线程池从实质上来讲,即通过空间换取工夫,因为线程的创立和销毁都是要耗费资源和工夫的,对于大量应用线程的场景,应用池化治理能够延迟线程的销毁,大大提高单个线程的复用能力,进一步晋升整体性能。 明天遇到了一个比拟典型的线上问题,刚好和线程池无关,另外波及到死锁、jstack命令的应用、JDK不同线程池的适宜场景等知识点,同时整个考察思路能够借鉴,特此记录和分享一下。 01 业务背景形容该线上问题产生在广告零碎的外围扣费服务,首先简略交代下大抵的业务流程,不便了解问题。 绿框局部即扣费服务在广告召回扣费流程中所处的地位,简略了解:当用户点击一个广告后,会从C端发动一次实时扣费申请(CPC,按点击扣费模式),扣费服务则承接了该动作的外围业务逻辑:包含执行反作弊策略、创立扣费记录、click日志埋点等。 02 问题景象和业务影响12月2号早晨11点左右,咱们收到了一个线上告警告诉:扣费服务的线程池工作队列大小远远超出了设定阈值,而且队列大小随着时间推移还在继续变大。具体告警内容如下: 相应的,咱们的广告指标:点击数、支出等也呈现了非常明显的下滑,简直同时收回了业务告警告诉。其中,点击数指标对应的曲线体现如下: 该线上故障产生在流量高峰期,继续了将近30分钟后才恢复正常。 03 问题考察和事变解决过程上面具体说下整个事变的考察和剖析过程。 第1步:收到线程池工作队列的告警后,咱们第一工夫查看了扣费服务各个维度的实时数据:包含服务调用量、超时量、谬误日志、JVM监控,均未发现异常。 第2步:而后进一步排查了扣费服务依赖的存储资源(mysql、redis、mq),内部服务,发现了事变期间存在大量的数据库慢查问。 上述慢查问来自于事变期间一个刚上线的大数据抽取工作,从扣费服务的mysql数据库中大批量并发抽取数据到hive表。因为扣费流程也波及到写mysql,猜想这个时候mysql的所有读写性能都受到了影响,果然进一步发现insert操作的耗时也远远大于失常期间。 第3步:咱们猜想数据库慢查问影响了扣费流程的性能,从而造成了工作队列的积压,所以决定立马暂定大数据抽取工作。然而很奇怪:进行抽取工作后,数据库的insert性能复原到失常程度了,然而阻塞队列大小依然还在继续增大,告警并未隐没。 第4步:思考广告支出还在继续大幅度上涨,进一步剖析代码须要比拟长的工夫,所以决定立刻重启服务看看有没有成果。为了保留事故现场,咱们保留了一台服务器未做重启,只是把这台机器从服务治理平台摘掉了,这样它不会接管到新的扣费申请。 果然重启服务的杀手锏很管用,各项业务指标都恢复正常了,告警也没有再呈现。至此,整个线上故障失去解决,继续了大略30分钟。 04 问题根本原因的剖析过程上面再具体说下事变根本原因的剖析过程。 第1步:第二天下班后,咱们猜想那台保留了事故现场的服务器,队列中积压的工作应该都被线程池解决掉了,所以尝试把这台服务器再次挂载下来验证下咱们的猜想,后果和预期齐全相同,积压的工作依然都在,而且随着新申请进来,零碎告警立即再次出现了,所以又马上把这台服务器摘了下来。 第2步:线程池积压的几千个工作,通过1个早晨都没被线程池解决掉,咱们猜想应该存在死锁状况。所以打算通过jstack命令dump线程快照做下详细分析。 #找到扣费服务的过程号$ ps aux|grep "adclick"# 通过过程号dump线程快照,输入到文件中$ jstack pid > /tmp/stack.txth在jstack的日志文件中,立马发现了:用于扣费的业务线程池的所有线程都处于waiting状态,线程全副卡在了截图中红框局部对应的代码行上,这行代码调用了countDownLatch的await()办法,即期待计数器变为0后开释共享锁。 第3步:找到上述异样后,间隔找到根本原因就很靠近了,咱们回到代码中持续考察,首先看了下业务代码中应用了newFixedThreadPool线程池,外围线程数设置为25。针对newFixedThreadPool,JDK文档的阐明如下: 创立一个可重用固定线程数的线程池,以共享的无界队列形式来运行这些线程。如果在所有线程处于沉闷状态时提交新工作,则在有可用线程之前,新工作将在队列中期待。对于newFixedThreadPool,外围包含两点: 1、最大线程数 = 外围线程数,当所有外围线程都在解决工作时,新进来的工作会提交到工作队列中期待; 2、应用了无界队列:提交给线程池的工作队列是不限度大小的,如果工作被阻塞或者解决变慢,那么显然队列会越来越大。 所以,进一步论断是:外围线程全副死锁,新进的工作不对涌入无界队列,导致工作队列一直减少。 第4步:到底是什么起因导致的死锁,咱们再次回到jstack日志文件中提醒的那行代码做进一步剖析。上面是我简化过后的示例代码: /*** 执行扣费工作 */public Result<Integer> executeDeduct(ChargeInputDTO chargeInput) { ChargeTask chargeTask = new ChargeTask(chargeInput); bizThreadPool.execute(() -> chargeTaskBll.execute(chargeTask )); return Result.success();}/*** 扣费工作的具体业务逻辑 */public class ChargeTaskBll implements Runnable { public void execute(ChargeTask chargeTask) { // 第一步:参数校验 verifyInputParam(chargeTask); // 第二步:执行反作弊子工作 executeUserSpam(SpamHelper.userConfigs); // 第三步:执行扣费 handlePay(chargeTask); // 其余步骤:点击埋点等 ... }}/*** 执行反作弊子工作 */public void executeUserSpam(List<SpamUserConfigDO> configs) { if (CollectionUtils.isEmpty(configs)) { return; } try { CountDownLatch latch = new CountDownLatch(configs.size()); for (SpamUserConfigDO config : configs) { UserSpamTask task = new UserSpamTask(config,latch); bizThreadPool.execute(task); } latch.await(); } catch (Exception ex) { logger.error("", ex); }}通过上述代码,大家是否发现死锁是怎么产生的呢?根本原因在于:一次扣费行为属于父工作,同时它又蕴含了屡次子工作:子工作用于并行执行反作弊策略,而父工作和子工作应用的是同一个业务线程池。当线程池中全部都是执行中的父工作时,并且所有父工作都存在子工作未执行完,这样就会产生死锁。上面通过1张图再来直观地看下死锁的状况: ...

November 8, 2020 · 1 min · jiezi

关于多线程:Java线程池实现原理及其在美团业务中的实践转载

Java多线程在大厂下是必问的问题,一篇比拟干货的美团技术图分享如下: https://tech.meituan.com/2020...

November 1, 2020 · 1 min · jiezi

关于多线程:面试官看你简历说写精通ThreadLocal这几道题你都会吗

问题和Synchronized的区别存储在jvm的哪个区域真的只是以后线程可见吗会导致内存透露么为什么用Entry数组而不是Entry对象你学习的开源框架哪些用到了ThreadLocalThreadLocal里的对象肯定是线程平安的吗口试题一、概述1、官网术语ThreadLocal类是用来提供线程外部的局部变量。让这些变量在多线程环境下拜访(get/set)时能保障各个线程里的变量绝对独立于其余线程内的变量。 2、大白话ThreadLocal是一个对于创立线程局部变量的类。 通常状况下,咱们创立的成员变量都是线程不平安的。因为他可能被多个线程同时批改,此变量对于多个线程之间彼此并不独立,是共享变量。而应用ThreadLocal创立的变量只能被以后线程拜访,其余线程无法访问和批改。也就是说:将线程公有化变成线程私有化。 二、利用场景每个线程都须要一个独享的对象(比方工具类,典型的就是SimpleDateFormat,每次应用都new一个多节约性能呀,间接放到成员变量里又是线程不平安,所以把他用ThreadLocal治理起来就完满了。)比方:/** * Description: SimpleDateFormat就一份,不浪费资源。 * * @author TongWei.Chen 2020-07-10 14:00:29 */public class ThreadLocalTest05 { public static String dateToStr(int millisSeconds) { Date date = new Date(millisSeconds); SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return simpleDateFormat.format(date); } private static final ExecutorService executorService = Executors.newFixedThreadPool(100); public static void main(String[] args) { for (int i = 0; i < 3000; i++) { int j = i; executorService.execute(() -> { String date = dateToStr(j * 1000); // 从后果中能够看出是线程平安的,工夫没有反复的。 System.out.println(date); }); } executorService.shutdown(); }}class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // java8的写法,装逼神器// public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =// ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));}仔细的敌人曾经发现了,这TM也是每个线程都创立一个SimpleDateFormat啊,跟间接在办法外部new没区别,错了,大错特错!1个申请进来是一个线程,他可能贯通了N个办法,你这N个办法假如有3个都在应用dateToStr(),你间接new的话会产生三个SimpleDateFormat对象,而用ThreadLocal的话只会产生一个对象,一个线程一个。每个线程内须要保留全局变量(比方在登录胜利后将用户信息存到ThreadLocal里,而后以后线程操作的业务逻辑间接get取就完事了,无效的防止的参数来回传递的麻烦之处),肯定层级上缩小代码耦合度。再细化一点就是: ...

October 31, 2020 · 5 min · jiezi

关于多线程:cc-Linux多线程编程

Linux多线程编程 线程概念 线程是指运行中的程序的调度单位。一个线程指的是过程中一个繁多程序的控制流,也被称为轻量级线程。它是零碎独立调度和调配的根本单位。同一过程中的多个线程将共享该零碎中的全副系统资源,比方文件描述符和信号处理等。一个过程能够有很多线程,每个线程并行执行不同的工作。 线程与过程比拟 ① 和过程相比,它是一种十分“勤俭”的多任务操作形式。在Linux零碎中,启动一个新的过程必须调配给它独立的地址空间,建设泛滥的数据表来保护其代码段、堆栈段和数据段,这种多任务工作形式的代价十分“低廉”。而运行于一个过程中的多个线程,它们彼此之间应用雷同的地址空间,共享大部分数据,启动一个线程所破费的空间远远小于启动一个过程所破费的空间,而且线程间彼此切换所须要工夫也远远小于过程间切换所须要的工夫。 ② 线程间不便的通信机制。对不同过程来说它们具备独立的数据空间,要进行数据的传递只能通过通信的形式进行。这种形式不仅费时,而且很不不便。线程则不然,因为同一过程下的线程之间共享数据空间,所以一个线程的数据能够间接为其余线程所用,不仅不便,而且快捷。 线程根本编程 Linux零碎下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,须要应用头文件pthread.h,连贯时须要应用库libpthread.a。因为pthread的库不是Linux零碎的库,所以在编译时要加上 -lpthread。例如:gcc filename -lpthread。留神,这里要讲的线程相干操作都是用户空间中的线程的操作。 线程创立:创立线程实际上就是确定调用该线程函数的入口点,这里通常应用的函数是pthread_create()。在线程创立后,就开始运行相干的线程函数。 线程退出:在线程创立后,就开始运行相干的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种办法。另一种退出线程的办法是应用函数pthread_exit(),这是线程的被动行为。这里要留神的是,在应用线程函数时,不能随便应用exit()退出函数来进行出错解决。因为exit()的作用是使调用过程终止,而一个过程往往蕴含多个线程,因而,在应用exit()之后,该过程中的所有线程都终止了。在线程中就能够应用pthread_exit()来代替过程中的exit()。 线程期待:因为一个过程中的多个线程是共享数据段的,因而,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而失去开释。正如过程之间能够用wait()零碎调用来同步终止并开释资源一样,线程之间也有相似机制,那就是pthread_join()函数。pthread_join()用于将以后过程挂起来期待线程的完结。这个函数是一个线程阻塞的函数,调用它的函数将始终期待到被期待的线程完结为止,当函数返回时,被期待线程的资源就被发出。 线程勾销:后面曾经提到线程调用pthread_exit()函数被动终止本身线程,然而在很多线程利用中,常常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种性能,但在被勾销的线程的外部须要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置本人的勾销状态。例如,被勾销的线程接管到另一个线程的勾销申请之后,是承受函数疏忽这个申请;如果是承受,则再判断立即采取终止操作还是期待某个函数的调用等。 线程标识符获取:获取调用线程的标识ID。 线程革除:线程终止有两种状况:失常终止和非正常终止。线程被动调用pthread_exit()或者从线程函数中return都将使线程失常退出,这是可预感的退出形式;非正常终止是线程在其它线程的干涉下,或者因为本身运行出错(比方拜访非法地址)而退出,这种退出形式是不可预感的。不论是可预感的线程终止还是异样终止,都回存在资源开释的问题,如何保障线程终止时能顺利地开释掉本人所占用的资源,是一个必须思考的问题。 从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包含调用pthread_exit()和异样终止,不包含return)都将执行pthread_cleanup_push()所指定的清理函数。 Linuxc/c++服务器开发高阶视频学习材料+qun720209036获取 更多视频内容包含C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,P2P,K8S,Docker,TCP/IP,协程,DPDK多个高级知识点分享。 视频链接:C/C++Linux服务器开发/后盾架构师-学习视频 1、如何利用2个条件变量实现线程同步? 思路:就是来回的利用pthread_cond_signal()函数,当一方被阻塞时,唤醒函数能够唤醒pthread_cond_wait()函数,只不过pthread_cond_wait()这个办法要执行其后的语句,必须遇到下一个阻塞(也就是pthread_cond_wait()办法时),才执行唤醒后的其后语句。 代码如下: include<stdio.h>include<unistd.h>include<stdlib.h>include<string.h>include<pthread.h>define MAX_NUM 2static int count = 1;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t js = PTHREAD_COND_INITIALIZER;pthread_cond_t os = PTHREAD_COND_INITIALIZER;void A(void arg){pthread_mutex_lock(&mutex); while(count <= MAX_NUM) { if(count%2 == 1){ printf("A = %dn", count); count++; pthread_cond_signal(&os); sleep(5); ...

October 30, 2020 · 1 min · jiezi

关于多线程:多线程5ThreadLocal

ThreadLocal是一个线程外部的存储类,能够在指定线程内存储数据,数据存储当前,只有指定线程能够失去存储数据,ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的相互独立的。通过get和set办法就能够失去以后线程对应的值,ThreadLocal不是用来解决共享对象的多线程拜访问题的,通过ThreadLocal的set()办法设置到线程的ThreadLocal.ThreadLocalMap里的是是线程本人要存储的对象,其余线程不须要去拜访,也是拜访不到的。各个线程中的ThreadLocal.ThreadLocalMap以及ThreadLocal.ThreadLocal中的值都是不同的对象。 1、每个线程都有一个本人的ThreadLocal.ThreadLocalMap对象 2、每一个ThreadLocal对象都有一个循环计数器 3、ThreadLocal.get()取值,就是依据以后的线程,获取线程中本人的ThreadLocal.ThreadLocalMap,而后在这个Map中依据第二点中循环计数器获得一个特定value值 set1、先对ThreadLocal外面的threadLocalHashCode取模获取到一个table中的地位 2、这个地位上如果有数据,获取这个地位上的ThreadLocal (1)判断一下地位上的ThreadLocal和我自身这个ThreadLocal是不是一个ThreadLocal,是的话数据就笼罩,返回 (2)不是同一个ThreadLocal,再判断一下地位上的ThreadLocal是是不是空的,这个解释一下。Entry是ThreadLocal弱援用,"static class Entry extends WeakReference<ThreadLocal>",有可能这个ThreadLocal被垃圾回收了,这时候把新设置的value替换到以后地位上,返回 (3)下面都没有返回,给模加1,看看模加1后的table地位上是不是空的,是空的再加1,判断地位上是不是空的...始终到找到一个table上的地位不是空的为止,往这外面塞一个value。换句话说,当table的地位上有数据的时候,ThreadLocal采取的是方法是找最近的一个空的地位设置数据。 3、如果没有数据,实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals /** * Sets the current thread's copy of this thread-local variable 将此线程局部变量的以后线程正本设置为指定值。 大多数子类将不须要重写此办法,而仅依附{@link #initialValue}办法来设置线程局部变量的值。 * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of @param value要存储在此本地线程的以后线程正本中的值。 * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; //对ThreadLocal外面的threadLocalHashCode取模获取到一个table中的地位 int i = key.threadLocalHashCode & (len-1); //这个地位上如果有数据,获取这个地位上的ThreadLocal for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //判断一下地位上的ThreadLocal和我自身这个ThreadLocal是不是一个ThreadLocal,是的话数据就笼罩,返回 if (k == key) { e.value = value; return; } //不是同一个ThreadLocal,再判断一下地位上的ThreadLocal是是不是空的,Entry是ThreadLocal弱援用,"static class Entry extends WeakReference<ThreadLocal>",有可能这个ThreadLocal被垃圾回收了 if (k == null) { //设置新的value替换到以后地位上,返回 replaceStaleEntry(key, value, i); return; } } //下面都没有返回,给模加1,看看模加1后的table地位上是不是空的,是空的再加1,判断地位上是不是空的...始终到找到一个table上的地位不是空的为止,往这外面塞一个value。换句话说,当table的地位上有数据的时候,ThreadLocal采取的 //是方法是找最近的一个空的地位设置数据 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }get1、获取以后线程 ...

October 21, 2020 · 4 min · jiezi

关于多线程:多线程4线程池

对于线程池线程池就是首先创立一些线程,它们的汇合称为线程池。应用线程池能够很好地进步性能,线程池在系统启动时即创立大量闲暇的线程,程序将一个工作传给线程池,线程池就会启动一条线程来执行这个工作,执行完结当前,该线程并不会死亡,而是再次返回线程池中成为闲暇状态,期待执行下一个工作 为什么要应用线程池多线程运行工夫,零碎一直的启动和敞开新线程,老本十分高,会过渡耗费系统资源,以及过渡切换线程的危险,从而可能导致系统资源的解体。这时,线程池就是最好的抉择了 ThreadPoolExecutor类Java外面线程池的顶级接口是Executor,然而严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService ExecutorService真正的线程池接口。ScheduledExecutorService能和Timer/TimerTask相似,解决那些须要工作反复执行的问题。ThreadPoolExecutorExecutorService的默认实现。ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。ThreadPoolExecutor咱们来看一下ThreadPoolExecutor的具体实现: 在ThreadPoolExecutor中有四个构造方法 public class ThreadPoolExecutor extends AbstractExecutorService { /** * Core pool size is the minimum number of workers to keep alive 外围线程数量是维持线程池存活的最小数量,而且不容许超时,除非设置allowCoreThreadTimeOut,在这种状况下最小值为0 * (and not allow to time out etc) unless allowCoreThreadTimeOut * is set, in which case the minimum is zero. */ private volatile int corePoolSize; /** * Maximum pool size. Note that the actual maximum is internally 最大线程数量,留神,理论最大数量受容量限度 * bounded by CAPACITY. */ private volatile int maximumPoolSize; /** * The queue used for holding tasks and handing off to worker 用于保留工作和移交给工作线程 * threads. We do not require that workQueue.poll() returning * null necessarily means that workQueue.isEmpty(), so rely * solely on isEmpty to see if the queue is empty (which we must * do for example when deciding whether to transition from * SHUTDOWN to TIDYING). This accommodates special-purpose * queues such as DelayQueues for which poll() is allowed to * return null even if it may later return non-null when delays * expire. */ private final BlockingQueue<Runnable> workQueue; /** * Timeout in nanoseconds for idle threads waiting for work. 闲暇线程的期待超时工夫,当线程数量超过corePoolSize或者allowCoreThreadTimeOut时应用,否则永远期待新的工作 * Threads use this timeout when there are more than corePoolSize * present or if allowCoreThreadTimeOut. Otherwise they wait * forever for new work. */ private volatile long keepAliveTime; /** * Factory for new threads. All threads are created using this 创立线程的工厂 * factory (via method addWorker). All callers must be prepared * for addWorker to fail, which may reflect a system or user's * policy limiting the number of threads. Even though it is not * treated as an error, failure to create threads may result in * new tasks being rejected or existing ones remaining stuck in * the queue. * * We go further and preserve pool invariants even in the face of * errors such as OutOfMemoryError, that might be thrown while * trying to create threads. Such errors are rather common due to * the need to allocate a native stack in Thread.start, and users * will want to perform clean pool shutdown to clean up. There * will likely be enough memory available for the cleanup code to * complete without encountering yet another OutOfMemoryError. */ private volatile ThreadFactory threadFactory; /** * Handler called when saturated or shutdown in execute. 回绝策略,线程饱和或敞开时调用的处理程序 */ private volatile RejectedExecutionHandler handler; //应用指定参数创立线程池,应用默认线程工厂和回绝策略 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } //应用指定参数创立线程池,应用默认回绝策略 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); } //应用指定参数创立线程池,应用默认线程工厂 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); } //应用指定参数创立线程池 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }}corePoolSize:外围池的大小,这个参数跟前面讲述的线程池的实现原理有十分大的关系。在创立了线程池后,默认状况下,线程池中并没有任何线程,而是期待有工作到来才创立线程去执行工作,除非调用了prestartAllCoreThreads()或者prestartCoreThread()办法,从这2个办法的名字就能够看出,是预创立线程的意思,即在没有工作到来之前就创立corePoolSize个线程或者一个线程。默认状况下,在创立了线程池后,线程池中的线程数为0,当有工作来之后,就会创立一个线程去执行工作,当线程池中的线程数目达到corePoolSize后,就会把达到的工作放到缓存队列当中;maximumPoolSize:线程池最大线程数,这个参数也是一个十分重要的参数,它示意在线程池中最多能创立多少个线程;keepAliveTime:示意线程没有工作执行时最多放弃多久工夫会终止。默认状况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程闲暇的工夫达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。然而如果调用了allowCoreThreadTimeOut(boolean)办法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;unit:参数keepAliveTime的工夫单位workQueue:一个阻塞队列,用来存储期待执行的工作,这个参数的抉择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种抉择 ...

October 21, 2020 · 8 min · jiezi

关于多线程:多线程3Lock

上一篇中说过了synchronized和它的应用范畴,看起来曾经能够解决所有状况了,那为什么还须要Lock呢? Lock和synchronized上篇中说到,被synchronized润饰的代码块只能由获取锁的线程执行,在开释锁之前,其余线程只能期待,这样就很影响效率,比方,一份文件不能同时写,然而容许同时读,这种状况synchronized就无奈实现,此时须要一个机制使多个线程能够同时执行读操作。 留神:Lock须要手动去开释,如果在执行完没有开释锁,就有可能造成死锁景象,synchronized不须要用户去手动开释锁,当synchronized办法或者synchronized代码块执行完之后,零碎会主动让线程开释对锁的占用。 可重入锁可重入锁,可重入就是说某个线程曾经取得某个锁,能够再次获取锁而不会呈现死锁,可重入锁有以下两种: synchronizedReentrantLocksynchronizedpublic class ReentrantLockDemo { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { synchronized (this) { System.out.println("第1次获取锁,这个锁是:" + this); int index = 1; while (true) { synchronized (this) { System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this); } if (index == 10) { break; } } } } }).start(); }}运行后果 每次获取的都是同一个锁 第1次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第2次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第3次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第4次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第5次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第6次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第7次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第8次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第9次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81第10次获取锁,这个锁是:com.example.offer.thread.lock.ReentrantLockDemo$1@3cc49e81ReentrantLock锁定和开释次数雷同public class ReentrantLockDemo2 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("线程1第1次获取锁,这个锁是:" + lock); int index = 1; while (true) { try { lock.lock(); System.out.println("线程1第" + (++index) + "次获取锁,这个锁是:" + lock); if (index == 10) { break; } } finally { //ReentrantLock不会主动开释,须要手动开释锁,否则会有死锁问题 lock.unlock(); } } } finally { lock.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("线程2第1次获取锁,这个锁是:" + lock); } finally { lock.unlock(); } } }).start(); }}运行后果 ...

October 21, 2020 · 4 min · jiezi

关于多线程:多线程2synchronized和volatile

多线程极大的晋升了效率,然而也带来了隐患,比方两个线程同时操作一个对象,就可能造成数据异样。 为什么会呈现线程平安问题以下是一个线程不平安的程序,运行后果有时是10000,有时比10000小,而且每次都可能不同,这就是线程不平安,因为count++的操作不是原子性的,分为三步,读改写,即先读取数据,在执行批改操作(+1),再将数据回写到内存中,但这样就会呈现问题,比方线程一和线程二获取到了数据10,而后线程二进行批改和回写操作,将数据变为11,此时线程一开始执行,然而此时线程一读取到的数据还是10而部署线程二批改后的11,这样就会造成线程一和二各做一次减少操作,然而count从10变为了11,即产生了数据异样。 public class ThreadUnsafeDemo { private static int count; private static class Thread1 extends Thread { public void run() { for (int i = 0; i < 5000; i++) { count++; } } } public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); Thread1 t2 = new Thread1(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); }}那么为什么会呈现线程不平安问题,次要有三点 原子性:一个或者多个操作在 CPU 执行的过程中被中断(CPU上下文切换)可见性:一个线程对共享变量的批改,另外一个线程不能立即看到有序性:程序执行的程序没有依照代码的先后顺序执行前两点后面曾经举例了,当初在解释一下第三点。为什么程序执行的程序会和代码的执行程序不统一呢?java平台包含两种编译器:动态编译器(javac)和动静编译器(jit:just in time)。动态编译器是将.java文件编译成.class文件(二进制文件),之后便能够解释执行。动静编译器是将.class文件编译成机器码,之后再由jvm运行。问题个别会呈现在动静编译器上,因为动静编译器为了程序的整体性能会对指令进行重排序,尽管重排序能够晋升程序的性能,然而重排序之后会导致源代码中指定的内存拜访程序与理论的执行程序不一样,就会呈现线程不平安的问题。如何保障线程平安针对原子性问题,JDK中有atomic类,这些类自身通过CAS保障操作的原子性。 针对可见性问题,能够通过synchronized关键字加锁来解决。与此同时,java还提供了一种轻量级的锁,即volatile关键字,要优于synchronized的性能,同样能够保障批改对其余线程的可见性。volatile个别用于对变量的写操作不依赖于以后值的场景中,比方状态标记量等。 针对有序性问题,能够通过synchronized关键字定义同步代码块或者同步办法保障有序性,另外也能够通过Lock接口保障有序性。 ...

October 21, 2020 · 6 min · jiezi

关于多线程:多线程1线程基础

如何创立线程Java中,创立线程的话,个别有两种形式 继承Thread类实现Runnable接口继承Thread类 public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); }public class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } @Override public void run() { System.out.println("name:" + name + " 子线程ID:" + Thread.currentThread().getId()); }}后果 主线程ID:1name:thread2 子线程ID:1name:thread1 子线程ID:12start():先来看看Java API中对于该办法的介绍:使该线程开始执行;Java 虚拟机调用该线程的 run 办法。后果是两个线程并发地运行;以后线程(从调用返回给 start 办法)和另一个线程(执行其 run 办法)。屡次启动一个线程是非法的。特地是当线程曾经完结执行后,不能再重新启动,用start办法来启动线程,真正实现了多线程运行,这时无需期待run办法体中的代码执行结束而间接继续执行后续的代码。通过调用Thread类的 start()办法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦失去cpu工夫片,就开始执行run()办法,这里的run()办法 称为线程体,它蕴含了要执行的这个线程的内容,Run办法运行完结,此线程随即终止。 run():同样先看看Java API中对该办法的介绍:如果该线程是应用独立的 Runnable 运行对象结构的,则调用该 Runnable 对象的 run 办法;否则,该办法不执行任何操作并返回。 Thread 的子类应该重写该办法。run()办法只是类的一个一般办法而已,如果间接调用Run办法,程序中仍然只有主线程这一个线程,其程序执行门路还是只有一条,还是要程序执行,还是要期待run办法体执行结束后才可继续执行上面的代码,这样就没有达到写线程的目标。 ...

October 21, 2020 · 14 min · jiezi

关于多线程:java-手写并发框架一异步查询转同步的-7-种实现方式

序言本节将学习一下如何实现异步查问转同步的形式,共计介绍了 7 种常见的实现形式。 思维导图如下: 异步转同步业务需要有些接口查问反馈后果是异步返回的,无奈立即获取查问后果。 比方业务开发中咱们调用其余零碎,然而后果的返回的确告诉的。 或者 rpc 实现中,client 调用 server 端,后果也是异步返回的,那么如何同步获取调用后果呢? 失常解决逻辑触发异步操作,而后传递一个惟一标识。 等到异步后果返回,依据传入的惟一标识,匹配此次后果。 如何转换为同步失常的利用场景很多,然而有时候不想做数据存储,只是想简略获取调用后果。 即想达到同步操作的后果,怎么办呢? 思路发动异步操作在异步后果返回之前,始终期待(能够设置超时)后果返回之后,异步操作后果对立返回常见的实现形式循环期待wait & notify应用条件锁应用 CountDownLatch应用 CyclicBarrierFutureSpring EventListener上面咱们一起来学习下这几种实现形式。 循环期待阐明循环期待是最简略的一种实现思路。 咱们调用对方一个申请,在没有后果之前始终循环查问即可。 这个后果能够在内存中,也能够放在 redis 缓存或者 mysql 等数据库中。 代码实现定义形象父类为了便于前面的其余几种实现形式对立,咱们首先定义一个形象父类。 /** * 形象查问父类 * @author binbin.hou * @since 1.0.0 */public abstract class AbstractQuery { private static final Log log = LogFactory.getLog(AbstractQuery.class); protected String result; public void asyncToSync() { startQuery(); new Thread(new Runnable() { public void run() { remoteCall(); } }).start(); endQuery(); } protected void startQuery() { log.info("开始查问..."); } /** * 近程调用 */ protected void remoteCall() { try { log.info("近程调用开始"); TimeUnit.SECONDS.sleep(5); result = "success"; log.info("近程调用完结"); } catch (InterruptedException e) { log.error("近程调用失败", e); } } /** * 查问完结 */ protected void endQuery() { log.info("实现查问,后果为:" + result); }}代码实现实现还是非常简单的,在没有后果之前始终循环。 ...

October 9, 2020 · 7 min · jiezi

关于多线程:编程体系结构05Java多线程并发

本文源码:GitHub·点这里 || GitEE·点这里 一、多线程导图 二、多线程根底1、根底概念 线程是操作系统可能进行运算调度的最小单位,蕴含在过程之中,是过程中的理论运作单位。一条线程指的是过程中一个繁多程序的控制流,一个过程中能够并发多个线程,每条线程并行执行不同的工作。 2、创立形式 继承Thread类、实现Runnable接口、基于Callable和Future接口、Timer是后盾线程、线程池。 3、线程状态 状态形容:初始状态、运行状态、阻塞状态、期待状态、超时期待状态、终止状态。 4、执行机制 JVM中一个利用是能够有多个线程并行执行,线程被一对一映射为服务所在操作系统线程,调度在可用的CPU上执行,启动时会创立一个操作系统线程;当该线程终止时,这个操作系统线程也会被回收。 5、内存模型 在虚拟机启动运行时,会创立多个线程,数据区中有的模块是线程共享的,有的是线程公有的: 线程共享:元数据区、堆Heap; 线程公有:虚拟机栈、本地办法栈、程序计数器; 单个CPU在特定时刻只能执行一个线程,所以多线程通过几块空间的应用,而后一直的争抢CPU的执行时间段。 三、常见概念1、线程优先级 线程调度器偏向执行线程优先级高的线程,线程优先级高阐明获取CPU资源的概率高,或者获取的执行工夫分片多,被执行的概率高但不代表优先级低的肯定最初执行。 2、守护线程 守护线程是反对辅助型线程,次要在程序中起到调度和支持性作用,当Jvm中非守护线程全副完结,守护线程也就会完结。 3、线程退出 线程A中,执行线程B的退出办法,那么A线程就会期待线程B执行结束再返回继续执行。 4、本地线程 ThreadLocal也叫做线程本地变量,为变量在每个线程中的创立正本,每个线程能够拜访本人外部的正本变量,线程之间互不相互影响。 四、线程平安在上图线程与内存空间的占用形式看,在线程拜访共享内存块时,保障线程平安就很有必要。 1、同步控制 Synchronized关键字同步控制,能够润饰办法,润饰代码块,润饰静态方法等,同步控制的资源少,能够进步多线程效率。 2、加锁机制 Lock接口:Java并发编程中资源加锁的根接口之一,规定了资源锁应用的几个根底办法。 ReentrantLock类:实现Lock接口的可重入锁,即线程如果取得以后实例的锁,并进入工作办法,在线程没有开释锁的状态下,能够再次进入工作办法,特点:互斥排它性,即同一个时刻只有一个线程进入工作。 Condition接口:形容可能会与锁有关联的条件变量,提供了更弱小的性能,例如在线程的期待/告诉机制上,Conditon能够实现多路告诉和选择性告诉。 3、Volatile关键字 volatile润饰成员变量,不能润饰办法,即标识该线程在拜访这个变量时须要从共享内存中获取,对该变量的批改,也须要同步刷新到共享内存中,保障了变量对所有线程的可见性。 五、线程通信线程是个独立的个体,然而在线程执行过程中,如果解决同一个业务逻辑,可能会产生资源争抢,导致并发问题,甚至死锁景象,线程之间协调工作,就须要通信机制来保障。 1、根底办法 相干办法是Java中Object层级的根底办法,任何对象都有该办法:notify()随机告诉一个在该对象上期待的线程,使其完结wait状态返回;wait()线程进入waiting期待状态,不会争抢锁对象,也能够设置等待时间; 2、期待/告诉机制 期待/告诉机制,该模式下指线程A在不满足工作执行的状况下调用对象wait()办法进入期待状态,线程B批改了线程A的执行条件,并调用对象notify()或者notifyAll()办法,线程A收到告诉后从wait状态返回,进而执行后续操作。两个线程通过基于对象提供的wait()/notify()/notifyAll()等办法实现期待和告诉间交互,进步程序的可伸缩性。 3、管道流通信 管道流次要用于在不同线程间间接传送数据,一个线程发送数据到输入管道,另一个线程从输出管道中读取数据,进而实现不同线程间的通信。 六、线程池1、Executor接口 Executor零碎中,将线程工作提交和工作执行进行理解耦的设计,Executor有各种功能强大的实现类,提供便捷形式来提交工作并且获取工作执行后果,封装了工作执行的过程,不再须要Thread().start()形式,显式创立线程并关联执行工作。 2、外围参数 3、相干API类 线程池工作:外围接口:Runnable、Callable接口和接口实现类; 工作的后果:接口Future和实现类FutureTask; 工作的执行:外围接口Executor和ExecutorService接口。在Executor框架中有两个外围类实现了ExecutorService接口,ThreadPoolExecutor和ScheduledThreadPoolExecutor。 七、罕用线程API1、Fork/Join机制 Fork/Join框架用于并行执行工作,外围的思维就是将一个大工作切分成多个小工作,而后汇总每个小工作的执行后果失去这个大工作的最终后果。外围流程:切分工作,模块工作异步执行,单任务后果合并。 2、容器类 ConcurrentHashMap:应用分段锁机制,把容器中数据分成一段一段的形式存储,而后给每一段数据配一把锁,当一个线程占用锁拜访其中一个段数据的时候,其余段的数据也能被其余线程拜访,即思考安全性也顾及执行效率。 ConcurrentLinkedQueue:基于链接节点的无界限程平安队列,依照FIFO先进先出准则对元素进行排序,队列的头部 是队列中工夫最长的元素,队列的尾部是队列中工夫最短的元素,新的元素增加到队列的尾部,获取元素操作从队列头部失去。 3、原子类 JDK自带原子操作类,解决多个线程同时操作一个变量的状况,其中包含:根本类型、数组类型、援用类型、属性批改类型。 八、利用场景1、定时工作 通过配置设置一些程序在指定工夫点,或者周期时间内法则循环执行,这里工作的执行就是基于多线程技术。 2、异步解决 异步解决就是不依照以后同步代码块程序执行,异步解决与同步解决是对抗的,异步的实现也须要多线程或者多过程,进步程序效率。 3、工作合成 ...

October 3, 2020 · 1 min · jiezi

关于多线程:3线程一共有哪些状态呢二

在上一章中介绍了使线程进入timed_waiting状态的3种办法 1. Thread.sleep(long)2. Object.wait(long)3. Thread.join(long)接下来持续实战使线程进入BLOCKED状态的办法 synchronized会使线程进入BLOCKED状态/** * synchronized线程处于timed_waiting状态 * * @author jinglei * @date 2020/8/21 13:09 */public class TimedWaitingBySync { private static Object lock = new Object(); public static void main(String[] args) { Thread syncLongThread = new Thread(new Runnable() { @Override public void run() { synchronized (lock){ System.out.println("子线程无奈进入同步代码块"); } } }); syncLongThread.setName("mythread-sync"); //启动线程 syncLongThread.start(); //留神,这个中央并不一定会保障主线程优先于子线程执行,具体哪个线程先执行是由CPU依据过后的系统资源调度的, // 只管大部分状况可能看到是主线程优先执行 synchronized (lock){ while(true){ } } }}依照如下步骤查看线程堆栈 堆栈内容如下能够看出线程进入了BLOCKED状态,并且是在一个对象锁或者是对象监视器上阻塞,当前看到这种日志就是阐明此线程须要获取一个对象锁,然而这个锁曾经被其余线程占有了,该线程只能进入阻塞队列期待其余线程开释锁。 谈到synchronized就不得不说下经典的死锁问题,死锁指的是两个及其以上线程在别离持有了相应的锁的同时,还须要对方曾经持有的锁,并且本身曾经持有的锁不会被动去开释,此时就造成了一个死循环,也就是死锁。 现实生活中的例子,比方有一双筷子,李梅和韩雷雷别离领有了一根筷子,他们都不会把本人的筷子给他人还想要对方的筷子,这就形成了死锁。 ...

August 24, 2020 · 1 min · jiezi

关于多线程:3线程一共有哪些状态呢

在上一章节中咱们理解到线程怎么创立和执行。在理论开发过程中多线程是很容易呈现问题的,一不小心可能会导致死锁从而引起整个零碎解体。只有对线程的整个运行过程比拟理解能力及时地去找到问题在哪。不晓得大家有没有想过上面几个问题, 一共有哪些线程状态呢?为什么要理解这些线程状态呢?这些状态怎么查看呢?我置信带着问题去学习是一种更高效的学习形式。接下来我会给小伙伴们一一找到答案。 上图是综合了线程个别状态和JVM中线程状态,其中红框期待timed_waiting、 期待waiting、阻塞blocked都是JVM的线程状态,JVM线程状态在JDK中有一个枚举类java.lang.Thread.State示意: public enum State { /** * 线程还没有启动时的状态 */ NEW, /** * 可运行状态。 */ RUNNABLE, /** * 进入synchronized同步代码块 */ BLOCKED, /** * 执行以下办法进入WAITING状态 * Object.wait * Thread.join * LockSupport.park */ WAITING, /** * 执行以下带有工夫的办法进入TIMED_WAITING状态 * Thread.sleep(long) * Object.wait(long) * Thread.join(long) * LockSupport.parkNanos * LockSupport.parkUntil */ TIMED_WAITING, /** * 终止或者沦亡状态 */ TERMINATED; }上面咱们通过代码和jstack命令演示下不同办法调用对一个的不同状态 sleep(long)会使线程进入timed_waiting状态 /** * 调用sleep(long)线程处于timed_waiting状态 * * @author jinglei * @date 2020/8/21 13:09 */ public class TimedWaitingBySleep { public static void main(String[] args) { //1.sleep Thread sleepThread = new Thread(new Runnable() { @Override public void run() { System.out.println("调用sleep()之后线程处于timed_waiting状态"); try { Thread.currentThread().sleep(50000); } catch (InterruptedException e) { e.printStackTrace(); } } }); sleepThread.setName("mythread-sleep"); //启动线程 sleepThread.start(); } }运行程序,而后通过jps命令找到对应的过程id,再用jstack processId > D:/test/sleepTimedWaiting.log把线程堆栈输入到文件 ...

August 21, 2020 · 1 min · jiezi

关于多线程:2-线程的两种实现方式

1. 线程的两种实现形式继承Thread类//继承Thread类 static class MyThread1 extends Thread{ @Override public void run() { System.out.println("继承Thread实现本人的线程"); } }实现Runnable接口//实现Runnable接口 static class MyThread2 implements Runnable{ @Override public void run() { System.out.println("实现Runnable接口实现本人的线程"); } }2.线程的启动public static void main(String[] args) { //1.通过继承Thread创立本人的线程并且启动 new MyThread1().start(); //2.通过实现Runnable,创立本人的线程并且启动 new Thread(new MyThread2()).start(); //3.通过匿名外部类创立线程 new Thread(new Runnable() { @Override public void run() { System.out.println("匿名外部类实现的线程"); } }).start(); //4.lambda表达式创立线程 new Thread(()->{ System.out.println("通过"); }).start(); }留神 不论用哪种形式实现本人的线程,最终要启动线程必须得通过Thread.start()办法启动,start()办法会把线程交给CPU去调度执行,只有当CPU调度到线程把工夫片调配给它,线程才会执行。

August 20, 2020 · 1 min · jiezi

关于多线程:多线程高并发

@[toc] 章节名称文章地址秋春招总结之MySQLMySQL秋春招总结之RedisRedis秋春招总结之并发多线程并发多线程每周二完定时更新 前言对于Java多线程方面的常识波及宽泛,从最根底的输出一个指令,期待运行实现到批处理操作系统,再到起初过程和线程的提出与纯熟使用到咱们的日常生活中,无疑也是咱们计算机的稳步发展的映照,这篇博客将尽可能的总结目前呈现的一些面试题目曾经本人遇到过的一些题目,心愿在本人总结几个月来遇到的问题的同时也可能进行进一步的深入与升华,每一次的记录也都让我更加记忆深切。 1. 根底过程与线程的区别注:这个题目是学习计算机操作系统必备的题目,也是初步进行了解与把握前面问题的关键所在 粗略答复:过程是操作系统进行资源分配与调度的根本单位,线程是任务调度与执行的根本单位,即CPU调配工夫单位。他们的本质区别是是否独自占有内存地址空间以及其余系统资源。 区别同一个过程中能够包含多个线程,并且线程共享整个过程的资源(寄存器,堆栈,上下文) 一个过程至多包含一个线程线程是轻量级的过程,同一类线程共享代码和数据空间,每个线程都有本人独立的运行栈和程序计数器,线程之间切换的开销比拟小。 蕴含关系只有一个线程的过程能够看做是单线程的,如果一个过程内有多个线程,在执行的过程是多条(线程)共同完成的; 线程是过程的一部分所以也被称之为轻量级过程。 什么是并发编程的三要素? 在Java中如何来保障多线程的平安运行。三要素: 原子性:就是咱们的程序是一个不可分割的整体,所有的操作要么全副都执行,要么都不执行或者都是失败,不可能说对于一段程序,一部分胜利执行并提交,另外一部分执行失败。有序性:程序的执行在原则上会依照咱们写的程序程序执行上来(然而在有些状况下,为了可能进步解决效率,在满足肯定的条件下,是运行进行指令的重排序)可见性: 利用到JMM (Java内存模型)来实现对共享变量的共享可见(一个线程进行了批改,另一个线程就能够得悉)可能会呈现的问题: 线程切换带来的原子性问题缓存导致的可见性问题编译优化带来的有序性问题解决方案: JDK Atomic结尾的原子类、synchronized、LOCK,能够解决原子性问题synchronized、volatile、LOCK,能够解决可见性问题Happens-Before 规定能够解决有序性问题什么是并行,什么是并发,说一说两者之间的区别:注: 面试中切实遇到过 并行:单位工夫内,多个处理器或多核处理器同时解决多个工作,是真正意义上的“同时进行”。 并发: 多个工作在同一个 CPU 核上,按细分的工夫片轮流(交替)执行,从逻辑上来看那些工作是同时执行(其实只是调配工夫片进行执行)。 串行:有n个工作,由一个线程按程序执行。因为工作、办法都在一个线程执行所以不存在线程不平安状况,也就不存在临界区的问题。 2. 实现Java的多线程对于这个题目来说可能会问道: 你来说一说创立线程有几种的形式?并说一说具体的区别 这里就须要本人来进行比拟充沛的筹备,首先明确创立的几种形式,而后本人实现进行了解与把握。 创立的四种形式:继承 Thread 类;定义一个类,继承Thread,并复写run()办法,对于run办法来说就是实现咱们本人的业务代码。实例化本人创立的类的对象,调用 start()办法(这里必须调用start 办法才算是真正的启动线程)public class Demo { public static class MyThread extends Thread{ public void run(){ System.out.println("Myhread"); } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); System.out.println("MyThread Is Run "); }}这个执行后果大家能够去运行尝试一下,有以下r留神点: ...

August 5, 2020 · 17 min · jiezi

关于多线程:JAVA多线程1线程基础

如何创立线程Java中,创立线程的话,个别有两种形式 继承Thread类实现Runnable接口继承Thread类 public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); }public class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } @Override public void run() { System.out.println("name:" + name + " 子线程ID:" + Thread.currentThread().getId()); }}后果 主线程ID:1name:thread2 子线程ID:1name:thread1 子线程ID:12start():先来看看Java API中对于该办法的介绍:使该线程开始执行;Java 虚拟机调用该线程的 run 办法。后果是两个线程并发地运行;以后线程(从调用返回给 start 办法)和另一个线程(执行其 run 办法)。屡次启动一个线程是非法的。特地是当线程曾经完结执行后,不能再重新启动,用start办法来启动线程,真正实现了多线程运行,这时无需期待run办法体中的代码执行结束而间接继续执行后续的代码。通过调用Thread类的 start()办法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦失去cpu工夫片,就开始执行run()办法,这里的run()办法 称为线程体,它蕴含了要执行的这个线程的内容,Run办法运行完结,此线程随即终止。 run():同样先看看Java API中对该办法的介绍:如果该线程是应用独立的 Runnable 运行对象结构的,则调用该 Runnable 对象的 run 办法;否则,该办法不执行任何操作并返回。 Thread 的子类应该重写该办法。run()办法只是类的一个一般办法而已,如果间接调用Run办法,程序中仍然只有主线程这一个线程,其程序执行门路还是只有一条,还是要程序执行,还是要期待run办法体执行结束后才可继续执行上面的代码,这样就没有达到写线程的目标。 ...

August 3, 2020 · 14 min · jiezi

关于多线程:JAVA多线程1线程基础

如何创立线程Java中,创立线程的话,个别有两种形式 继承Thread类实现Runnable接口继承Thread类 public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); }public class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } @Override public void run() { System.out.println("name:" + name + " 子线程ID:" + Thread.currentThread().getId()); }}后果 主线程ID:1name:thread2 子线程ID:1name:thread1 子线程ID:12start():先来看看Java API中对于该办法的介绍:使该线程开始执行;Java 虚拟机调用该线程的 run 办法。后果是两个线程并发地运行;以后线程(从调用返回给 start 办法)和另一个线程(执行其 run 办法)。屡次启动一个线程是非法的。特地是当线程曾经完结执行后,不能再重新启动,用start办法来启动线程,真正实现了多线程运行,这时无需期待run办法体中的代码执行结束而间接继续执行后续的代码。通过调用Thread类的 start()办法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦失去cpu工夫片,就开始执行run()办法,这里的run()办法 称为线程体,它蕴含了要执行的这个线程的内容,Run办法运行完结,此线程随即终止。 run():同样先看看Java API中对该办法的介绍:如果该线程是应用独立的 Runnable 运行对象结构的,则调用该 Runnable 对象的 run 办法;否则,该办法不执行任何操作并返回。 Thread 的子类应该重写该办法。run()办法只是类的一个一般办法而已,如果间接调用Run办法,程序中仍然只有主线程这一个线程,其程序执行门路还是只有一条,还是要程序执行,还是要期待run办法体执行结束后才可继续执行上面的代码,这样就没有达到写线程的目标。 ...

August 3, 2020 · 14 min · jiezi

关于多线程:深入浅出Node异步IO

异步IO## 一、为什么要应用异步I/O用户体验 javascript在单线程上执行,它与UI线程是一个线程,如果应用同步,当javascript在执行的时候UI渲染就必须要进行期待,这样就是的用户的体验极差。 如果网页须要申请一些资源,通过同步的形式获取的话,那么咱们就必须要期待js从服务器齐全获取到资源后再继续执行,这期间UI期待,就会使得与用户的交互极差,影响用户的体验感。 // 当初申请两个资源//耗时为M毫秒getData('from_db');//耗时为N毫秒getData('from_remote_api');随着利用的复杂性,情景会变成M+N+…和Max(M,N,…),此时同步和异步的优劣就会更加凸显。另一方面,随着网站和利用的扩大,数据往往会散布到多台服务器上,而散布意味着M和N的值会线性增长,这也会放大异步和同步在性能上的差别。总之,IO是低廉的,分布式IO是更低廉的! 资源分配 单线程同步IO 会因阻塞IO使得硬件资源无奈失去更优的利用。多线程编程 长处: 能够利用多核CPU无效晋升CPU的利用率 毛病: 编程中的死锁、状态同步使得程序员很是头疼。node的异步IO node采纳的异步IO,利用单线程,远离了多线程死锁、状态同步,利用异步让单线程远离了阻塞,使得CPU失去更好的利用。 为了补救单线程无奈利用多核CPU的问题,Node提供了子过程 childProcess ,将一些运算多的工作放入子过程进行高效的运算。 二、阻塞I/O 和 非阻塞 I/O阻塞IO 阻塞的IO操作就是发动IO操作后,线程阻塞期待IO实现,这期间cpu得不到无效利用。 非阻塞IO 非阻塞IO操作其实就是发动IO操作后,通过事件轮巡,或者事件告诉机制,一直查问IO操作是否实现,或者是主线程进入休眠期待事件告诉IO完结,而后持续向下执行代码,实际上非阻塞IO期间,cpu要不用来查问要不用来休眠,也没有失去无效利用。仍旧是同步IO。 三、node的异步I/O实现整个异步IO须要单个环节 事件循环 观察者 申请对象。 实际上node的异步IO是采纳了线程池技术,发动异步IO时,把io操作扔到线程池外面执行,而后主线程继续执行其余操作,io执行结束通过线程间通信告诉主线程,主线程执行回调。 IO线程是由Libuv(Linux下由libeio具体实现;window下则由IOCP具体实现)治理的线程池管制的,实质上是多线程。即采纳了线程池与阻塞IO模仿了异步IO。 异步IO原理 当遇到IO时,将其放入线程池中的一个IO线程,让该工作在IO线程上执行,在IO线程上是阻塞IO形式执行的,而后在主线程上继续执行,当遇到另一个IO工作时,将其在放入线程池,而后在另一条IO线程上执行(同样是阻塞IO形式),主线程继续执行着。 1、事件循环 正式因为事件循环使得回调函数十分的广泛。 node过程启动的时候,会创立一个相似的 while(1) 循环,每执行顺次循环的过程被称为是 Tick。Tick过程就是查看当下是否有待解决的事件,如果有则取出相干事件以及回调函数,而后执行回调函数(在主线程中执行)。而后进入下一个循环,持续检测如果不再有事件处理,就推出。 当I/O线程上的工作(阻塞I/O)执行结束之后,就会产生一个事件,这就是事件循环中的事件的产生由来。 2. 观察者 Node中事件的次要起源是网络申请和文件IO等。这些事件对应的观察者就是网络I/0观察者、文件I/0观察者。 3. 申请对象 在从js发动调用起到内核执行I/O结束之后。这个两头过程有一种两头产物,称为是申请对象。 以关上文件为例子 *0. 异步调用工作1. js调用外围模块2. 外围模块调用C++内建模块3. 内建模块在`libuv`层,分平台解决。本质上调用的都是`uv_fs_open`办法。4. 在调用的过程中,创立一个`FSReqWrap`申请对象。【这就是咱们的配角申请对象了】5. 对象创立结束后,设置好参数和回调函数,就会将其推入线程池中期待执行了。6. js线程继续执行后续的工作,以后的IO操作在线程池中执行,不论IO线程上是阻塞还是非阻塞,都不会影响主线程的执行,因而这就达到了异步的目标了。到这里其实就实现了异步IO的第一步了,回调告诉则是第二步。4. 执行回调 当IO线程中的工作执行结束后,就会将执行后果放在申请对象中。而后告诉TOCP。TOCP查看工作是否实现。如果实现了就将I/O申请对象退出观察者队列中,当作事件处理。而后通过事件循环来执行回调函数。 留神: Windows下是TOCP ,Linux下是通过epoll。*四、 node的非I/O的异步API1. 定时器 定时器的实现原理同异步IO,只是没有应用线程池。 setTimeout() ...

July 31, 2020 · 1 min · jiezi

关于多线程:MPMCQueue源码分析上

github地址:https://github.com/rigtorp/MP... 对于__cpp_lib_hardware_interference_size这个功能测试宏示意了c++17新引入的feature : https://zh.cppreference.com/w...相干:https://www.it1352.com/178422...其含意是两个对象间防止假数据共享(false sharing,也被称为伪共享)的最小偏移量。对于false sharing : https://www.cnblogs.com/cyfon... #ifdef __cpp_lib_hardware_interference_sizestatic constexpr size_t hardwareInterferenceSize = std::hardware_destructive_interference_size;#elsestatic constexpr size_t hardwareInterferenceSize = 64;#endif下面这段代码即获取了以后硬件条件下的防止伪共享的最小偏移量,如果编译器还没有实现这个feature,则自定义为64字节。(支流的硬件架构cache line 大小为64字节 : https://blog.csdn.net/midion9...) 为了防止伪共享的产生,让不同对象处于不同的缓存行即可,也就是设置偏移量为缓存行大小。 对于__cpp_aligned_newc++17引入的新feature,具体信息见: https://www.jianshu.com/p/7ce...其作用是提供内存对齐版本的new运算符。 #if defined(__cpp_aligned_new)template <typename T> using AlignedAllocator = std::allocator<T>;#elsetemplate <typename T> struct AlignedAllocator { using value_type = T; T *allocate(std::size_t n) { if (n > std::numeric_limits<std::size_t>::max() / sizeof(T)) { throw std::bad_array_new_length(); }#ifdef _WIN32 auto *p = static_cast<T*>(_aligned_malloc(sizeof(T) * n, alignof(T))); if (p == nullptr) { throw std::bad_alloc(); }#else T *p; if (posix_memalign(reinterpret_cast<void **>(&p), alignof(T), sizeof(T) * n) != 0) { throw std::bad_alloc(); }#endif return p; } void deallocate(T *p, std::size_t) {#ifdef WIN32 _aligned_free(p);#else free(p);#endif }};#endif如果编译器实现了aligned new的feature,则间接实现规范库版本的内存分配器,否则自定义一个,依据平台不同应用_aligned_malloc/posix_memalign实现。 ...

July 27, 2020 · 1 min · jiezi

关于多线程:MPMCQueue源码分析上

github地址:https://github.com/rigtorp/MP... 对于__cpp_lib_hardware_interference_size这个功能测试宏示意了c++17新引入的feature : https://zh.cppreference.com/w...相干:https://www.it1352.com/178422...其含意是两个对象间防止假数据共享(false sharing,也被称为伪共享)的最小偏移量。对于false sharing : https://www.cnblogs.com/cyfon... #ifdef __cpp_lib_hardware_interference_sizestatic constexpr size_t hardwareInterferenceSize = std::hardware_destructive_interference_size;#elsestatic constexpr size_t hardwareInterferenceSize = 64;#endif下面这段代码即获取了以后硬件条件下的防止伪共享的最小偏移量,如果编译器还没有实现这个feature,则自定义为64字节。(支流的硬件架构cache line 大小为64字节 : https://blog.csdn.net/midion9...) 为了防止伪共享的产生,让不同对象处于不同的缓存行即可,也就是设置偏移量为缓存行大小。 对于__cpp_aligned_newc++17引入的新feature,具体信息见: https://www.jianshu.com/p/7ce...其作用是提供内存对齐版本的new运算符。 #if defined(__cpp_aligned_new)template <typename T> using AlignedAllocator = std::allocator<T>;#elsetemplate <typename T> struct AlignedAllocator { using value_type = T; T *allocate(std::size_t n) { if (n > std::numeric_limits<std::size_t>::max() / sizeof(T)) { throw std::bad_array_new_length(); }#ifdef _WIN32 auto *p = static_cast<T*>(_aligned_malloc(sizeof(T) * n, alignof(T))); if (p == nullptr) { throw std::bad_alloc(); }#else T *p; if (posix_memalign(reinterpret_cast<void **>(&p), alignof(T), sizeof(T) * n) != 0) { throw std::bad_alloc(); }#endif return p; } void deallocate(T *p, std::size_t) {#ifdef WIN32 _aligned_free(p);#else free(p);#endif }};#endif如果编译器实现了aligned new的feature,则间接实现规范库版本的内存分配器,否则自定义一个,依据平台不同应用_aligned_malloc/posix_memalign实现。 ...

July 27, 2020 · 1 min · jiezi

ThreadLocal底层原理学习

1. 是什么?首先ThreadLocal类是一个线程数据绑定类, 有点类似于HashMap<Thread, 你的数据> (但实际上并非如此), 它所有线程共享, 但读取其中数据时又只能是获取线程自己的数据, 写入也只能给线程自己的数据 2. 怎么用?public class ThreadLocalDemo { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { threadLocal.set("zhazha" + Thread.currentThread().getName()); String s = threadLocal.get(); System.out.println("threadName = " + Thread.currentThread().getName() + " [ threadLocal = " + threadLocal + "\t data = " + s + " ]"); }, "threadName" + i).start(); } }}从他的输入来看, ThreadLocal是同一个, 数据存的是线程自己的名字, 所以和threadName是一样的名称 ...

June 28, 2020 · 4 min · jiezi