关于java并发:并发基础第01章实现线程的正确方式

1. 问题的引出:实现线程有几种形式?2种?5种?正确答案:两种 实现Runnable接口继承Thread类1.1 Thread类中的run()Thread类的源码: private Runnable target;@Overridepublic void run() { if (target != null) { target.run(); }}Thread类有个Runnable的target援用,如果结构器传入了target且不为null,就执行它的run();但前提是它有机会执行--什么意思呢?1.2 既实现了Thread又实现了Runnable接口执行谁的run()且看如下一段代码:MyTask类是Runnable接口的实现;MyThread是Thread类的子类;如果初始化MyThread时把MyTask作为Runnable传入到target,会执行谁的run()? package com.niewj.basic.createthread;/** * 既实现Runnable又继承Thread * * @author niewj * @description * @copyright © 2022 niewj.com * @department 研发 * @date 2023/1/3 23:14 */public class RunnableThread { public static void main(String[] args) { // 1. 实现了Runnable MyTask task = new MyTask(); // 2. 实现了Runnable,也继承了Thread, thread2输入什么? Thread thread = new MyThread(task); thread.start(); } // MyThread是继承Thread的类 static class MyThread extends Thread { public MyThread(Runnable runnable) { super(runnable); } @Override public void run() { System.out.println("==============MyThread.run()===="); } } // MyTask是实现Runnable的接口 static class MyTask implements Runnable { @Override public void run() { System.out.println("==============Runnable.run()===="); } }}执行后果如下:可见执行了Thread的run()而不是Runnable的;这里比拟蛊惑的是,不是说target!=null就执行它的run()吗?要害是这里不是判断target的问题,而是整个Thread子类的run()办法被子类笼罩了,没有机会执行到MyThread父类Thread的run()办法了,这才是要害!==============MyThread.run()====1.3 简写成如下的代码再了解一遍:package com.niewj.basic.createthread;/** * 既实现Runnable又继承Thread * * @author niewj * @description * @copyright © 2022 niewj.com * @department 研发 * @date 2023/1/3 23:14 */public class RunnableThread { public static void main(String[] args) { // 1. 实现了Runnable Thread thread = new Thread(() -> System.out.println("==============Runnable.run()====")) { @Override public void run() { System.out.println("==============MyThread.run()===="); } }; // 2. 实现了Runnable,也继承了Thread, thread2输入什么? thread.start(); }}控制台: ...

January 4, 2023 · 1 min · jiezi

关于java并发:从零开始自己动手写阻塞队列

从零开始本人入手写阻塞队列前言在咱们平时编程的时候一个很重要的工具就是容器,在本篇文章当中次要给大家介绍阻塞队列的原理,并且在理解原理之后本人入手实现一个低配版的阻塞队列。 需要剖析在后面的两篇文章ArrayDeque(JDK双端队列)源码深度分析和深刻分析(JDK)ArrayQueue源码当中咱们认真介绍了队列的原理,如果大家感兴趣能够查看一下! 而在本篇文章所谈到的阻塞队列当中,是在并发的状况下应用的,下面所谈到的是队列是并发不平安的,然而阻塞队列在并发下状况是平安的。阻塞队列的次要的需要如下: 队列根底的性能须要有,往队列当中放数据,从队列当中取数据。所有的队列操作都要是并发平安的。当队列满了之后再往队列当中放数据的时候,线程须要被挂起,当队列当中的数据被取出,让队列当中有空间的时候线程须要被唤醒。当队列空了之后再往队列当中取数据的时候,线程须要被挂起,当有线程往队列当中退出数据的时候被挂起的线程须要被唤醒。在咱们实现的队列当中咱们应用数组去存储数据,因而在构造函数当中须要提供数组的初始大小,设置用多大的数组。阻塞队列实现原理线程阻塞和唤醒在下面咱们曾经谈到了阻塞队列是并发平安的,而且咱们还有将线程唤醒和阻塞的需要,因而咱们能够抉择可重入锁ReentrantLock保障并发平安,然而咱们还须要将线程唤醒和阻塞,因而咱们能够抉择条件变量Condition进行线程的唤醒和阻塞操作,在Condition当中咱们将会应用到的,次要有以下两个函数: signal用于唤醒线程,当一个线程调用Condition的signal函数的时候就能够唤醒一个被await函数阻塞的线程。await用于阻塞线程,当一个线程调用Condition的await函数的时候这个线程就会阻塞。数组循环应用因为队列是一端进一端出,因而队列必定有头有尾。 当咱们往队列当中退出一些数据之后,队列的状况可能如下: 在上图的根底之上咱们在进行四次出队操作,后果如下: 在下面的状态下,咱们持续退出8个数据,那么布局状况如下: 咱们晓得上图在退出数据的时候不仅将数组后半局部的空间应用完了,而且能够持续应用前半部分没有应用过的空间,也就是说在队列外部实现了一个循环应用的过程。 为了保障数组的循环应用,咱们须要用一个变量记录队列头在数组当中的地位,用一个变量记录队列尾部在数组当中的地位,还须要有一个变量记录队列当中有多少个数据。 代码实现成员变量定义依据下面的剖析咱们能够晓得,在咱们本人实现的类当中咱们须要有如下的类成员变量: // 用于爱护临界区的锁private final ReentrantLock lock;// 用于唤醒取数据的时候被阻塞的线程private final Condition notEmpty;// 用于唤醒放数据的时候被阻塞的线程private final Condition notFull;// 用于记录从数组当中取数据的地位 也就是队列头部的地位private int takeIndex;// 用于记录从数组当中放数据的地位 也就是队列尾部的地位private int putIndex;// 记录队列当中有多少个数据private int count;// 用于寄存具体数据的数组private Object[] items;构造函数咱们的构造函数也很简略,最外围的就是传入一个数组大小的参数,并且给下面的变量进行初始化赋值。 @SuppressWarnings("unchecked")public MyArrayBlockingQueue(int size) { this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.notFull = lock.newCondition(); // 其实能够不必初始化 类会有默认初始化 默认初始化为0 takeIndex = 0; putIndex = 0; count = 0; // 数组的长度必定不可能小于0 if (size <= 0) throw new RuntimeException("size can not be less than 1"); items = (E[])new Object[size];}put函数这是一个比拟重要的函数了,在这个函数当中如果队列没有满,则间接将数据放入到数组当中即可,如果数组满了,则须要将线程挂起。 ...

August 13, 2022 · 4 min · jiezi

关于java并发:Java并发笔记03-互斥锁上解决原子性问题

原子性问题的源头是线程切换 Q:如果禁用 CPU 线程切换是不是就解决这个问题了?A:单核 CPU 可行,但到了多核 CPU 的时候,有可能是不同的核在解决同一个变量,即使不切换线程,也有问题。 所以,解决原子性的要害是「同一时刻只有一个线程解决该变量,也被称为互斥」。 如何做到呢?用「锁」。 一、锁模型一)繁难锁模型个别看到的锁模型长上面这样。 但对于这个模型,会有几个疑难: 锁的是什么?临界区的这一堆代码相干的都被锁了?爱护的又是什么? 二)改良后的锁模型用上面这个模型来解释就解答了下面几个问题: 要爱护的是临界区中的资源 R因而要为 R 创立一个对应的锁 LR须要解决资源 R 的时候先加锁,解决完之后解锁 一个资源必须和锁对应,不能用 A 锁去锁 B 资源二、Java 提供的锁技术Java 提供了多种技术,这里仅谈及 Synchronized。 Synchronized 关键字Java 语言提供的 synchronized 关键字,就是锁的一种实现。synchronized 关键字能够用来润饰办法,也能够用来润饰代码块。 class X { // 润饰非静态方法 synchronized void foo() { // 临界区 } // 润饰静态方法 synchronized static void bar() { // 临界区 } // 润饰代码块 Object obj = new Object(); void baz() { synchronized(obj) { // 临界区 } }} Q:synchronized 没看到 lock 和 unlock?A:在编译的时候会做转换,synchronized起始的中央加锁,完结的中央解锁。 ...

May 14, 2022 · 1 min · jiezi

关于java并发:Java并发编程的艺术一书知秋

前言最近又把《Java并发编程的艺术》这本书,重读了一遍,感觉播种比上一次还大!因为在理论的学校学习中,Java并发这一块,其实并没有很罕用,然而恰好不巧的事,在企业中在理论生产中,并发编程是不可或缺的一块,所以重读这本书我觉得很有必要。最近感悟,学习是一件很高兴的事,每当读《Java并发》《JVM》时,感觉莫名的兴奋,对技术的激情还是有哒!我的《平庸的世界》还没有读完,wuwuw,等忙完这阵子也要补补了。 一、并发编程的挑战1、上下文切换 (无锁并发编程(防止锁竞争),CAS算法,应用起码线程,协程)2、死锁 条件:1、互斥条件 2、不可剥夺条件 3、申请与放弃条件 4、循环期待条件解决:1防止同时取得多个锁 2防止一个锁内应用多个资源 3尝试应用定时锁 4数据库锁加锁和解锁在一个链接里Java:按序加锁、加锁时限、死锁检测(保护一个数据结构)3、资源限度(硬件(上传下载 读写)(集群) 软件(连接数)(池化复用)) 二、Java并发机制的底层实现1、volatile ①Lock前缀指令会引起处理器缓存写会主内存②处理器缓存写会主内存会使其余处理器的缓存生效2、synchronized 1.6后优化 无锁: 偏差锁标识为0,则CAS获取偏差锁偏差锁:偏差锁标识为1,锁对象记录线程ID,下次该线程获取时间接测试,失败则CAS尝试锁记录指向以后线程ID敞开偏差锁,间接进入轻量级锁轻量级锁:加锁:以后线程的栈帧中创立空间,对象头的MarkWord复制该空间,CAS尝试对象图的MW指向该空间,胜利则取得锁,失败则自旋获取锁;解锁:CAS操作将栈帧中的锁记录Displaced MarkWoed替换回对象头,胜利即解锁,失败则有竞争,收缩为重量级锁,阻塞竞争线程,本人开释锁并唤醒竞争线程重量级锁:竞争线程阻塞,解锁后唤醒竞争线程3、原子操作 锁(CAS形式开释获取锁)自旋CAS(ABA(版本号)、循环开销、只保障一个共享变量的原子操作)三、Java内存模型Java并发采纳共享内存模式(消息传递模式)隐式通信显示同步主内存存储共享变量 每个线程有本地内存保留主内存的共享变量正本四、Java并发根底 1、过程资源分配的最小单位、线程程序执行的最小单位 2、线程的状态 new->runnable(running、ready)->terminated waiting|time_waiting|blocked 3、中断线程interrupt 而不应用stop(不开释资源);也可在线程外部应用一个boolean变量 4、线程先获取监视器对象,获取失败则进入SynchronizedQueue wait->进入WaitQueue->notify->SynchronizedQueue? |5、t.join 以后线程进入wating态期待t线程执行完 波及期待告诉机制 给t加锁 让以后线程期待,t完结时唤醒以后线程 6、ThreadLocal线程本地变量 数据库连贯治理保障以后线程操作的Connection都为同一个 ThreadLocal.set会在thread中ThreadLocalMap保留值,以ThreadLocal为键,value为值 ThreadLocal.get会获取thread中的ThreadLocalMap以ThreadLocal取值 ThreadLocalMap是ThreadLocal的外部类,被thread援用五、Java中的锁 1、Lock接口 非阻塞获取锁、超时获取锁、响应中断的获取锁 2、AQS 1定义同步组件,实现AQS,重写相应办法,同步组件调用AQS的模板办法即可 2依赖同步队列实现同步状态的治理 同步队列中有节点保留获取同步线程失败的线程援用,期待状态 同步器蕴含头尾节点,首节点是获取同步状态胜利的节点,开释同步状态时唤醒后继节点并设置为首节点 3、重入锁:反复加锁而不被本人阻塞 ReentrantLock|Synchronized 偏心与非偏心锁:是否申请程序 ReentrantLock 4、读写锁ReentrantReadWriteLock 一个读多个写 保护一对读锁写锁 同步整型状态按位切割 锁降级(写->读) 5、LockSupport工具 阻塞唤醒以后线程 6、Condition(Lock.newCondition) 相似wait/notify 作为同步器的外部类,AQS援用多个Condition期待队列 await同步队列首节点->期待队列尾结点 signal期待队列首节点->同步队列首节点六、Java并发容器和框架 1、ConcurrentHashMap JDK7 segment[](继承自ReentantLockd)->数组+链表 get不加锁(volatile润饰) put加分段锁(判断是否扩容,单个Segment扩容) JDK8 数组+链表+红黑树 get不加锁 put时初始化,没有hash抵触间接CAS插入,有就synchronized锁住头结点插入 put操作 1数组为空则进行初始化 2首节点为空则cas直接插入 3须要扩容则帮助扩容 扩容时多线程并发扩容helpTransfer 非凡首节点ForwardingNode示意已扩容过间接跳过 4首节点加synchronized锁put 2、线程平安队列 非阻塞(循环cas)|阻塞(锁) 3、ConcurrentLinkedQueue无界限程平安队列 cas入队尾结点 cas出队列头结点 4、阻塞队列BlockingQueue ArrayBlockingQueue 数组构造的有界阻塞队列(必须指定长度) 不保障偏心拜访 LinkedBlockingQueue 链表构造的有界阻塞队列(可指定,默认长度为int_max) PriorityBlockingQueue 反对优先级的无界阻塞队列(可扩容,长度最大也为int_max(queue为数组实现的堆)) DelayQueue 延时获取元素的无界阻塞队列 SynchronnousQueue 一个put对应一个take不存储元素 LinkedTransferQueue 链表构造的无界阻塞队列 LinkedBlockingQueue 链表构造组成的双向阻塞队列 阻塞队列实现原理 期待告诉模式 condition.await|signal->LockSupport.park|unpark(先保留以后线程再阻塞)->底层park|unpark七、原子操作类 原子更新根本类型 AtomicBoolean AtomicInteger AtomicLong(其余根本类型 转为int型) 以AtomicInteger的getAndIncrement为例 get()获取旧值 新值=旧值+1 cas(旧值,新值)->unsafe的native办法 循环直到胜利 原子更新数组 AtomicIntegerArray AtomicLongArray AtomicReferenceArray 原子更新援用类型 AtomicReference AtomicReferenceFiledUpdatr AtomicMarkableReference 原子更新字段类 AtomicIntegerFiledUpdater AtomicLongFiledUpdater AtomicStampedReference八、Java中的并发工具类 1、CountDownLatch代替join实现计数器 2、CyclicBarrier 阻塞一批线程直到所有线程都实现,关上屏障 下一步动作实施者不一样CountdownLatch为主线程 CyclicBarrier为其余线程 CyclicBarrier 计数器能够重置 适宜更简单场景 3、Semaphore 管制并发线程的数量 流控 4、Exchanger 线程间替换数据九、线程池 1、实现原理 execute提交工作 外围线程池未满(即便有闲暇,不销毁)则创立线程执行工作,已满则阻塞队列是否已满,未满则退出队列,已满则看线程池是否已满,未满则创立线程执行工作,已满则执行回绝策略 创立的线程封装为工作线程Worker,执行完从阻塞队列中获取工作 2、new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler) corePoolSize 外围线程大小 maximumPoolSize 最大线程数量 keepAliveTime 闲暇线程存活工夫 workQueue ArrayBlockingQueue LinkedBlockingQuene SynchronousQuene PriorityBlockingQueue threadFactory 线程工厂 次要定义名字 handler 回绝策略 CallerRunsPolicy 调用者线程执行 AbortPolicy 间接抛弃 抛出RejectedExecutionException异样 DiscardPolicy 间接抛弃 DiscardOldestPolicy 摈弃最早进入队列的工作,放进队列 3、Executor框架 ThreadPoolExecutor SingleThreadExecutor 1-1 LinkedBlockingQueue FixedThreadPool core-max LinkedBlockingQueue CacheThreadPool 0-max SynchronousQueue 不同创立线程 ScheduledThreadPoolExecutor 比Timer更灵便 ScheduledThreadPoolExecutor DelayQueue 外部是一个PriorityQueue把time小的优先 工作须要实现Delay接口 SingleThreadScheduledExecutor Future 示意后果 Runnable Callable Executors总结最近,没那么多工夫来写博客了,只能简略的对重读的常识,进行总结,所以排版不是很好,有工夫会回来优化的! ...

