资源预览内容
第1页 / 共18页
第2页 / 共18页
第3页 / 共18页
第4页 / 共18页
第5页 / 共18页
第6页 / 共18页
第7页 / 共18页
第8页 / 共18页
第9页 / 共18页
第10页 / 共18页
亲,该文档总共18页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
JavaJava 同步、异步相关知识点同步、异步相关知识点Java 同步、异步相关知识点关键字: java 同步、异步相关知识点一、关键字:thread(线程) 、thread-safe(线程安全)、intercurrent(并发的)synchronized(同步的)、asynchronized(异步的)、volatile(易变的) 、atomic(原子的) 、share(共享)二、总结背景:一次读写共享文件编写,嚯,好家伙,竟然揪出这些零碎而又是一路的知识点。于是乎,Google 和翻阅了Java 参考大全 、Effective Java Second Edition ,特此总结一下供日后工作学习参考。三、概念:1、 什么时候必须同步?什么叫同步?如何同步?要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。这归因于 java 语言规范的内存模型,它规定了:一个线程所做的变化何时以及如何变成对其它线程可见。因为多线程将异步行为引进程序,所以在需要同步时,必须有一种方法强制进行。例如:如果 2 个线程想要通信并且要共享一个复杂的数据结构,如链表,此时需要确保它们互不冲突,也就是必须阻止 B 线程在 A 线程读数据的过程中向链表里面写数据(A 获得了锁,B 必须等 A 释放了该锁) 。为了达到这个目的,java 在一个旧的的进程同步模型监控器(Monitor)的基础上实现了一个巧妙的方案:监控器是一个控制机制,可以认为是一个很小的、只能容纳一个线程的盒子,一旦一个线程进入监控器,其它的线程必须等待,直到那个线程退出监控为止。通过这种方式,一个监控器可以保证共享资源在同一时刻只可被一个线程使用。这种方式称之为同步。 (一旦一个线程进入一个实例的任何同步方法,别的线程将不能进入该同一实例的其它同步方法,但是该实例的非同步方法仍然能够被调用) 。错误的理解:同步嘛,就是几个线程可以同时进行访问。同步和多线程关系:没多线程环境就不需要同步;有多线程环境也不一定需要同步。锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility) 。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题小结:为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如 java 集合框架中 Hashtable 和 Vector 是线程安全的。我们的大部分程序都不是线程安全的,因为没有进行同步,而且我们没有必要,因为大部分情况根本没有多线程环境) 。2、 什么叫原子的(原子操作)?Java 原子操作是指:不会被打断地的操作。 (就是做到互斥 和可见性?!)那难道原子操作就可以真的达到线程安全同步效果了吗?实际上有一些原子操作不一定是线程安全的。那么,原子操作在什么情况下不是线程安全的呢?也许是这个原因导致的:java 线程允许线程在自己的内存区保存变量的副本。允许线程使用本地的私有拷贝进行工作而非每次都使用主存的值是为了提高性能(本人愚见:虽然原子操作是线程安全的,可各线程在得到变量(读操作)后,就是各自玩弄自己的副本了,更新操作(写操作)因未写入主存中,导致其它线程不可见) 。那该如何解决呢?因此需要通过 java 同步机制。在 java 中,32 位或者更少位数的赋值是原子的。在一个 32位的硬件平台上,除了 double 和 long 型的其它原始类型通常都是使用 32 位进行表示,而 double 和 long 通常使用 64 位表示。另外,对象引用使用本机指针实现,通常也是 32 位的。对这些 32 位的类型的操作是原子的。这些原始类型通常使用 32 位或者 64 位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java 语言保证的是原始类型的表数范围而非 JVM 中的存储大小。因此,int 型总是有相同的表数范围。在一个 JVM 上可能使用 32 位实现,而在另一个 JVM 上可能是 64 位的。在此再次强调:在所有平台上被保证的是表数范围,32 位以及更小的值的操作是原子的。3、 不要搞混了:同步、异步举个例子:普通 B/S 模式(同步)AJAX 技术(异步)同步:提交请求-等待服务器处理-处理完返回 这个期间客户端浏览器不能干任何事异步:请求通过事件触发-服务器处理(这是浏览器仍然可以作其他事情)-处理完毕可见,彼“同步”非此“同步”我们说的 java 中的那个共享数据同步(synchronized)一个同步的对象是指行为(动作) ,一个是同步的对象是指物质(共享数据) 。4、 Java 同步机制有 4 种实现方式:(部分引用网上资源) ThreadLocal synchronized( ) wait() 与 notify() volatile目的:都是为了解决多线程中的对同一变量的访问冲突ThreadLocalThreadLocal 保证不同线程拥有不同实例,相同线程一定拥有相同的实例,即为每一个使用该变量的线程提供一个该变量值的副本,每一个线程都可以独立改变自己的副本,而不是与其它线程的副本冲突。优势:提供了线程安全的共享对象与其它同步机制的区别:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信;而 ThreadLocal 是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要多个线程进行同步了。volatilevolatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。优势:这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。缘由:Java 语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile 关键字就是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。线程为了提高效率,将某成员变量(如 A)拷贝了一份(如B) ,线程中对 A 的访问其实访问的是 B。只在某些动作时才进行 A和 B 的同步,因此存在 A 和 B 不一致的情况。volatile 就是用来避免这种情况的。 volatile 告诉 jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。sleep() vs wait()sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。(如果变量被声明为 volatile,在每次访问时都会和主存一致;如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁时变量被同步。 )四、例子:Demo1:package test.thread;/* author ydj*/class SynTest/非同步static void method(Thread thread)System.out.println(“begin “+thread.getName();tryThread.sleep(2000);catch(Exception ex)ex.printStackTrace();System.out.println(“end “+thread.getName();/同步方式一:同步方法synchronized static void method1(Thread thread)/这个方法是同步的方法,每次只有一个线程可以进来System.out.println(“begin “+thread.getName();tryThread.sleep(2000);catch(Exception ex)ex.printStackTrace();System.out.println(“end “+thread.getName();/同步方式二:同步代码块static void method2(Thread thread)synchronized(SynTest.class) System.out.println(“begin “+thread.getName();tryThread.sleep(2000);catch(Exception ex)ex.printStackTrace();System.out.println(“end “+thread.getName();/同步方式三:使用同步对象锁private static Object _lock1=new Object();private static byte _lock2=;/据说,此锁更可提高性能。源于:锁的对象越小越好static void method3(Thread thread)synchronized(_lock1) System.out.println(“begin “+thread.getName();tryThread.sleep(2000);catch(Exception ex)ex.printStackTrace();System.out.println(“end “+thread.getName();public static void main(String args)/启动 3 个线程,这里用了匿名类for(int i=0;i3;i+)new Thread()public void run()method(this);/method1(this);/method2(this);/method3(this);.start();/*执行 method()方法结果:begin Thread-0begin Thread-2begin Thread-1end Thread-1end Thread-0end Thread-2说明了:在没有同步限制的条件下,多个线程可以同时进入一个对象的方法中操作。这样对共享可
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号