接触过并发的朋友相信对java的锁都不陌生,Java大体可以分为两种锁,隐式锁(内置锁)和显示锁;
OK,什么是内置锁?
很简单,就是我们经常写的同步块--synchronized。
内置锁使用比较简单,再方法上加入synchronized关键字或者在需要调用的地方添加synchronized(Obj){}块即可;
这里主要说一下显示锁:
OK,什么是显示锁?
很简单,通过一个显示的对象,来手动开启和关闭一个锁。Java util的concurrent包下面有一个locks包,包下有一个lock接口,该接口就是实现显示锁的底层类,ReentrantLock类就是最常用的显示锁。
为什么要显示锁?
优点:显示锁可以使你的代码更加灵活,提供锁可等待、可中断、可重入、公平性策略等机制;在性能方面,jdk5显示锁的性能远远大于内置锁,jdk6中也比内置锁高一些,6中对内置锁做了优化;
缺点:显示锁使用的危险性比内置锁要高,比如如果你忘记在finally块中释放锁,程序可以正常运行,但是,这是一个定时炸&弹,一旦爆发,不可控的结果很可怖!另,因为内置锁是在方法描述中添加的关键字,所以,当线程在执行的时候,在线程转储中能看到哪些调用栈帧中获得了哪些锁,非常有利于调试,而显示锁是一个对象,并没有办法看到哪些调用栈帧中获得了哪些锁。
显示锁怎么用?
显示锁有以下几种:ReentrantLock(保守锁,又有叫互斥锁)、RentrankReadWriteLock(读写锁)
1、ReentrantLock示例:
class Operations { // 创建一个显示锁非公平策略显示锁 final ReentrantLock rwLock = new ReentrantLock(); @SuppressWarnings("static-access") public void exec(long waitTime) { // 获得锁 rwLock.lock(); try { System.out.println(String.format( "Operation - doRead() - I am [%s] read now ", Thread .currentThread().getName())); Thread.currentThread().sleep(waitTime); System.out.println(String.format( "Operation - doRead() - I am [%s] read OK.%s", Thread .currentThread().getName(), waitTime)); } catch (Exception e) { e.printStackTrace(); } finally { // 释放锁 rwLock.unlock(); } } }
public static void main(String[] args) { final Operations op = new Operations(); // 调用线程1,获得锁后,停止1分钟 Thread r1 = new Thread(new Runnable() { @Override public void run() { op.exec(10000L); } }); Thread r2 = new Thread(new Runnable() { @Override public void run() { op.exec(20000L); } }); r1.start(); r2.start(); }
看Operations类,显示的使用一定是和try{}catch(){}finally{}一起使用,在finally中一定要释放锁,当调用Reentrant.lock方法后,他会判断计数是否为零,为零则认为对象没有被锁定,计数+1,调用unlock方法后,同理计数-1,如果为0,则释放锁,别的线程可获得该锁。注意,该锁可以被重入,同一个线程可以递归的获取锁!此锁最多支持同一个线程发起的 2147483648 个递归锁。试图超过此限制会导致由锁方法抛出的Error。上面代码输出:
Operation - doRead() - I am [Thread-0] read now
Operation - doRead() - I am [Thread-0] read OK.10000
Operation - doRead() - I am [Thread-1] read now
Operation - doRead() - I am [Thread-1] read OK.20000
OK,上面是最简单的使用,那么,当有一个线程A在持有锁的时候,又有10个线程在等待锁释放状态,这时候A释放了锁,那么,10个线程是先调用哪个?
上面代码是非公平的策略,即,让等待的10个线程抢锁,谁抢到归谁!如何配置公平策略?在显示锁的构造函数里面,有一个boolean参数,默认false(非公平),如果设置为true则为公平策略,即:哪个等待的时间最长,就优先让哪个线程获得锁。
总结:Reentrant实现了一种最标准的互斥锁,每次仅允许一个线程持有锁,但是很多情况下,对于一个公共的内存对象,你可能需要允许多个线程同时去读,仅一个线程去写,这时候就需要用到读-写锁
2、RentrankReadWriteLock
说明:
一个资源可以被多个读操作访问,或者只能被一个写操作访问,并且读、写操作不能同时存在。
示例:
class Operation { // 定义一个读写锁 final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); // 定义一个读锁 final Lock r = rwLock.readLock(); // 定义一个写锁 final Lock w = rwLock.writeLock(); @SuppressWarnings("static-access") public void doRead(long waitTime) { // 获得读锁 r.lock(); try { System.out.println(String.format( "Operation - doRead() - I am [%s] read now ", Thread .currentThread().getName())); // 持有读锁的状态下休眠 Thread.currentThread().sleep(waitTime); System.out.println(String.format( "Operation - doRead() - I am [%s] read OK.%s", Thread .currentThread().getName(), waitTime)); } catch (Exception e) { e.printStackTrace(); } finally { // 释放锁 r.unlock(); } } public void doWrite() { // 获得一个写锁 w.lock(); try { System.out.println(String.format( "Operation - doRead() - I am [%s] wirte now ", Thread .currentThread().getName())); } finally { // 释放一个写锁 w.unlock(); } } }
public class ReadWriteLockMain { public static void main(String[] args) { final Operation op = new Operation(); // 读线程1 休眠10s Thread r1 = new Thread(new Runnable() { @Override public void run() { op.doRead(10000L); } }); // 读线程2 休眠20s Thread r2 = new Thread(new Runnable() { @Override public void run() { op.doRead(20000L); } }); // 读线程3 休眠30s Thread r3 = new Thread(new Runnable() { @Override public void run() { op.doRead(30000L); } }); // 写操作线程 Thread w1 = new Thread(new Runnable() { @Override public void run() { op.doWrite(); } }); r1.start(); r2.start(); r3.start(); w1.start(); } }
在操作中,线程r1,r2,r3同时获得读锁,r1休眠10s后释放,r2休眠20s后释放,r3休眠30s释放,w1获取读锁;输出中,r1、r2、r3全部同时打印结果,当全部释放后,w1才能获得锁;
Operation - doRead() - I am [Thread-0] read now
Operation - doRead() - I am [Thread-1] read now
Operation - doRead() - I am [Thread-2] read now
Operation - doRead() - I am [Thread-0] read OK.10000
Operation - doRead() - I am [Thread-1] read OK.20000
Operation - doRead() - I am [Thread-2] read OK.30000
Operation - doRead() - I am [Thread-3] wirte now
相关推荐
读书笔记-Java并发编程实战-基础篇
Java并发编程与高并发解决方案-学习笔记-www.itmuch.com
《Java并发编程实战》个人读书笔记,非常详细: 1 简介 2 线程安全性 3 对象的共享 4 对象的组合 5 基础构建模块 6 任务执行 7 取消与关闭 8 线程池的使用 9 图形用户界面应用程序 10 避免活跃性危险 11 性能与可...
Java并发编程与高并发解决方案-学习笔记
Java并发编程与高并发解决方案笔记-基础篇.docx
当调用 start 启动线程时 Java 虚拟机会调 用该类的 run方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我 们可以继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的 ...
《Java并发编程实践》一书的个人读书笔记。主要列举包括各个章节的关键知识点,便于反复阅读和知识复习掌握。
java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记
java并发编程与并发解决方案是自己多年开发和学习的笔记,有助于(ˇˍˇ) 想~进一步提高的java开发工程师或架构师深入的学习java架构并发处理。同时,它也是 在实际工作中多年高并发解决方案和经验的总结
的管理,这种分离还在不同事务间划分了自然的分界线,在程序出现错误时可以很方便地进行恢复,还有利于提高程序的并发性。围绕任务执行来管理应用程序时,第一步要指明一个清晰的任务边界,理想情况下,任务是 独立...
线程安全就是对共享的、可变的状态进行管理,对象的状态就是它的数据,换句话说就是在不可控制的并发访问中保护数据。
Java并发实践-学习笔记,英文版pdf,英文版笔记。希望对大家有帮助。
Java高并发笔记.pdf
使用java.util.concurrent类库构造安全的并发应用程序的基础。共享其实就是某一线程的数据改变对其它线程可见,否则就会出现脏数据。
在实践中,委托是创建线程安全类最有效的策略之一:用已有的线程安全类来管理所 有状态即可。
java中没有提供任何机制,来安全是强迫线程停止手头的工作,Thread.stop和 Thread.suspend方法存在严重的缺陷,不能使用。程序不应该立即停止,应该采用中断这种协作机制来处理,正确的做法是:先清除当前进程中的...
详细的讲述了并发、高并发、CPU Cache、CPU多级缓存、CPU多级缓存 - 缓存一致性(MESI)、CPU多级缓存-乱序执行优化、Java内存模型(Java Memory Model,JMM)、并发的优势和风险...等等图文并茂详解
《java并发编程实战》读书笔记-第3章-对象的共享,脑图形式,使用xmind8制作 包括可见性、发布与逸出、线程封闭、不可变性、安全发布等内容
【Java技术资料】-(机构内训资料)Java并发体系学习思维笔记
java并发笔记