标签:nal 允许 dea 有用 private 1.2 eem ict 对象
多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制。这是Java并发编程中必须要理解的一个知识点。其实使用起来还是比较简单,但是一定要理解。
有几个概念一定要牢记:
synchronized的特点是自动释放锁,作用在方法时自动获取锁,任意对象都可做为锁,它是最常用的加锁机制,锁定几行代码,如下:
//--------同步方法1 public synchronized void test(){ //一段代码 } //--------同步方法2 private Object lock=new Object(); public void test2(){ synchronized(lock){ } }
synchronized可以手动指定锁,当作用在方法时会自动获取锁:
Lock的特点是,必须自己创建锁(锁类型已经指定为Lock的实现类,不能使用其它对象),必须自己释放锁。代码结构如下:
Lock l = ...; l.lock(); try { // 执行代码 } finally { l.unlock(); }
注意一定要在finally中释放锁,保证即便抛出异常也可以释放。
这是一个Lock的一个实例。
ReentrantLock(可重入锁),只有一个属性即是否公平。公平的含义是当有多个线程竞争锁时,按先来后到获得锁,但使用公平策略时,对效率有一定的影响。
加锁与解锁:
查询当前锁的相关状态:
Condition相关(见第五章):
当有一种情况,一个类中有多个方法需要同步,其中有读有写,如果所有的方法都使用同步,虽然可以保证数据的准确性,但当读取次数远大于写入次数的时候,同步就会对性能产生较大的影响。这时候,就有一种同步策略,读操作和读操作不互斥,读操作和写操作互斥,写操作和写操作互斥,这样可以提供性能。
虽然解释的很通俗但是使用它们还是要考虑以下情况(全部来自jdk api):
创建ReentrantReadWriteLock:
获得读或者写锁:
其它方法不怎么常用,若有具体需求可以查看API文档。
下面给一个简单的例子,一个并发访问的map:
class RWDictionary { private final Map<String, Data> m = new TreeMap<String, Data>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Data get(String key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } public String[] allKeys() { r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); } } public Data put(String key, Data value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { w.lock(); try { m.clear(); } finally { w.unlock(); } } }
但是还是不建议这么用,因为已经有ConcurrentHashMap了。
不可以,要么是随机的,要么是按照公平策略,优先安排等待时间最长的线程获取它想要的锁。
允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。
此外,writer 可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果 reader 试图获取写入锁,那么将永远不会获得成功。
重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
我觉得这个关键词水比较深,轻易不要把它用在同步上,volatile的中文意思是不稳定的。先找个JDK源码中的例子看一下(jdk1.8大约有130个类使用了volatile),Thread类中有:
private volatile Interruptible blocker;
这是线程的与中断有关的变量,当一个线程获得它需要中断时会立即抛出异常。下面是HashMap里面的一个变量:
transient volatile int modCount;
这个用来变量是一个计数器,用在当迭代时若对容器修改,便抛出异常的一个操作。
在什么情况下使用volatile:当一个变量需要做为一个信号,具有各种状态,改变状态将会引发一种操作的时候,就用volatile。
简单解释一下,当线程读取一个变量时,会对变量进行缓存,所以若对一种信号的变化比较敏感需要使用volatile,那就不能使用缓存,每次都需要读取实际的值。最后说一遍企图对volatile变量进行并发的i++,这样没有什么意义。
这是由新增Lock类而同时增加的类,毕竟对象的wait和notify方法要在synchronized语句块中,既然现在用Lock了当然要新增一种新的等待唤醒机制了,JDK API已经说得很清楚了:
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
而示例已经足够说明用法了,所以java的api文档是最好的参考资料:
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
这个就是最基本的用法。
没有具体的构造方法,通过Lock实现对象来获取Condition对象,Lock有下面的方法:newCondition() :返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition的方法和对象的等待唤醒类似:
等待变成了await方法,唤醒变成了signal方法。
【Java并发系列04】线程锁synchronized和Lock和volatile和Condition
标签:nal 允许 dea 有用 private 1.2 eem ict 对象
原文地址:http://www.cnblogs.com/yiwangzhibujian/p/6219047.html