java并发编程艺术总结.
- 格式:pdf
- 大小:966.19 KB
- 文档页数:27
java多线程编程实验总结与体会[Java多线程编程实验总结与体会]本次实验锻炼了我的Java多线程编程能力,让我更深入地了解了多线程编程的实现原理和技巧,同时也让我意识到在多线程环境下需要考虑的问题和注意事项。
下面我将结合具体实验内容,分享我在实践中的体会和思考。
1. 实验环境搭建在进行本次实验之前,我首先进行了实验环境的搭建。
我选择了Java SE Development Kit 8和Eclipse作为开发工具,同时也安装了JDK8的API 文档作为参考资料。
在搭建环境的过程中,我认识到Java的生态系统非常强大,附带的工具和资源也非常充足,这为我们开发和调试带来了很大的便利。
2. 多线程原理在研究多线程编程之前,我们需要对Java语言中的线程概念有一个清晰的认识。
线程是指操作系统能够进行运算调度的最小单位,是执行线程代码的路径。
在Java中,线程是一种轻量级的进程,可以同时运行多个线程。
每个线程都有自己的堆栈和局部变量,线程之间可以共享全局变量。
Java的多线程编程是通过Thread类和Runnable接口来实现的。
在实践中,我发现多线程编程最基本的原理是线程的并发执行。
多个线程可以在同一时间内执行不同的代码,提高CPU利用率,加快程序运行速度。
但是,在多线程并发执行的过程中,我们需要注意线程之间的同步问题,避免出现数据竞争和并发安全等问题。
3. 多线程的实现在Java中,我们可以通过继承Thread类或者实现Runnable接口来创建线程。
对于简单的线程,我们可以采用继承Thread类的方式来实现。
例如,在实验一中,我们在Main线程内创建了两个子线程,分别用来执行奇数和偶数的累加操作。
我们可以分别定义两个类OddThread和EvenThread继承Thread类,分别实现run()方法,用来执行具体的奇数和偶数累加操作。
然后在Main线程内创建OddThread和EvenThread 对象,并调用start()方法来启动两个线程,并等待两个线程完成操作。
java 并发总结
Java并发是指在一个程序中同时运行多个线程,这些线程共享程序的资源,包括内存、CPU时间和I/O设备等。
在Java语言中,使用关键字synchronized来实现同步访问共享资源,使用wait()和notify()方法来实现线程间的通信。
Java并发的优点是提高程序的执行效率,缩短程序的执行时间,增强程序的可靠性。
但是,Java并发也带来了一些问题,比如死锁、竞态条件和资源争用等。
为了避免这些问题,需要使用正确的并发控制技术,如锁、信号量、条件变量等。
Java并发最重要的概念是线程安全。
线程安全指多个线程在同时访问共享资源时不会出现竞态条件和资源争用等问题。
为了实现线程安全,可以使用同步机制,如synchronized关键字和Lock接口,也可以使用线程安全的类和方法,如ConcurrentHashMap和AtomicInteger等。
Java并发中还有一些其他的概念和技术,如线程池、Fork/Join 框架、并发集合等。
掌握这些概念和技术可以帮助我们更好地应对并发编程的挑战。
综上所述,Java并发是一项重要的技能,掌握好它可以使我们更加高效地编写并发程序。
- 1 -。
JAVA编程中的并发编程技巧在Java编程中,并发编程是一个非常重要的话题,因为多线程的应用程序在当今的软件开发中非常常见。
并发编程技巧可以提高程序的性能和效率,但同时也会引入一些潜在的问题,例如竞争条件和死锁。
在这篇文章中,我们将探讨一些Java并发编程中常用的技巧,以帮助开发人员更好地利用多线程编程。
1.使用线程池线程池是一个管理线程的工具,它可以帮助开发人员在需要时重复使用线程,而不是不断地创建新的线程。
这样可以减少资源消耗和提高性能。
在Java中,可以使用Executor框架来创建线程池。
Executor框架提供了一组用于管理线程池的接口和类。
```javaExecutorService executor = Executors.newFixedThreadPool(5);executor.submit(new MyTask();executor.shutdown(;```2.使用同步机制在并发编程中,多个线程可能会同时访问共享的资源,因此需要对这些资源进行同步。
在Java中,可以使用synchronized关键字或者ReentrantLock类来实现同步。
以下是一个使用synchronized关键字的例子:```javapublic synchronized void incremencount++;```3.使用并发集合类Java提供了一些并发集合类,例如ConcurrentHashMap和CopyOnWriteArrayList,这些类可以在多线程环境下安全地操作集合。
使用这些并发集合类可以避免在自己的代码中实现同步逻辑。
```javaConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(;map.put("key", "value");```4. 使用volatile关键字在Java中,volatile关键字可以用来标记一个变量,保证其在多线程环境下的可见性。
Java并发编程笔记之基础总结(⼀)⼀.线程概念说到线程就必须要提⼀下进程,因为线程是进程中的⼀个实体,线程本⾝是不会独⽴存在的。
进程是代码在数据集合上的⼀次运⾏活动,是系统进⾏资源分配和调度的基本单位,线程则是进程的⼀个执⾏路径,⼀个进程⾄少有⼀个线程,进程中的多个线程是共享进程的资源的。
操作系统在分配资源时候是把资源分配给进程的,但是 CPU 资源就⽐较特殊,它是分派到线程的,因为真正要占⽤ CPU 运⾏的是线程,所以也说线程是 CPU 分配的基本单位。
Java 中当我们启动 main 函数时候其实就启动了⼀个 JVM 的进程,⽽ main 函数所在线程就是这个进程中的⼀个线程,也叫做主线程。
如上图⼀个进程中有多个线程,多个线程共享进程的堆和⽅法区资源,但是每个线程有⾃⼰的程序计数器,栈区域。
、其中程序计数器是⼀块内存区域,⽤来记录线程当前要执⾏的指令地址,那么程序计数器为何要设计为线程私有的呢?前⾯说了线程是占⽤ CPU 执⾏的基本单位,⽽ CPU ⼀般是使⽤时间⽚轮转⽅式让线程轮询占⽤的,所以当前线程 CPU 时间⽚⽤完后,要让出 CPU,等下次轮到⾃⼰时候在执⾏。
那么如何知道之前程序执⾏到哪⾥了?其实程序计数器就是为了记录该线程让出 CPU 时候的执⾏地址,待再次分配到时间⽚时候就可以从⾃⼰私有的计数器指定地址继续执⾏了。
另外每个线程有⾃⼰的栈资源,⽤于存储该线程的局部变量,这些局部变量是该线程私有的,其它线程是访问不了的,另外栈还⽤来存放线程的调⽤栈帧。
堆是⼀个进程中最⼤的⼀块内存,堆是被进程中的所有线程共享的,是进程创建时候分配的,堆⾥⾯主要存放使⽤ new 操作创建的对象实例。
⽅法区则是⽤来存放进程中的代码⽚段的,是线程共享的。
⼆.线程创建⽅式与运⾏Java 中有三种线程创建⽅法,分别为实现 Runnable 接⼝的run⽅法、继承 Thread 类并重写 run ⽅法、使⽤ FutureTask ⽅式。
Java应用开发中的并发编程技巧在Java应用开发中,并发编程是一个重要的技术,它能够提高应用程序的性能和效率。
然而,并发编程也面临着一些挑战和难题。
本文将介绍一些Java应用开发中的并发编程技巧,帮助开发人员充分利用并发编程的优势。
1. 使用线程池在Java中,线程池是一种管理线程的机制,可以避免频繁地创建和销毁线程。
通过使用线程池,可以减少线程的开销,并且能够更好地控制并发执行的线程数量,提高系统的可伸缩性和稳定性。
2. 使用并发集合类Java提供了许多并发集合类,如ConcurrentHashMap、ConcurrentLinkedQueue等。
这些集合类在并发环境下提供了线程安全的操作,并且能够减少锁竞争,提高效率。
开发人员可以根据实际需求选择合适的并发集合类,以实现高效的并发操作。
3. 使用原子变量在并发编程中,原子变量是一种线程安全的变量类型。
Java提供了一系列的原子变量类,如AtomicInteger、AtomicLong等。
通过使用原子变量,可以避免线程之间的竞争条件,保证线程安全。
4. 使用锁机制锁机制是一种常用的并发控制手段,可以保证共享资源的一致性和线程安全性。
Java中的锁机制主要有synchronized关键字和Lock接口。
开发人员可以根据业务需求选择合适的锁机制,并且要注意避免死锁和锁竞争等问题。
5. 使用条件变量条件变量是一种线程间通信的机制,用于控制线程的执行顺序和条件。
Java提供了Condition接口,可以与锁机制结合使用,实现复杂的线程同步和通信。
开发人员可以利用条件变量,实现更加灵活和高效的并发编程。
6. 使用并发工具类Java提供了一系列的并发工具类,如CountDownLatch、CyclicBarrier等。
这些工具类可以帮助开发人员实现更加复杂的线程协作和控制逻辑,提高应用程序的性能和效率。
总结:在Java应用开发中,合理运用并发编程的技巧可以帮助开发人员充分利用多核处理器的优势,提高系统的并发能力和性能。
计算机编程知识:Java并发编程与关键技术Java并发编程与关键技术随着计算机技术不断发展,计算机系统的处理速度越来越快,内存大小也越来越大。
但是,单线程运行的程序在这些硬件条件下并未得到充分的利用。
因此,多线程技术应运而生。
Java并发编程是指在Java程序中使用多个线程来完成任务,以提高程序的处理效率。
Java并发编程技术包括三个核心部分:线程、锁和共享变量。
线程是在操作系统层面上区分任务的基本单位,锁的作用是为了防止多个线程同时访问共享变量造成的并发问题。
而共享变量则是多个线程之间传递数据的媒介。
在Java中,使用Thread类来创建线程。
线程的创建有两种方式:继承Thread类和实现Runnable接口。
使用实现Runnable接口的方式可以避免Java单一继承的限制,使得程序更加灵活。
在进程中可能会有多个线程同时运行,此时线程之间的执行顺序是无序的。
如果希望线程按照一定的顺序执行,可以使用join()方法。
该方法让当前线程等待指定线程执行完毕后再继续执行,从而保证线程的顺序性。
锁分为两种:互斥锁和读写锁。
互斥锁是为了保证同一时间只有一个线程访问共享变量,它使用synchronized关键字来实现。
而读写锁是为了允许多个读操作同时执行,但写操作执行时必须独占锁,使用ReentrantReadWriteLock类来实现。
由于多个线程之间访问共享变量可能会产生并发问题,Java提供了解决并发问题的关键技术:原子类和volatile关键字。
原子类是一组以原子方式进行操作的类,例如AtomicInteger、AtomicLong等,它们能够在多线程并发的情况下保证数据的一致性。
而volatile关键字则是保证数据的可见性,即当一个线程对volatile变量进行修改时,其他线程能够立即看到这个变量的最新值。
除了锁和并发解决方案以外,在处理多线程程序时还要注意死锁问题。
死锁指两个或者两个以上的线程在执行的过程中因相互等待资源而造成的一种互相等待的现象,使线程无法运行。
Java并发编程掌握多线程的精髓随着计算机技术的发展,多核处理器的出现使得并发编程成为了当今软件开发中不可忽视的重要环节。
而多线程作为实现并发的方法之一,是Java中不可或缺的一部分。
Java并发编程中的多线程涉及到了很多复杂的概念和技术,掌握多线程的精髓对于编写高效且安全的并发程序至关重要。
本文将分享Java并发编程中掌握多线程的精髓。
1. 多线程的基础概念多线程是指在同一程序中启动多个线程并发执行,每个线程独立执行不同的任务。
在Java中,使用Thread类和Runnable接口可以实现多线程编程。
其中Thread类是一个线程的抽象,而Runnable接口定义了线程的任务。
2. 创建多线程在Java中,创建多线程可以通过两种方式:继承Thread类和实现Runnable接口。
继承Thread类需要重写run()方法,而实现Runnable接口需要实现run()方法。
创建多线程的方式取决于实际需求和设计原则。
3. 线程同步与互斥多线程程序可能涉及到多个线程访问共享资源的情况,这时就需要线程同步来保证数据的一致性和安全性。
Java提供了多线程同步的关键字synchronized,它可以修饰代码块或方法,保证同一时间内只有一个线程可以执行被修饰的代码。
4. 锁的概念与使用在Java中,锁是用来控制对共享资源的访问的工具。
常见的锁包括ReentrantLock和synchronized关键字。
ReentrantLock是Java提供的可重入锁,它提供了更多的灵活性和功能。
5. 线程池的作用与使用线程池是管理和重用线程的技术,它可以避免频繁创建和销毁线程的开销。
在Java中,通过使用线程池可以更好地管理和调度线程的执行,并提高程序的性能和响应速度。
6. 线程间的通信多线程中,线程之间的通信是一个重要的问题。
Java提供了多种线程通信的方式,如wait()、notify()、notifyAll()等方法。
1、线程的状态1.1创建线程的两种方式,接口和线程类。
利用接口的好处:更好的体现面向对象的思想,可以避免由于Java的单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;(同步问题)适合多个相同程序代码的线程区处理同一资源的情况。
(关注微信订阅号:javaedu)1.2线程就绪等待调度运行start()方法。
1.3线程的中断这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行。
1.4、线程挂起和恢复(挂起还拥有对象锁,死锁)线程的挂起和恢复实现的正确方法是:通过设置标志位,让线程在安全的位置挂起1.5利用多线程模拟同步运行用jion方法,mThread.jion()表示该线程运行完毕后,在运行调用它的线程。
1.6sleep休眠当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;1.7stop线程停止stop方法突然终止线程(持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一致性状态的数据,突然释放锁可能使某些对象中的数据处于不一致状态)1.8线程可以阻塞于四种状态:(参考资料:/RA5iKhq)当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止(若制定了超时值的话)线程阻塞与不同I/O的方式有多种。
常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)。
2、线程的种类守护线程与线程阻塞的四种情况Java中有两类线程:User Thread(用户线程)、DaemonThread(守护线程)用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。
java并发编程总结我跟你说啊,这java并发编程啊,就像一群小蚂蚁搬家似的。
你看啊,每个小蚂蚁就像是一个线程,都有自己要干的事儿。
我刚开始接触这java并发编程的时候啊,那脑袋就跟浆糊似的。
那些个概念啊,就像一群调皮的小鬼,在我眼前晃悠来晃悠去,什么线程安全啊,锁机制啊,我看着就头疼。
我那时候就皱着眉头,眼睛死死盯着那些代码,就好像那些代码会突然变成妖怪跑了似的。
这线程啊,就像一个个小工人。
有时候呢,它们很听话,各自干各自的活,有条不紊的。
但有时候啊,就乱套了。
就像几个小工人都要抢同一个工具,你争我夺的,这时候就容易出乱子。
这个时候就得用到锁机制,就好比给这个工具上了一把锁,一次只能一个小工人用,其他小工人就得等着。
我就记得我有一次啊,没把这个锁机制弄好,那程序运行起来就跟疯了似的,到处出错。
我就坐在那儿,抓着头发,嘴里嘟囔着:“这可咋整,这可咋整。
”再说说那个并发容器,这就像一个个特制的小盒子。
普通的盒子可能装东西的时候会有问题,但是这并发容器就不一样了。
它能让那些小蚂蚁,哦不,线程,很好地把东西放进去拿出来,不会乱了套。
我有个朋友啊,他也在学这个java并发编程。
有一回我们俩凑一块儿研究这并发容器呢,他就一脸迷惑地问我:“你说这东西咋就这么神奇呢?”我就笑着跟他说:“我也还没全整明白呢,就像雾里看花似的。
”这java并发编程里还有个线程池,这就像一个大的劳务市场。
那些小工人,也就是线程,都在这个劳务市场里等着被分配任务。
要是没有这个线程池啊,就好比每次有任务都得重新去招人,那多麻烦啊。
但是管理这个线程池也不容易啊,就像管理一个大市场,得知道什么时候让工人进去干活,什么时候让工人休息,可复杂了。
我有次试着调整线程池的参数,弄了半天,感觉就像在黑暗里摸索,眼睛瞪得老大,就盼着能一下子找到那个合适的点。
在这java并发编程的世界里啊,就像在一个大的迷宫里。
有时候你觉得你找到了出口,可一转弯又迷了路。
java并发编程知识点备忘最近有在回顾这⽅⾯的知识,稍微进⾏⼀些整理和归纳防⽌看了就忘记.会随着进度不断更新内容,⽐较零散但尽量做的覆盖⼴⼀点.如有错误烦请指正~java线程状态图线程活跃性问题死锁饥饿活锁饥饿原因:⾼优先级造成低优先级⽆法运⾏(概率吧)⽆法进⼊同步块(⽐如进⼊的线程陷⼊死循环)⽆法被唤醒(没有notify)线程安全性问题的条件:多线程环境下多线程共享同个资源存在⾮原⼦性操作破坏掉其中⼀条即可synchronized内置锁涉及字节码:monitorenter monitorexit锁的信息存在对象头中偏向锁轻量级锁重量级锁相关:参考资料:单例模式实现积极加载懒加载:double check,静态内部类枚举实现参考资料:volatile硬盘 - 内存 - CPU缓存lock指令: 当前处理器缓存⾏写回内存,其他处理器该内存地址数据失效参考资料:原⼦更新类JDK5开始提供,J.U.C.atomic包底层多是使⽤了Unsafe类的CAS操作.基本分类:原⼦更新基本类型: AtomicInteger原⼦更新数组: AtomicIntegerArray原⼦更新引⽤类型: AtomicReference原⼦更新字段: AtomicIntegerFieldUpdater参考资料:AQS可以作为锁同步器的基础.本⾝是⼀个模板类,只需要重写所需的抽象⽅法:获取和释放(独占)的分析:获取成功下直接返回.如果获取失败,并且被中断下就直接返回.如果没有抢占成功,则判断是否需要park,park后线程将不再被调度,park解除后返回中断状态,(但这个实现中即使被中断也没关系,在acquireInterruptibly中则会直接抛错)是否需要park是由上⼀个节点的状态来决定的,在⽗节点为SIGNAL的情况下需要park,如果⼤于0(表⽰cancel,这个节点以及前⾯连续的cancel 节点需要从队列⾥出去了)这⾥可以看到不是SIGNAL的情况下⽗节点要么被移出要么被设置为了SIGNAL,注意这段代码外层是⼀个死循环,所以最终如果⼀直没抢占到,这个线程肯定会被park的.这个设计在源码⾥就有说明,SIGNAL不是⽤来控制当前节点⽽是控制他的⼦节点,同时也说明这个节点的⼦节点正在等待.对应release的时候也是,释放的节点(头节点)需要判断⾃⼰的状态是否⼩于0(为0的话说明没有⼦节点被添加要唤醒).⾃⼰看源码的简单分析,只看了这⼀个流程.公平和⾮公平主要就是对于队列的操作,公平的实现直接获取下⼀个节点即可.更为详细的请参考:2018年6⽉09⽇20点42分读写锁要注意⽤了⼀个state存了两个锁的状态共享锁(读锁)⾼16位互斥锁(写锁)低16位读锁获取的前置条件:读锁要获取前提是没有写锁或者有写锁但是写锁是⾃⼰持有的这也是锁能降级的实现原理⼀个线程持有写锁后可以⾃⼰持有读锁要要获取读锁的话,得保证没有读锁,这也就是ReentrantReadWriteLock⽆法实现升级的原因.所以千万不要写出这样的代码readLock.lock();....writeLock.lock();....readLock.unlock();....writeLock.lock();获取写锁时就会发⽣死锁.2018年7⽉19⽇02点37分Update:2018年4⽉26⽇02点38分 init2018年5⽉5⽇16点17分单例-原⼦更新类。
1、并发编程的挑战上下文切换:CPU通过时间片分配算法来循环执行任务,在切换任务的过程中,会保存上一个任务的状态,以便在下次切换回这个任务时,可以再加载这个任务的状态。
减少上下文切换的方法:无锁并发编程、CAS算法、使用最少线程和使用协程2、Java并发机制的底层实现原理Java代码编译后java字节码然后加载到JVM然后转化为CUP执行的汇编,java的并发依赖于JVM的实现与CPU的指令。
1. Volatile的应用可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
后面还是详细介绍volatile关键字2. synchronized的实现原理与应用1) synchronized简介synchronized在JVM中实现,JVM基于进入与退出Monitor对象来实现方法同步与代码块同步,在同步代码块前后分别形成monitorenter和monitorexit这两个字节码。
synchronized的锁存放在java对象头里,在对象头里有关于锁的信息:轻量级锁,重量级锁,偏向锁。
(对象头里还包括:GC标记、分代年龄、线程ID、HashCode等。
)2) 锁的介绍级别从低到高:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁能升级不能降级,目的是提高获取锁和释放锁的效率。
偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。
为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁(对象)时,会在对象头里记录偏向锁的线程ID。
以后该线程进入与退出同步块时不需要进行CAS操作来加锁和解锁。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
轻量级锁:线程通过CAS来获取锁(线程栈帧中有存储锁记录的空间,将Mask Word复制到锁记录中,然后尝试使用CAS将对象头中的Mask Word替换成指向锁记录的指针),如果成功,就获取锁,失败就尝试自旋来获取锁。
重量级锁:为了避免在轻量级中无用的自旋(比如获取到锁的线程被阻塞住了),JVM可以将锁升级成重量级。
当锁处于这个状态时,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程。
锁优点缺点使用场景偏向锁加锁与解锁不需要额外的消耗。
线程存在竞争时,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块轻量级锁竞争的线程不会阻塞,提高了程序的响应速度始终得不到锁竞争的线程,自旋消耗CPU追求响应时间,同步块执行速度非常快重量级锁线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢追求吞吐量,同步块执行时间较长3. 原子操作的实现原理原子:不能被中断的一个或一系列操作。
在java中可以通过锁和循环CAS的方式来实现原子操作。
1) 使用循环CAS实现原子操作利用处理器提供的CAS指令来实现,自旋CAS现在的基本思路就是循环进行CAS操作直达成功为止。
CAS实现原子操作的三大问题:1. ABA问题:如果一个值原来是A后来变成了B,然后又变成了A。
那么使用CAS时进行检查的时候,该值实际上发送了变化。
解决:加入版本号,如:1A-2B-3A2. 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3. 只能保证一个共享变量的原子操作。
CAS不能对多个共享变量进行。
JDK1.5,提供的AtomicReference类可以保证引用对象之间的原子性,可以将多个变量放在一个对象中进行。
2) 使用锁机制实现原子操作锁机制保证了只有获取到锁的线程才能操作锁定的内存区域。
3、Java内存模型1. Java内存模型的基础1) 并发编程模型的两个关键问题:线程之间的通信与同步。
在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信,在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来进行显示通信。
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
在共享内存模型下,同步是显示进行,程序员必须显示指定某个方法或某段代码需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息接收之前,因此同步是隐式进行的。
Java的并发采用的是共享内存模型,java线程之间的通信总是隐式的进行,整个通信过程对程序员透明。
2) Java内存模型的抽象结构Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。
线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。
并且每个线程不能访问其他线程的工作内存。
3) 从源代码到指令序列的重排序源代码-编译期优化重排序-指令级并行重排序-内存系统重排序-最终执行的指令序列在执行程序时,为了提高性能,编译期和处理器常常会对指令做重排序。
可以加入内存屏障禁止指令重排序。
4) Happens-before介绍1. 程序次序规则:一个单线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作2. 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作3. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始Happens-before并不意味着前一个操作必须要在后一个操作之前执行,而是要求前一个操作(执行结果)对后一个操作可见。
(若两个操作之间没有依赖,那么可以重排序两个操作)5) as-if-serial语义不管怎么重排序,单线程的执行结果不能被改变。
2. 并发编程的三个重要概念1) 原子性即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
2) 可见性可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
3) 有序性即程序执行的顺序按照代码的先后顺序执行。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before 原则。
3. 深入理解volatile关键字1) Volatile的介绍一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 通过内存屏障,禁止进行指令重排序优化。
volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中。
Volatile的写-读与锁的释放-获取具有相同的内存语义。
Volatile可以保证可见性,不能保证原子性(最经典的是volatile变量的自增操作,在多线程中出现结果错误),能保证一定程度上的有序性。
关于volatile的有序性,volatile关键字禁止指令重排序有两层意思: 1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行; 2)在进行指令优化时,不能将在对volatile变量前面的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
2) volatile的原理和实现机制—加入内存屏障 “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令” lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能: 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 2)它会强制将对缓存的修改操作立即写入主存; 3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
3) Volatile的运用场景 synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率;而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。
通常来说,使用volatile 必须具备以下2个条件: 1)对变量的写操作不依赖于当前值(自增) 2)该变量没有包含在具有其他变量的不变式中(不懂)可以用作状态标记量volatile boolean flag = false;while(!flag){doSomething();}public void setFlag() {flag = true;}单列模式中double checkclass Singleton{private volatile static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if(instance==null) {synchronized (Singleton.class) {if(instance==null)instance = new Singleton();}}return instance;}}4. 锁的内存语义锁的实现主要是用了一个volatile的状态变量,这部分放在锁的实现来讲。