一、前言

  多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制。这是Java并发编程中必须要理解的一个知识点。其实使用起来还是比较简单,但是一定要理解。

  有几个概念一定要牢记:

  • 加锁必须要有锁
  • 执行完后必须要释放锁
  • 同一时间、同一个锁,只能有一个线程执行

二、synchronized

  synchronized的特点是自动释放锁作用在方法时自动获取锁,任意对象都可做为锁,它是最常用的加锁机制,锁定几行代码,如下:

//--------同步方法1
public synchronized void test(){
//一段代码
}
//--------同步方法2
private Object lock=new Object();
public void test2(){
synchronized(lock){ }
}

2.1 synchronized获取的锁

  synchronized可以手动指定锁,当作用在方法时会自动获取锁:

  • 作用于普通方法获得当前对象锁,等价于synchronized(this)
  • 作用于静态方法获得类锁,等价于synchronized(类.class)

三、Lock

  Lock的特点是,必须自己创建锁(锁类型已经指定为Lock的实现类,不能使用其它对象),必须自己释放锁。代码结构如下:

Lock l = ...;
l.lock();
try {
// 执行代码
} finally {
l.unlock();
}

  注意一定要在finally中释放锁,保证即便抛出异常也可以释放。

3.1 ReentrantLock详解

  这是一个Lock的一个实例。

3.1.1 构造方法

  ReentrantLock(可重入锁),只有一个属性即是否公平。公平的含义是当有多个线程竞争锁时,按先来后到获得锁,但使用公平策略时,对效率有一定的影响。

  • ReentrantLock() :最常用,获取一个不公平的锁,
  • ReentrantLock(boolean fair):获取指定公平策略的锁。

3.1.2 方法摘要

  加锁与解锁:

  • void lock() :获取锁,这是最常用的方法。
  • void unlock() :释放锁,必须要的方法,使用完一定要释放锁。
  • boolean tryLock() :仅在调用时锁未被另一个线程保持的情况下,才获取该锁。会破坏公平性原则。
  • boolean tryLock(long timeout, TimeUnit unit) :如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
  • void lockInterruptibly() :如果当前线程未被中断,则获取锁。

  查询当前锁的相关状态:

  • boolean isLocked() :查询此锁是否由任意线程保持。
  • boolean isHeldByCurrentThread() :查询当前线程是否保持此锁。
  • boolean isFair() :如果此锁的公平设置为 true,则返回 true。
  • int getHoldCount() :查询当前线程保持此锁的次数。
  • int getQueueLength() :返回正等待获取此锁的线程估计数。
  • boolean hasQueuedThread(Thread thread) :查询给定线程是否正在等待获取此锁。
  • boolean hasQueuedThreads() :查询是否有些线程正在等待获取此锁。

   Condition相关(见第五章):

  • Condition newCondition() :返回用来与此 Lock 实例一起使用的 Condition 实例。
  • int getWaitQueueLength(Condition condition):返回等待与此锁相关的给定条件的线程估计数。

四、ReadWriteLock

  当有一种情况,一个类中有多个方法需要同步,其中有读有写,如果所有的方法都使用同步,虽然可以保证数据的准确性,但当读取次数远大于写入次数的时候,同步就会对性能产生较大的影响。这时候,就有一种同步策略,读操作和读操作不互斥,读操作和写操作互斥,写操作和写操作互斥,这样可以提供性能。

  虽然解释的很通俗但是使用它们还是要考虑以下情况(全部来自jdk api):

  1. 在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频繁。Reader 优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。公平或者“按次序”实现也是有可能的。
  2. 在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。
  3. 确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?
  4. 可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗?

4.1 创建与获取锁

  创建ReentrantReadWriteLock:

  • ReentrantReadWriteLock():创建默认非公平的锁。
  • ReentrantReadWriteLock(boolean fair):创建指定公平策略的锁。

  获得读或者写锁:

  • readLock() :返回用于读取操作的锁。
  • writeLock() :返回用于写入操作的锁。

4.2 其他方法

  其它方法不怎么常用,若有具体需求可以查看API文档。

4.3 示例

  下面给一个简单的例子,一个并发访问的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了。

4.4 ReentrantReadWriteLock理解

4.4.1 锁顺序是否可以按读锁或者写锁来优先指定

  不可以,要么是随机的,要么是按照公平策略,优先安排等待时间最长的线程获取它想要的锁。

4.4.2 什么是锁重入

  允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。

  此外,writer 可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果 reader 试图获取写入锁,那么将永远不会获得成功。

4.4.3 什么是锁降级

  重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。

五、volatile

  我觉得这个关键词水比较深,轻易不要把它用在同步上,volatile的中文意思是不稳定的。先找个JDK源码中的例子看一下(jdk1.8大约有130个类使用了volatile),Thread类中有:

private volatile Interruptible blocker;

  这是线程的与中断有关的变量,当一个线程获得它需要中断时会立即抛出异常。下面是HashMap里面的一个变量:

