该系列博文会告诉你如何全面深入地学习Java并发技术,从Java多线程基础,再到并发编程的基础知识,从Java并发包的入门和实战,再到JUC的源码剖析,一步步地学习Java并发编程,并上手进行实战,以便让你更完整地了解整个 Java并发编程知识体系,形成自己的知识框架。 Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制。 Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小 参考资料 周志明:《深入理解Java虚拟机》 方腾飞:《Java并发编程的艺术》 Java中synchronized的实现原理与应用
Java并发学习4【面试+工作】 九.fork&join Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架 其中Vector(同步的ArrayList)和Stack(继承自Vector,先进后出)、HashTable(继承自Dictionary,实现了Map接口)是比较老的容器,Thinking in Java 中明确指出,这些容器现在仍然存在于JDK中是为了向以前老版本的程序兼容,在新的程序中不应该在使用。 ConcurrentHashMap 对应的非并发容器:HashMap 目标:代替Hashtable、synchronizedMap,支持复合操作 原理:JDK6中采用一种更加细粒度的加锁机制Segment “分段锁”,JDK8中采用CAS无锁算法 CopyOnWriteArrayList 对应的非并发容器:ArrayList 目标:代替Vector、synchronizedList 原理:利用高并发往往是读多写少的特性
在高并发Java(1):前言中已经提到了无锁的概念,由于在jdk源码中有大量的无锁应用,所以在这里介绍下无锁。 Java当中提供了很多无锁类,下面来介绍下无锁类。 2 无所类的使用 我们已经知道,无锁比阻塞效率要高得多。我们来看看Java是如何实现这些无锁类的。 2.1. ) park()(把这个线程停下来,在以后的Blog中会提到) 底层的CAS操作 非公开API,在不同版本的JDK中,可能有较大差异 2.3. 1 shift = 31 - Integer.numberOfLeadingZeros(scale); 前导零的意思就是比如8位表示12,00001100,那么前导零就是1前面的0的个数,就是4。 如果我们原有代码中未申明这个类型,那么简单得申明一下就行,这不会引起什么问题。
以此就能提高系统中的资源利用率,增加系统的吞吐量。 ? 并发和并行 进程和线程 进程是指一个内存中运行的应用程序。 Java程序的进程(Java的一个程序运行在系统中)里至少包含主线程和垃圾回收线程(后台线程)。 处理两个文件则需要: 1| 5秒读取文件A 2| 2秒处理文件A 3| 5秒读取文件B 4| 2秒处理文件B 5| --------------------- 6| 总共需要14秒 从磁盘中读取文件的时候 ② 系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高. ③ Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程 courseId=1003108028 《Java零基础入门教程》 《Java并发编程的艺术》 《Java 7并发编程实战手册》
偏向锁 大多数情况下,锁不存在多线程竞争,且总是由同一线程多次获得 线程访问同步块并获取锁,就在对象头和栈帧中的锁记录里存储偏向锁ID,之后线程出入同步块就不需要CAS来加锁和解锁,只是测试对象头的Mark 测试成功,线程获得锁,失败,就测试Mark Word中锁标识是否是1(当前锁是偏向锁):没有设置,用CAS竞争锁;设置了,用CAS将对象头偏向锁指向当前线程。 轻量级锁 加锁:线程执行同步代码块之前,JVM在该线程栈帧中创建存储锁记录的空间,并将对象MarkDown复制到锁记录中,然后线程尝试用CAS将对象头中的Mark Word替换为指向锁的指针。
如何创建一个线程 按 Java 语言规范中的说法,创建线程只有一种方式,就是创建一个 Thread 对象。 而从 HotSpot 虚拟机的角度看,创建一个虚拟机线程 有两种方式,一种是创建 Thread 对象,另一种是创建 一个本地线程,加入到虚拟机线程中。 如果从 Java 语法的角度。有两种方法。 Executors JDK 的 java.util.concurrent.Executors 类提供了几个静态的方法,用于创建不同类型的线程池。 原理 JDK 中的线程池通过 HashSet 存储工作者线程,通过 BlockingQueue 来存储待处理任务。 另外,如果待处理队列中没有任务要处理,并且工作者线程数目超过了核心工作者数目,那么,需要减少工作者线程数目。
一、Java中锁分类 1.1 偏向锁/轻量级锁/重量级锁 这三种锁指的是synchronized锁的状态,Java1.6之前是基于重量级锁,Java1.6之后对synchronized进行了优化,为了减少获取和释放锁带来的性能消耗 java中的Synchronized内置锁和ReentrantLock显式锁都是独占锁。 2、共享锁 共享锁就是在同一时刻允许多个线程持有的锁。 在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO(先进先出)的规则从队列中取到自己。 3、例子 在Java的并发包中,ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。 二、synchronized锁 详细见Java并发——synchronized锁 三、Lock锁 Java并发——Lock锁 四、synchronized 和 Lock 对比 相同点: 1、synchronized
转载:http://www.cnblogs.com/dolphin0520/p/3932921.html 一. java中的ThreadPoolExecutor类 java.util.concurrent.ThreadPoolExecutor 类时线程池中最核心的一个类,因此如果要透彻的了解java中线程池,必须先了解这个类。 这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。 4.任务缓存队列及排队策略 前面提到任务缓存队列,即workQueue,它是用来存放等待执行的任务。 不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池: Executors.newCachedThreadPool
[Java] Java 并发包中并发原理剖析之ConcurrentLinkedQueue ConcurrentLinkedQueue是线程安全的无界非阻塞队列,其底层数据结构使用单向链表实现,对于入队和出队操作使用 队列中包含的元素是从head访问的非空节点。通过CAS将节点的引用指向null,自动的会将它从队列中移除。 队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。 内存一致性效果:当存在其他并发 collection 时,将对象放入 ConcurrentLinkedQueue 之前的线程中的操作 happen-before 随后通过另一线程从 ConcurrentLinkedQueue REFERENCES Java并发编程之美 JDK-API-DOCS
---- 然后我们再来看看Guava中创建不可变对象的方法,示例代码如下: @Slf4j public class ImmutableExample3 { /** * 不可变的list ---- 线程封闭 在上一小节中,我们介绍了不可变对象,不可变对象在多线程下是线程安全的,因为其避开了并发,而另一个更简单避开并发的的方式就是本小节要介绍的线程封闭。 1.在Java中同步容器主要分为两类,一类是集合接口下的同步容器实现类: List -> Vector、Stack Map -> HashTable(key、value不能为null) 注:vector 本小节将简单介绍一下并发容器,并发容器也称为J.U.C,即是其包名:java.util.concurrent。 所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。
转载请以链接形式标明出处: 本文出自:103style的博客 Java并发编程的艺术笔记 并发编程的挑战 Java并发机制的底层实现原理 Java内存模型 Java并发编程基础 Java中的锁的使用和实现介绍 Java并发容器和框架 Java中的12个原子操作类介绍 Java中的并发工具类 Java中的线程池 Executor框架 ---- 简介 在JDK的并发包里提供了几个非常有用的并发工具类。 ** * 假设只有4个sheet,所以只启动4个线程 */ private Executor executor = Executors.newFixedThreadPool( 假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据 虽然有20个线程在执行,但是只允许5个并发执行。
Java 中的并发(Concurrency) 指多个任务在同一时间段内交替执行(宏观上同时进行,微观上可能是 CPU 快速切换调度),目的是提高程序效率,充分利用系统资源(如 CPU、内存、I/O 等) undefined例如:4 核 CPU 同时运行 4 个线程是并行,1 核 CPU 快速切换 4 个线程是并发。三、Java 实现并发的方式Java 提供了多种并发编程工具,核心是通过线程实现:1. 启动线程 new Thread(futureTask).start(); // 4. Java 提供 Executors 工具类快速创建线程池:import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors 理解并发是 Java 进阶的关键,尤其在高并发场景(如分布式系统、高流量服务器)中,合理设计并发模型能显著提升系统性能。
java并发中ExecutorService的使用 ExecutorService是java中的一个异步执行的框架,通过使用ExecutorService可以方便的创建多线程执行环境。 第一种方式是使用Executors中的工厂类方法,例如: ExecutorService executor = Executors.newFixedThreadPool(10); 除了newFixedThreadPool Future<String>> futures = executorService.invokeAll(callableTasks); 关闭ExecutorService 如果ExecutorService中的任务运行完毕之后 ExecutorService和 Fork/Join java 7 引入了Fork/Join框架。那么两者的区别是什么呢? 本文的代码请参考https://github.com/ddean2009/learn-java-concurrency/tree/master/ExecutorService
java并发中CountDownLatch的使用 在java并发中,控制共享变量的访问非常重要,有时候我们也想控制并发线程的执行顺序,比如:等待所有线程都执行完毕之后再执行另外的线程,或者等所有线程都准备好了才开始所有线程的执行等 最后在主线程中调用await()方法来等待子线程结束执行。 @Slf4j public class MainThreadWaitUsage implements Runnable { private List<String> outputScraper 我们是主线程等待子线程,那么在这个例子中,我们将会看看怎么子线程一起等待到准备好的状态,再一起执行。 sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } 本文的例子可以参考https://github.com/ddean2009/learn-java-concurrency
9.2 改进的读写锁:StampedLock StampedLock是JDK 8中引入的新的锁机制,可以认为是读写锁的一个改进版本,读写锁虽然分离了读和写,使得读与读之间可以完全并发,但是读和写之间仍然是冲突的 简单粗暴的分散了高并发下的竞争压力。 答案就在LongAdder的java doc中,从我们翻译的那段可以看出,LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add方法,而AtomicLong还具有cas 从java doc中可以看出,其适用于统计计数的场景,例如计算qps这种场景。在高并发场景下,qps这个值会被多个线程频繁更新的,所以LongAdder很适合。 ---- 参考: https://www.jianshu.com/p/22d38d5c8c2a 《实战Java高并发程序设计》
Java 并发 线程状态转换 新建(New) 创建后尚未启动。 可运行(Runnable) 可能正在运行,也可能正在等待 CPU 时间片。 時雨:在 《Java 并发核心知识体系精讲》中,参考 Oracle 官方文档,标注实现多线程方式只有两种:实现 Runnable 接口和继承 Thread 类。 $Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) 如果只想中断 Executor 中的一个线程 它是 JUC 并发包中的核心基础组件。 CountDownLatch 用来控制一个或者多个线程等待多个线程。 在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
上篇文章 并发编程3:线程池的使用与执行流程 中我们了解到,线程池中需要使用阻塞队列来保存待执行的任务。这篇文章我们来详细了解下 Java 中的阻塞队列究竟是什么。 具体到 Java 中,使用 BlockingQueue 接口表示阻塞队列: public interface BlockingQueue<E> extends Queue<E> { //添加失败时会抛出异常 七种阻塞队列的前三种 Java 中提供了 7 种 BlockingQueue 的实现,在看线程池之前我根本搞不清楚究竟选择哪个,直到完整地对比总结以后,发现其实也没什么复杂。 ,这样在添加、获取时通过判断数组元素个数可以感知到并发的获取/添加操作 ;此外就是链表比数组的优势了。 不了解 Comparator 和 Comparable 可以看这篇 Java 解惑:Comparable 和 Comparator 的区别。
如果转移结点时发现同步队列中的前继结点已取消,或者是更新前继结点的状态为 SIGNAL 失败,这两种情况都会立即唤醒线程,否则的话在 signal 方法结束时就不会去唤醒已在同步队列中的线程,而是等到它的前继结点来唤醒 reportInterruptAfterWait(interruptMode); } //返回剩余时间 return nanosTimeout - (System.nanoTime() - lastTime); } 4. 唤醒条件队列中的头结点 //唤醒条件队列中的下一个结点 public final void signal() { //判断当前线程是否持有锁 if (! = null) { //唤醒条件队列中的头结点 doSignal(first); } } //唤醒条件队列中的头结点 private void doSignal(Node //2.将头结点的后继结点引用置空 first.nextWaiter = null; //3.将头结点转移到同步队列, 转移完成后有可能唤醒线程 //4.
已执行 2 已执行 1 已执行 全部执行完毕 0 已执行 1 已执行 2 已执行 3 已执行 4 已执行 全部执行完毕 Semaphore Semaphore 信号量维护了一个许可集,每次使用时执行acquire e.printStackTrace(); } } } 输出如下: 0 准备执行 2 准备执行 1 准备执行 3 准备执行 4 准备执行 2 已经执行 2 已经释放 4 已经执行 4 已经释放 1 已经执行 1 已经释放 0 已经执行 0 已经释放 3 已经执行 3 已经释放 Exchanger Exchanger 用于两个线程间的数据交换 交换得到数据为:pool-1-thread-2 pool-1-thread-2 交换得到数据为:pool-1-thread-1 pool-1-thread-3 等待交换 pool-1-thread-4 等待交换 pool-1-thread-4 交换得到数据为:pool-1-thread-3 pool-1-thread-3 交换得到数据为:pool-1-thread-4 pool-1-thread-5
今天使用到了volatile关键字,之前了解到这个关键字,但是不知道他的具体作用是什么,下面就来详细解释一下他的作用: 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块(synchronized) 和 volatile 关键字机制。 在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。 换句话说,另一个线程可能已经改变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗]) 3. 代码块被执行 4.