Java 并发 线程状态转换 新建(New) 创建后尚未启动。 可运行(Runnable) 可能正在运行,也可能正在等待 CPU 时间片。 時雨:在 《Java 并发核心知识体系精讲》中,参考 Oracle 官方文档,标注实现多线程方式只有两种:实现 Runnable 接口和继承 Thread 类。 2. 性能 新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。 3. (J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。 在 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框架使用了一种有效的方法来平衡可用线程的负载
上文说到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次,结果出现原子性问题。
synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。 但是换一个对象访问这个临界区就不会阻塞 2)synchronized修饰静态方法 由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。 Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。 但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务 2、轻量级锁 JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。
从上篇文章 并发编程1:全面认识 Thread 我们了解了 Java 中线程的基本概念和关键方法。 2:上下文切换的开销 当 CPU 调度不同线程时,它需要更新当前执行线程的数据,程序指针,以及下一个线程的相关信息。 这种切换会有额外的时间、空间消耗,我们在开发中应该避免频繁的线程切换。 这里先简单地对“Java 内存模型”进行介绍,后序介绍完常见并发类后再详细总结。 多线程可能出现的问题 通过上述介绍,我们可以知道,如果多个线程共享一个对象,每个线程在自己的栈中会有对象的副本。 总结 本篇文章结合 Java 内存模型简单介绍了多线程开发的优点与可能导致的问题,犹豫了一下我还是觉得有必要在开始学习 Java 各种并发 API 之前了解它们出现的背景,这样更容易明白它们解决了什么问题 2.在JAVA中,有六个不同的地方可以存储数据: 寄存器(register) 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。
Java中创建线程的三种方法以及区别 Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。 Java可以用三种方式来创建线程,如下所示: 继承Thread类创建线程 实现Runnable接口创建线程 使用Callable和Future创建线程 线程池创建线程 下面让我们分别来看看这三种创建线程的方法 call()方法可以有返回值 call()方法可以声明抛出异常 Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask 但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。 继承Thread类的线程类不能再继承其他父类(Java单继承决定)。 2. 性能问题 多线程并不一定绝对提供程序效率,要看具体的场景。
一、可见性、原子性、有序性(三大特性) 1)发生背景 由于cpu、内存、io设备的速度差异,做了以下优化 1、cpu 增加了缓存,以均衡与内存的速度差异 2、操作系统增加了进程、线程,以分时复用cpu, 进而均衡cpu与io设备的速度差异 3、编译程序优化指令执行次序,使得cpu缓存能够得到更加合理利用 2)带来的问题(引出三大特性) 现实中的并发问题往往是三种问题的综合症 1、缓存导致的可见性问题 2 在解锁的时候,JVM需要强制刷新缓存,使得当前线程所修改的内存对其他线程可见 (3) final 当一个对象包含final修饰的实例字段时,其他线程能够看到已经初始化的final实例字段,这是安全的 二、java 内存模型(两大核心之一) 1)Java内存模型定义了线程和内存的交互方式 在JMM抽象模型中,分为主内存、工作内存。 把从执行引擎接收到的的值赋值给工作内存变量 Store(存储):把工作内存的变量值传递给主内存,以便后续的write使用 Write(写入):用于主内存变量,把store获得的变量的值放入主内存变量 3)内存模型解决并发问题主要采用两种方式
并发与并行 并发指的是同时应对多个事件的能力,并行指的是同时做多件事的能力。 位级并行:32位计算机能够同时处理32位数运算,而8位计算机却要进行多次运算。 通过扩展cpu数量可以提高这种架构性能,但是SMP服务器cpu利用率最好是2-4个cpu。 java内存模型 java内存类似于SMP,但是其屏蔽了底层硬件环境的差异,给java提供了统一的内存访问模型。 java中所有线程共享主内存,对于每个线程都有自己的工作区,包括寄存器,栈,写换冲区,缓存,硬件,编译优化等。 多线程并发 并发问题也就带来来线程安全访问的问题。多线程执行时需要考虑进行额外的协调。 不可变性:可变数据是引起不安全的主要原因,如果一个数据不可变,则不会存在数据安全问题。
Java并发编程的核心挑战 线程安全与数据竞争 线程安全的概念及其重要性 数据竞争的产生原因及常见场景 如何通过同步机制(如锁、原子类)避免数据竞争 // 示例:使用synchronized关键字实现线程安全 wait(); } buffer.remove(0); notify(); } } } 性能与可伸缩性 并发编程对性能的影响 Java并发工具类(如CountDownLatch、CyclicBarrier、Semaphore)的应用场景 如何选择合适的工具类解决并发问题 工具类的使用注意事项 // 示例:使用CountDownLatch new Thread(() -> { // 任务逻辑 latch.countDown(); }).start(); } latch.await(); 并发编程的最佳实践 并发编程的新特性(如CompletableFuture、Flow API) 多核处理器与并发编程的关系 如何应对分布式环境下的并发挑战 // 示例:使用CompletableFuture实现异步编程 CompletableFuture.supplyAsync
并行与并发 单核CPU下,线程实际还是 串行执行 的。 一般会将这种 线程轮流使用 CPU 的做法称为并发(concurrent)。 引用 Rob Pike 的一段描述: 并发(concurrent)是同一时间对应(dealing with)多件事情的能力 并行(parallel)是同一时间动手做(doing)多件事情的能力 生活例子 : 家庭主妇做饭、打扫卫生、洗衣服,她一个人轮流交替做多件事,这时就是并发 家庭主妇雇了一个保姆,她们一起做这些事,这时既有并发、也有并行(这时会产生竞争,例如洗衣机只有一台,一个人用洗衣机时,另一个人只能等待 $main$0(Test10.java:19) at java.lang.Thread.run(Thread.java:748) 08:32:04.201 [main] DEBUG io.ray -
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接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。
本章将着重介绍Java并发编程的基础知识,从启动一个线程到线程间不同的通信方式,最后通过简单的线程池示例以及应用(简单的Web服务器)来串联本章所介绍的内容。 [4] Signal Dispatcher // 分发处理发送给JVM信号的线程 [3] Finalizer // 调用对象finalize方法的线程 [2] Reference (2)更快的响应时间 有时我们会编写一些较为复杂的代码(这里的复杂不是说复杂的算法,而是复杂的业务逻辑),例如,一笔订单的创建,它包括插入订单数据、生成订单快照、发送邮件通知卖家和记录货品销售数量等。 (3)更好的编程模型 Java为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁地考虑如何将其多线程化。 一旦开发人员建立好了模型,稍做修改总是能够方便地映射到Java提供的多线程编程模型上。
1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运行一个线程 一般来讲,CPU核心数和线程数的关系为核心数:线程数=1:1;但是如果使用了超线程技术,可以达到1:2甚至更多。 并行和并发 并行:同一时刻,可以同时处理事情的能力。 并发:与单位时间相关,在单位时间内可以处理事情的能力。 高并发编程的意义和注意事项 意义和好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化 问题。 2.
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。 033[0m') def task(): search() get() if __name__ == '__main__': for i in range(100): #模拟并发 q.full()) #满了 print(q.get()) print(q.get()) print(q.get()) print(q.empty()) #空了 View Code 生产者消费者模型 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题 ,然后自始至终使用这三个进程去执行所有任务(高级一些的进程池可以根据你的并发量,搞成动态增加或减少进程池中的进程数量的操作),不会开启其他进程,提高操作系统效率,减少空间的占用等。 如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()''' p.apply_async(func [, args [, kwargs
在单线程中,如果某操作无法满足先验条件,就只能失败,但在并发程序中先验条件可能会由于其他线程执行的操作而变成真。 java中等待某个条件为真的各种内置机制(包括等待和通知机制)都与内置加锁紧密关联。 4.1设计线程安全的类 在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量 找出约束状态变量的不可变性条件 建立对象状态的并发访问管理策略 4.3委托给线程安全的类 可以将共享资源委托给一个线程安全的类 synchronized,volatile或者任何一个线程安全类都对应于某种同步策略,用于在并发访问时确保数据的完整性。一定要在忘记之前记录下来。
; 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内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
Executor两级调度模型 在HotSpot虚拟机中,Java中的线程将会被一一映射为操作系统的线程。 在Java虚拟机层面,用户将多个任务提交给Executor框架,Executor负责分配线程执行它们; 在操作系统层面,操作系统再将这些线程分配给处理器执行。 2.
另外处理器还引入了L1、L2等多级缓存机制,而每个核心都有自己的缓存,这就导致了逻辑次序上后写入的数据未必真的写入了。 ---- Java内存模型 以上我们简单介绍了在多核并发的环境下CPU进行乱序执行优化时所带来的线程安全问题,为了保证线程安全,我们需要采取一些额外的手段去防止这种问题的发生。 不过在介绍如何采用实际手段解决这种问题之前,我们先来看看Java虚拟机是如何解决这种问题的:为了屏蔽各种硬件和操作系统内存的访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,所以Java 这意味着,如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程可能同时(并发)执行。 ---- 并发的风险与优势 ?
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 在高并发下的情况下如何保证取得的元素是最新的?
ThreadLocal 对于多任务,Java 标准库提供的线程池可以方便地执行这些任务,同时复用线程。那么如何在一个线程内传递状态? Java 标准库提供了一个特殊的 ThreadLocal,它可以在一个线程中传递同一个对象。 void processUser(user) { try { threadLocalUser.set(user); step1(); step2( threadLocalUser.remove(); } } void step1() { User u = threadLocalUser.get(); printUser(); } void step2( ) { User u = threadLocalUser.get(); checkUser(u.id); } 注意到普通的方法调用一定是同一个线程执行的,所以,step1()、step2(