这是java高并发系列第2篇文章,一个月,咱们一起啃下java高并发,欢迎留言打卡,一起坚持一个月,拿下java高并发。 由于临界区的存在,多线程之间的并发必须受到控制。 根据控制并发的策略,我们可以把并发的级别分为阻塞、无饥饿、无障碍、无锁、无等待几种。 阻塞 一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。 应该比较熟悉,表中需要一个字段version(版本号),每次更新数据version+1,更新的时候将版本号作为条件进行更新,根据更新影响的行数判断更新是否成功,伪代码如下: 1.查询数据,此时版本号为w_v 2. 在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。 在无锁的调用中,一个典型的特点是可能会包含一个无穷循环。 java高并发系列目录: 1.java高并发系列-第1天:必须知道的几个概念 希望您能把这篇文章分享给更多的朋友,让它帮助更多的人。帮助他人,快乐自己,最后,感谢您的阅读。微信扫码入群一起交流。
Java并发学习2【面试+工作】 三.synchronized&volatile synchronized 关键字synchronized的作用是实现进程间的同步。 重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。它的几个重要方法如下: ?
New(新创建) 2. Runnable(可运行) 3. Blocked(被阻塞) 4. Waiting(等待) 5. Timed Waiting(计时等待) 6. 2. 当线程试图获取一个内部对象锁而该锁被其他线程持有则线程进入阻塞状态,当所有其他线 程释放该锁,并且线程调度器允许该线程持有它的时候,线程才变为非阻塞状态 3. 因为run方法正常退出而死亡 2. Thread.State getState() //获得指定的线程状态 void stop() //停止该线程 2.1 线程属性 线程的属性包括:线程优先级、守护线程、线程组以及处理未捕获异常的处理器 Java
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务。 //fromIndex....mid....toIndex //1...................70....100 int mid = (fromIndex+toIndex)/2; > t2) { int s1, s2; t2.fork(); if (((s1 = t1.doInvoke()) & ABNORMAL) ! = 0) t1.reportException(s1); if (((s2 = t2.doJoin()) & ABNORMAL) ! = 0) t2.reportException(s2); } 工作密取(Work Stealing) 在后台,fork-join框架使用了一种有效的方法来平衡可用线程的负载
如果使用多进程,那么并发数相对来说不会很高。而线程是更细小的调度单元,更加轻量级,所以线程会较为广泛的用于并发设计。 在Java当中线程的概念和操作系统级别线程的概念是类似的。 事实上,Jvm将会把Java中的线程映射到操作系统的线程区。 2. 线程的基本操作 2.1 线程状态图 ? 上图是Java中线程的基本操作。 当new出一个线程时,其实线程并没有工作。 如果不了解Java的中断机制,这样的一种解释极容易造成误解,认为调用了线程的interrupt方法就一定会中断线程。 其实,Java的中断是一种协作机制。 结果输出是: 1 2 in t1 in t2 说明两个线程都争夺到了锁,但是控制台的红灯还是亮着的,说明t1,t2一定有线程没有执行完。我们dump出堆来看看 ? 发现t2一直被suspend。 当一个Java应用内,所有非守护进程都结束时,Java虚拟机就会自然退出。 此前有写过一篇python中如何实现,查看这里。 而Java中变成守护进程就相对简单了。
Java 并发 线程状态转换 新建(New) 创建后尚未启动。 可运行(Runnable) 可能正在运行,也可能正在等待 CPU 时间片。 時雨:在 《Java 并发核心知识体系精讲》中,参考 Oracle 官方文档,标注实现多线程方式只有两种:实现 Runnable 接口和继承 Thread 类。 2. 性能 新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。 3. (J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。 在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
上文说到synchronized,JAVA并发编程synchronized全能王的原理,虽然被评为并发全能王,不过用起来也是格外注意,不能搞大力出奇迹那一套,容易出现性能问题。 volatile是轻量级的并发解决方案,不会阻塞线程,是一种简单的同步机制。JAVA对volatile的定义是:volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性。 在多线程并发到这一段代码语句123,CPU处理器就是按顺序执行。不好出现132顺序。 2.如何保证可见性 保证此变量的修改,能被所有线程及时看到。 4.volatile的缺点-原子性问题 比如两个线程对一个volatile修饰的count字段,进行2w次++,由于原子性问题,导致结果并不是20000. package lading.java.mutithread 5.volatile怎么用更科学 像4的示例,volatile修饰的count并发++2w次,结果出现原子性问题。
Java内存模型是围绕着原子性,有序性,可见性展开的。 volatile 能保证数据的可见性 volatile 能保证一定的有序性 volatile 不能保证操作的原子性。 2. volatile 不保证原子性 任意单个volatile变量的读/写具有原子性,但是类似于++这种复合操作不具有原子性 Java内存模型的原子性可以由synchronized和Lock实现。 例如: // x,y为非volatile变量 // flag为volatile变量 x = 2; //语句1 y = 0; //语句2 flag = true; //语句 但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。这就是能保证“一定”的有序性的原因。 并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。 但是换一个对象访问这个临界区就不会阻塞 2)synchronized修饰静态方法 由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。 Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。 但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务 2、轻量级锁 JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。
Java并发 - (并发基础) 1、什么是共享资源 堆是被所有线程共享的一块内存区域。在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例 Java中几乎所有的对象实例都在这里分配内存。 如下图: 2、并发编程的难点 原子性问题 操作系统做任务切换(CPU切换),可以发生在任何一条CPU指令执行完成后; CPU能保证的原子操作是指令级别的,而不是高级语言的操作符(例如:n++)。 并发编程的内存模型 共有两种并发编程模型:共享内存模型、消息传递模型,Java采用的是前者。 /article/739920a92d0d27e2053174ef2 参考地址:http://concurrent.redspider.group/RedSpider.html JMM 是Java Memory 重排序对可见性的影响 参考下图,虽然处理器执行的顺序是A1->A2,但是从内存角度来看,实际发生的顺序是A2->A1。
在单线程中,如果某操作无法满足先验条件,就只能失败,但在并发程序中先验条件可能会由于其他线程执行的操作而变成真。 java中等待某个条件为真的各种内置机制(包括等待和通知机制)都与内置加锁紧密关联。 4.1设计线程安全的类 在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量 找出约束状态变量的不可变性条件 建立对象状态的并发访问管理策略 4.3委托给线程安全的类 可以将共享资源委托给一个线程安全的类 synchronized,volatile或者任何一个线程安全类都对应于某种同步策略,用于在并发访问时确保数据的完整性。一定要在忘记之前记录下来。
Executor两级调度模型 在HotSpot虚拟机中,Java中的线程将会被一一映射为操作系统的线程。 在Java虚拟机层面,用户将多个任务提交给Executor框架,Executor负责分配线程执行它们; 在操作系统层面,操作系统再将这些线程分配给处理器执行。 2.
; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @program: concurrency-demo 修改代码如下(重复代码忽略): ... import java.util.concurrent.atomic.AtomicInteger; @Slf4j public class CountExample2 这样一来 LongAdder 相当于是在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候通过对base的直接更新,可以很好的保证和Atomic的性能基本一致,在高并发的场景, 通过将热点分散来提高并行度 缺点:在统计的时候如果有并发更新,可能会导致统计结果有些误差。 ---- 线程安全性-有序性 本小节我们来介绍一下线程安全性里的有序性: 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
在上一篇《Java 并发系列(1)AbstractQueuedSynchronizer 源码分析之概要分析》中介绍了 AbstractQueuedSynchronizer 基本的一些概念,主要讲了 AQS = null) { //1.指向当前尾结点 node.prev = pred; //2.设置当前结点为尾结点 if (compareAndSetTail = head; } } else { //1.指向当前尾结点 node.prev = t; //2. 2. 怎样以响应线程中断获取锁?
1.Java容器 1.1.同步容器 Vector ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。 这个机制允许任意数量的读线程可以并发访问Map,读者和写者也可以并发访问Map,并且有限数量的写进程还可以并发修改Map,结果是为并发访问带来更高的吞吐量,同时几乎没有损失单个线程访问的性能。 Java并发队列 2.1.ConcurrentLinkedQueue ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无所的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue 在Java中,BlockingQueue的接口位于java.util.concurrent 包中(在Java5版本开始提供),由上面介绍的阻塞队列的特性可知,阻塞队列是线程安全的。 所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。
死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
高级多线程控制类Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。 定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。 这2个操作都是原子操作。 参考文章:Java多线程并发编程一览笔录 https://www.cnblogs.com/yw0219/p/10597041.htmlJava 中的多线程你只要看这一篇就够了 https://juejin.im /entry/57339fe82e958a0066bf284f转载本站文章《java并发编程(2):Java多线程-java.util.concurrent高级工具》,请注明出处:https://www.zhoulujun.cn
5 并发容器 5.1 Hashtable、HashMap、TreeMap、HashSet、LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比。 2. = null); 15: } 16: } 在正常单线程的情况下,如果有如下的HashMap的结构,为了方便这里只有2个bucket(java.util.HashMap中默认是 16)。 1: // Transfer method in java.util.HashMap - 2: // called to resize the hashmap 3: 4: for (int j 在高并发下的情况下如何保证取得的元素是最新的?
本篇内容包括:Java 并发工具类的介绍、使用方式与 Demo,包括了 CountDownLatch(线程计数器)、CyclicBarrier(回环栅栏)、Semaphore(信号量) 以及 Exchanger CountDownLatch(线程计数器) CountDownLatch 线程计数器,俗称闭锁,作用是类似加强版的 Join,是让一组线程等待其他的线程完成工作以后才执行 CountDownLatch 类位于 java.util.concurrent String[] args) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(2) getName() + "执行完毕"); latch.countDown(); }).start(); System.out.println("等待 2 CyclicBarrier 中最重要的方法就是 await 方法,它有 2 个重载版本: public int await():用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任务
在 Jdk1.5 开始 Java 开始引进提供了 java.util.concurrent.atomic 包,到 Jdk8 时,atomic 包共提供了 16 个原子类,分为 6 种类型,分别是:①、基本类型原子类 当多线程更新变量的值时,可能得不到预期的值,当然增加 syncronized 关键字可以解决线程并发的问题。但原子类提供了一种用法简单,性能高效,线程安全的更新变量的方式。 原子类相较于 synchronized 关键字和 lock,有着以下的优点: 简单:操作简单,底层实现简单 高效:占用资源少,操作速度快 安全:在高并发和多线程环境下要保证数据的正确性 2、Array(数组类型原子类) Array 数组类型原子类,包括三种:AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray。 但是,在并发量很大的场景下,Atomic 基本类型原子类(AtomicInteger 和 AtomicLong)有很大的性能问题。