May 4, 2021 · 1 min · jiezi

关于java并发:Java并发JMM的8大原子操作及并发3之volatile关键字可见性

摘要咱们之前解说了JMM模型,以及其引入的必要行,以及JMM与JVM内存模型的比拟和JMM与硬件内存构造的对应关系。 思维导图本节次要解说思维导图如下: 内容1、JMM的8大原子操作1、lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 2、unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,开释后的变量 才能够被其余线程锁定。3、read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以 便随后的load动作应用。 4、load(载入):作用于工作内存的变量,它把read操作从主内存中失去的变量值放入工作内存的 变量正本中。 5、use(应用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚 拟时机到一个须要应用变量的值的字节码指令时将会执行这个操作。 6、assign(赋值):作用于工作内存的变量,它把一个从执行引擎接管的值赋给工作内存的变量, 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 7、store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随 后的write操作应用。8、write(写入):作用于主内存的变量,它把store操作从工作内存中失去的变量的值放入主内存的变量中。 留神:1、如果须要把变量总主内存赋值给工作内存:read和load必须是间断;read只是把主内存的变量值从主内存加载到工作内存中,而load是真正把工作内存的值放到工作内存的变量正本中。 2、如果须要把变量从工作内存同步回主内存;就须要执行程序执行store跟write操作。store作用于工作内存,将工作内存变量值加载到主内存中,write是将主内存外面的值放入主内存的变量中。 代码实例: public class VolatileTest2 { static boolean flag = false; public void refresh(){ this.flag = true; String threadName = Thread.currentThread().getName(); System.out.println("线程: "+threadName+" 批改共享变量flag为"+flag); } public void load(){ String threadName = Thread.currentThread().getName(); while (!flag){ } System.out.println("线程: "+threadName+" 嗅探到flag状态的扭转"+" flag:"+flag); } public static void main(String[] args) { /** * 创立两个线程 */ VolatileTest2 obj = new VolatileTest2(); Thread thread1 = new Thread(() -> { obj.refresh(); }, "thread1"); Thread thread2 = new Thread(() -> { obj.load(); }, "thread2"); thread2.start(); try { /** * 确保咱们线程2先执行 */ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } thread1.start(); }}咱们发现下面代码数据后果为: ...

January 13, 2021 · 2 min · jiezi

关于java并发:Java并发JMM

摘要之前咱们解说过cpu多级缓存模型,然而对于JVM来说为了屏蔽掉各种操作系统跟各种硬件的差别,是各个操作系统和硬件数据读写原理一致性而引入了java内存模型JMM; 思维导图内容JMM模型前言: JMM它是一个虚构的货色,是一个形象的概念;形容的是一组标准;形象的就是cpu的多核缓存架构;为了实现java跨平台;屏蔽掉计算机硬件跟操作系统,保障在各个操作系统上读取数据的一致性。如下,咱们能够把java内存模型跟计算机多核cpu缓存模型进行形象。 java的工作内存能够是:计算机主内存、cpu的多级缓存、cpu的寄存器。jvm外面的主内存能够是:计算机主内存、cpu的多级缓存、cpu的寄存器。

January 10, 2021 · 1 min · jiezi

关于java并发:Java并发线程及并发

摘要之前咱们解说了cpu多多级缓存模型,以及为什么须要引入cpu多级缓存模型?(为了解决cpu运算速度远高于基于I/O总线读取主内存数据速度)而后引入cpu多级缓存模型之后产生的问题?(数据缓存一致性)而后就是解决cpu缓存一致性问题的计划?(总线加锁及缓存一致性协定MESI)而后具体解说了缓存一致性协定MESI中多线程读写主内存数据时候产生的问题;这一讲次要解说下什么是线程以及为什么须要并发? 思维导图咱们依照以下思维逻辑导图解说线程及并发。 内容1、线程及过程过程: 零碎分配资源的根本单位;其就是咱们运行的一个应用程序;比方:JVM过程、微信、QQ。 线程: 操作系统调度cpu的根本单位;是过程中的一个场景快照。 线程及过程关系: 1、过程是零碎分配资源的根本单位;线程是调度cpu的根本单位。过程是没有调用cpu的权力的,如果咱们的jvm是一个应用程序,它自身是没有调用cpu的权力的(调用cpu的权力是操作系统的性能),如果咱们的jvm可能不依赖于咱们的操作系统。间接能够操作cpu和显卡的话,那么这个jvm不就是一个操作系统了吗?那他还装置到操作系统干嘛呢?线程是调度cpu的根本单位,自身是不具备更多的资源,只具备本身须要的简略资源,所以说线程会共享过程外面的资源。 2、综上所叙述:线程是调度cpu的根本单位,一个过程至多蕴含一个线程,线程寄生在过程当中。每个线程都有一个程序计数器(记录要执行的下一条指令)、一组寄存器(保留以后线程的工作变量)。 分类: 依照线程所属空间,咱们将线程分为:用户线程(User-Level Thread)、内核线程(Kernel-Level Thread);不同品种的线程工作在不同的空间外面。 咱们看下如下代码: str = “I like learning“ //用户空间 x = x + 2file.write(str) //切换到内核空间y=x+4 //切换到用户空间 如上图:咱们的用户空间划分为两局部:用户空间、内核空间。内核空间:零碎内核运行的空间。 用户空间:用户程序运行的空间;比方JVM、PS、播放器。 咱们程序运行的状态,如果运行在内核空间就是内核态,运行在用户空间的话它是用户态。为了安全性起见,两者是隔离的,即便用户空间的JVM解体了,内核空间不受影响的。内核空间的话是能够执行任何命令,调用零碎任何资源。用户空间的话简略运算,不能调度系统资源。内核空间会提供一个接口供用户空间发送指令。 cpu特权级别: cpu特权级别分为:ring0、ring3。 cpu特权级别:为什么咱们的JVM须要依赖咱们的内核能力调用咱们的cpu呢?因为咱们的cpu分为了两级特权。一级是ring0(最好级别,领有cpu最高操作权限)、一级是ring3最低级别(领有简略的操作权限,没有外围操作权限);用户空间只有cpu的ring3特权级别。内核空间只有cpu的ring0特权级别。为什么须要这样的划分?为了安全性:如果说咱们的JVM在用户空间,具备cpu0的特权,那么他就可能操作咱们的内核空间,这样的话通过更改内核能够植入病毒,以及管制其余应用程序。所以只有内核空间才具备操作cpu的最高特权级别。 用户线程与内核线程: 用户线程(ULT): 就是在用户空间外面的过程创立的线程。用户线程它是没有cpu的应用权限的,它是不可能间接去调度cpu(只能简略操作权限),因为咱们的内核是不晓得多线程的存在的。内核基本不晓得多线程的用户线程存在,因为在其外部保护的是一个过程表。咱们cpu处理器资源的调配工夫分片的话,是以过程为根本单位的,所以线程是依靠于咱们的主过程去执行的。所有的线程都执行在同一条线上。这就有一个问题当咱们的用户线程阻塞了,比方线程1阻塞了。整个主过程将会被阻塞。如下: 内核线程(KLT):内核线程由在内核空间创立,保护在内核空间的线程表外面(如下面图示:过程表里保护了用户空间的过程、线程表外面保护了内核空间的线程);同理,内核过程也是在内核空间创立,在内核空间保护过程表。内核级线程是操作系统去实现的。cpu会为咱们的内核级线程调配工夫片。所以多个线程都能够去抢夺cpu资源。内核线程阻塞了的话不会影响内核过程的运行。 在java外面用的是哪种线程呢?在java外面1.2版本之前用的是ULT;1.2之后用的是KLT内核级线程;java线程模型就是依赖于底层内核线程去保护的,两者有什么关系呢?如下: 他们是一一映射的关系。 如上:咱们的jvm过程是能够创立多个线程的,实质上是jvm去创立了线程栈空间(其实没有去创立真正的线程);线程栈空间外面会有一些栈针指令; 创立真正的线程是须要通过咱们的库调度器去调度咱们的内核空间去创立内核线程,从而操作调度cpu; java线程<--->内核线程。 2、并发Java线程生命周期 Java线程生命周期的话是仅仅限度在咱们的JVM外面,总共就只有6中状态: 新建: NEW运行:RUNNABLE期待:WAITING阻塞:BLOCKED 终止:TERMINATED 留神,咱们的阻塞之后 不是不运行了,而是进入就绪状态,等待时间片调配。 为什么用并发 1、充分利用多核CPU的计算能力。 2、不便进行业务拆分,晋升利用性能。 并发产生的问题: 1、高并发场景下,导致频繁的上下文切换 2、临界区线程平安问题,容易呈现死锁的,产生死锁就会造成零碎性能不可用。 留神:在咱们单核处理器也反对多线程代码的,只不过是cpu给每一个线程调配工夫片(时分复用)来实现这种机制的。这情状况下cpu给每个线程调配的工夫比拟短,让咱们感觉多个线程如同是同时执行的。因为这个工夫片的工夫只有几十毫秒,是十分的短,然而须要频繁的线程上下文切换。 还有一点要须要留神的:并发与并行区别:并发:多个工作交替执行;比方工夫片cpu的切分。 并行:才是真正意义上的多个工作同时进行。 如果咱们的零碎内核只有一个cpu,那么他应用多线程时候,实际上并不是并行的,他只能通过工夫片的模式。去并发执行工作。真正意义上的并发只会呈现在多个cpu的零碎当中。 毛病:如果在大并发条件下大量创立线程。因为咱们的linux零碎或者jvm他的线程数是有一个峰值的。如果超过了这个峰值的话,性能会大幅度降落。 线程上下文切换原理分析如下: ...

January 10, 2021 · 1 min · jiezi