transient volatile int modCount;

  这个用来变量是一个计数器,用在当迭代时若对容器修改,便抛出异常的一个操作。

5.1 小总结

  在什么情况下使用volatile:当一个变量需要做为一个信号,具有各种状态,改变状态将会引发一种操作的时候,就用volatile。

  简单解释一下,当线程读取一个变量时,会对变量进行缓存,所以若对一种信号的变化比较敏感需要使用volatile,那就不能使用缓存,每次都需要读取实际的值。最后说一遍企图对volatile变量进行并发的i++,这样没有什么意义。

六、Condition

  这是由新增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();
}
}
}

  这个就是最基本的用法。

6.1 构造方法

   没有具体的构造方法,通过Lock实现对象来获取Condition对象,Lock有下面的方法:newCondition() :返回用来与此 Lock 实例一起使用的 Condition 实例。

6.2 普通方法

  Condition的方法和对象的等待唤醒类似:

  • void await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
  • boolean await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  • long awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  • void awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。
  • boolean awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
  • void signal() :唤醒一个等待线程。
  • void signalAll() :唤醒所有等待线程。

  等待变成了await方法,唤醒变成了signal方法。

【Java并发系列04】线程锁synchronized和Lock和volatile和Condition的更多相关文章

  1. Java 并发:内置锁 Synchronized

    摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程訪问某一共享.可变数据时,始终都不会导致数据破坏以及其它不该出现的结果. 而全部的并发模式在解决问题时,採 ...

  2. java并发系列(二)-----线程之间的协作(wait、notify、join、CountDownLatch、CyclicBarrier)

    在java中,线程之间的切换是由操作系统说了算的,操作系统会给每个线程分配一个时间片,在时间片到期之后,线程让出cpu资源,由其他线程一起抢夺,那么如果开发想自己去在一定程度上(因为没办法100%控制 ...

  3. Java并发机制(2)--synchronized与Lock

    本内容整理自:博客园-海 子-java并发编程系列-http://www.cnblogs.com/dolphin0520/category/602384.html 1.基础: 1.什么时候出现线程安全 ...

  4. Java并发基础04. 线程技术之死锁问题

    我们知道,使用 synchronized 关键字可以有效的解决线程同步问题,但是如果不恰当的使用 synchronized 关键字的话也会出问题,即我们所说的死锁.死锁是这样一种情形:多个线程同时被阻 ...

  5. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  6. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  7. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

  8. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  9. Java 并发系列之二:java 并发机制的底层实现原理

    1. 处理器实现原子操作 2. volatile /** 补充: 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题. 轻量级的synchronized,不会造成阻塞.性能比s ...

随机推荐

  1. INTRODUCTION TO BIOINFORMATICS

    INTRODUCTION TO BIOINFORMATICS      这套教程源自Youtube,算得上比较完整的生物信息学领域的视频教程,授课内容完整清晰,专题化的讲座形式,细节讲解比国内的京师大 ...

  2. 【先定一个小目标】Windows下安装MongoDB 3.2

    1.MongoDB 安装 官网提供了三个版本下载: - MongoDB for Windows 64-bit 适合 64 位的 Windows Server 2008 R2, Windows 7 , ...

  3. window 使用vagrant搭建开发开发环境

    # -*- mode: ruby -*-# vi: set ft=ruby : # All Vagrant configuration is done below. The "2" ...

  4. 解决Myeclipse PermGen space问题

    myeclipse配置web服务器配置 Window—Preferences—Myeclipse—Servers—tomcat JDK的Optional Java VM arguments配置为:-X ...

  5. 现代软件工程作业 第二章 学习github笔记

    在网上大量资料的辅助下,学习了github的基本使用方法,尝试了一些常见的命令.为了便于记忆总结了自己的学习内容. 1.首先需要在github的官网上注册一个帐号,并新建一个repository,选这 ...

  6. JavaScript - 原型

    一切皆为对象 殊不知,JavaScript的世界中的对象,追根溯源来自于一个 null 「一切皆为对象」,这句着实是一手好营销,易记,易上口,印象深刻. 万物初生时,一个null对象,凭空而生,接着O ...

  7. 分布式的Id生成器

    项目中需要一个分布式的Id生成器,twitter的Snowflake中这个既简单又高效,网上找的Java版本 package com.cqfc.id; import org.slf4j.Logger; ...

  8. ajax跨域解决方案2

    配置文件添加: <system.webServer>       <httpProtocol>        <customHeaders>          &l ...

  9. bzoj4237 稻草人

    我是萌萌的传送门 题意不难理解吧-- 一开始看到这道题的时候lrd告诉我这题要分治,还给我讲了讲分治要怎么写,好像是CDQ+树状数组来着--(好吧我已经忘了--)然而我第一眼看完题之后的思路是数据结构 ...

  10. python基础七

    subprocess subprocess是专门用来替代os.system;os.spawn更加的先进. 但是subprocess.run()是在python3.5之后才出现的 实例 >> ...