目录
1.锁的基本概念
2.Java集成结构图
3.用法
3.1 synchronized
3.1 ReentrantLock(可重入锁)
3.2 ReentrantReadWriteLock(读写锁)
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/package-summary.html
1.锁的基本概念
(1)阻塞和非阻塞:
阻塞指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
(2)同步和异步:
同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成 后,通过状态、通知和回调来通知调用者。
(3)什么是可重入锁 :可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。
(4)什么叫读写锁 :读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。简单的说就是写锁是排他锁,读锁是共享锁。
(5)获取锁涉及到的两个概念即公平和非公平 :公平表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序。而非公平就是一种获取锁的抢占机制,和公平相对就是先来不一定先得,这个方式可能造成某些线程饥饿(一直拿不到锁)
2.Java集成结构图
3.用法
ReentrantLock,ReentrantReadWriteLock,Sychronized用法即作用
3.1 synchronized
Synchronized的作用:使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。(阻塞锁)。
Lock接口:JavaSE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。
3.1 ReentrantLock(可重入锁)
ReenTrantLock实现了Lock接口,
ReenTrantLock和synchronized都是可重入锁,作用基本相同
ReenTrantLock:同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
Synchronized:执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED 状态。
import java.util.concurrent.locks.ReentrantLock;
/**
* @author zpoison
* @data 2018/7/3/003 15:07
*/
public class ReetrantLockTest implements Runnable{
private ReentrantLock lock =new ReentrantLock();
@Override
public void run() {
sayHello();
}
public void sayHello(){
try {
lock.lock();
//查询当前线程对此锁定的保持数。(持有锁为1不只有为0)
System.out.println("前线程对此锁定的保持数(加锁时):"+lock.getHoldCount());
//检查次方法是否持有锁
System.out.println("是否持有锁:"+lock.isHeldByCurrentThread());
System.out.println(Thread.currentThread().getName() + " locking ...");
System.out.println("Hello world!");
System.out.println(Thread.currentThread().getName() + " unlocking ..."+"\n");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
//确保是同一个对象
ReetrantLockTest reetrantLockTest = new ReetrantLockTest();
for ( int i = 0; i < 5; i++) {
Thread thread =new Thread(reetrantLockTest,"线程"+i);
thread.start();
}
}
}
ReenTrantLock独有的能力:
1. ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
2. ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
3. ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
3.2 ReentrantReadWriteLock(读写锁)
ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
ReentrantReadWriteLock特点
1. 锁降级
当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。
public class ReadWriteLockTest implements Runnable {
//读写锁
private ReadWriteLock lock =new ReentrantReadWriteLock();
String test="你好";
int i =0;
@Override
public void run() {
sayHello();
writeHello();
}
public void sayHello(){
//写锁
lock.writeLock().lock();
test="Hello"+i++;
System.out.println("写内容"+test);
System.out.println(test+Thread.currentThread().getName());
lock.writeLock().unlock();
//读锁
lock.readLock().lock();
System.out.println("读开始。。。。");
System.out.println("读取内容"+test);
lock.readLock().unlock();
}
public void writeHello(){
}
public static void main(String[] args) {
ReadWriteLockTest readWriteLockTest =new ReadWriteLockTest();
for ( int i = 0; i < 5; i++) {
Thread thread =new Thread(readWriteLockTest,"线程"+i);
thread.start();
}
}
}
输出结果
2. 通过一个缓存示例说明读写锁的使用方式
public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
// 设置key对应的value,并返回旧的value
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
// 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}
上述示例中,Cache组合一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的 读锁和写锁来保证Cache是线程安全的。在读操作get(Stringkey)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put(String key,Object value)方法和clear()方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。Cache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程方式。