您的位置:首页 > 编程语言 > Java开发

Java 并发编程知识点学习总结 (2)

2017-03-14 12:00 826 查看
Java 并发编程知识点学习总结 (2)

最近在清理思路,准备重新的阶段开始,想把自己过去学习的并发知识转入CSDN上来分享,也希望能得到大牛指点,不断更新学习笔记,欢迎直接留言交流,如转发请说明并附上链接,感谢!

第2篇从这里开始,是因为将volatile synchroized Lock放一起是最方便共享的方式(自己查资料会反复在这3个内容上研究,我把优缺点也一并整理了,代码部分慢慢上)。

continue…

14、volatile关键字

JVM内存模型对主内存(Main memory)和每个线程之间定义了一种可共享的抽象关系:线程之间被定义为共享变量的存储在主内存中,而每个线程都私有线程本地内存(Local memory)用于存放共享变量的读写副本。volatile修饰的变量保证了某一线程在对本地的副本修改后刷新给主内存,其他线程的本地副本会过期,强制其他线程必须从主内存中更新获取,使得共享变量的变化在线程中“可见”。

优点:

被修饰的变量具有线程可见(可见性),JVM禁止了在编译和执行时”围绕”相关变量的逻辑行操作指令顺序的重排(有序性);

缺点:

不能保证变量操作的原子性(CAS),不能同步,不会阻塞。

而内存语义相较重量级的 synchronized 关键字来说,不仅保证了可见性、原子性,还可将方法、代码段定义为同步处理。

15、synchronized关键字

对于普通方法使用synchronized,锁是当前实例对象;

对于static方法使用,锁是当前类的Class对象;

对于同步代码块,锁是synchronized括号里配置的对象;

底层实现原理是JVM基于进入和退出Monitor对象来实现,JVM在编译后将synchronized修饰的代码段开始位置插入 monitorenter 指令,在代码的结束处以及异常处理处插入 monitorexit 指令,并且保证其配对使用,任何对象都有一个monitor,持有它就是获取此对象的锁。

synchronized关键字,保证了在并发程序中对共享数据的正确访问。一个对象的方法为synchronized声明,只能被一个线程访问,如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,两个线程都不会阻塞。

对于共享数据的定义必须注意:两个线程是可以同时访问一个对象的两个不同的synchronized方法,一个是静态sync方法,另一个是非静态sync方法,如果两个方法的逻辑都会改变相同的数据,就会出现数据不一致的错误。

synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程都将阻塞等待(如果某一个线程异常处理不当,即出现线程阻塞保持的状态,cpu100%)。如果方法声明没有使用synchronized关键字,所有的线程都能在同一时间执行,因而总运行时间将降低。如果已知一个方法不会被一个以上线程调用,则无需synchronized声明。

可以递归调用被synchronized声明的方法,当线程访问一个对象的同步方法时,它还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。

通过synchronized关键字保护代码块(而不是整个方法)的访问:方法的其余部分保持在synchronized代码块之外,以获取更好的的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。便即在获取一幢楼人数的操作中,我们只使用synchronized关键字来保护对人数更新的代码片段,让其他操作不使用共享数据。这样使用synchronized时,必须把找对象引用作为传入参数。同一时间只有一个线程被允许访问这个synchronized代码。synchronized(this){…}

16、synchronized代码块

使用this来引用执行方法所属的对象,也可以使用其他的对象对其进行引用,在synchronized代码所属的类中,这样的对象就是为这个目的而创建的。分别使用不同的对象来控制不同的非依赖属性的同步访问,即同时允许不同的线程对对应不同的共享属性进行操作(如果将属性按照event来设计共享属性的原子性,将可以获得更好的性能)

优点:

可同步代码块,代码块执行完或执行遇到异常抛出,JVM会使线程自动释放锁;

缺点:

对并发读-读操作存在线程竞争和阻塞而影响效率,也不能像Lock的实现那样有tryLock()判断是否获取锁成功和延迟获取、lockInterruptibly()响应异常抛出用于中断当前等待获取锁的线程功能。

17、Lock接口,

使用Lock的实现来定义临界区,并且保证同一时间只有一个执行线程访问这个临界区时,必须是ReentrantLock实例。在临界区的开始,必须通过lock()方法获取对锁的控制,当线程A访问这个方法时,如果没有其他线程获取对这个锁的控制,lock()方法将让线程A获得锁并且允许它立刻执行临界区代码。否则,如果其他线程B正在执行这个锁保护临界区代码,lock()方法将让线程A休眠直到线程B执行完临界区代码。

在线程离开临界区的时候,我们必须使用unlock()方法来释放它持有的锁,以让其他线程来访问临界区。如果在离开临界区的时候没有调用unlock()方法,其他线程将永久地等待,从而导致了死锁(Deadlock)情景。如果在临界区使用了try-catch块,不要忘记将unlock()方法放入finally部分。

tryLock()与lock()方法的不同是:线程使用tryLock()不能获取锁,tryLock()会立即返回,它不会将线程置入休眠,返回的是true/false,要重视此方法的返回和对应行为,如果为false,程序不会在临界区执行代码,如果不采取必须的逻辑措施,会出现共享资源数据不一致问题。

ReentrantLock类允许使用递归的调用,如果一个线程获取了锁并且进行了递归调用,它将继续持有这个锁,因此调用lock()方法后也将立即返回,并且线程将继续执行递归调用,同时还可以调用其他的方法。

要避免两个线程都是试图获取对方拥有的锁,当两个或多个线程被阻塞并且它们在等待的锁永远不会被释放时,就会发生死锁。

18、ReadWriteLock接口与ReentrantReadWriteLock实现类,

通过接口的readLock()与writeLock()方法分别获取Lock接口的实现读写实现类,以供不同的环境使用,每个lock()应对应unlock()使用。

ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,它允许控制这两个类的行为,默认fair值是false,它称为非公平模式(non fair mode)。在非公平模式下,当有很多线程在等待锁(ReentrantLock和ReentrantReadWriteLock)时,锁将选择它们中的一个来访问临界区,这个选择是没有任何约束的。如果fair值是true,则称为公平模式(fair mode)。在公平模式下,当有很多线程在等待锁(ReentrantLock和ReentrantReadWriteLock)时,锁将选择等待时间最长的一个线程来访问临界区,这两种模式只适用于lock()与unLock()方法,对不会将其他线程置于休眠状态的tryLock()不具有影响。

Condition对象都要通过Lock实现类.newCondition()获取,因此每个条件都是有其绑定的锁对象的,必须在lock.lock()….lock.unlock()之间使用,它配合自定义的条件逻辑调用 .await()来阻塞到unlock()之前的逻辑代码,并使用其他Condition对象的 .signalAll() 来唤醒其他条件锁的阻塞线程继续进行其他对象对应的判断+执行的逻辑。

19、与锁绑定的所有条件对象都是通过Lock接口声明的newCondition()方法创建的,在使用条件的时候,必须获取这个条件绑定的锁,所带条件的代码必须在调用Lock对象的lock()与unlock()方法之间。

当线程调用条件的await()方法时,它将自动释放这个条件绑定的锁,其他某个线程才可以获取这个锁并且执行相同的操作,或者执行这个锁保护的另一个临界区代码。

当一个线程调用了条件对象的signal()或者signalAll()方法后,一个或多个条件在该条件上挂起的线程将被唤醒,但这并不能保证让它们挂起的条件已经满足,所以必须在while()循环中调用await(),在条件成立之前不能离开这个循环。如果条件不成立,将再次调用await()。

必须小心使用await()和signal()方法。如果调用了一个条件的await()方法,却从不调用它的signal()方法,这个线程将永久休眠。

因为调用await()进入休眠的线程会被中断,所以必须处理InterruptedException异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 并发