不知道其他人是不是有这样的想法,对于我个人而言感觉锁挺难的,只要在面试中问到锁的相关知识,心里就会咯噔一下,在度娘搜了好多零零散散的知识,还是得记录一下,省的自己以后忘记了。
首先来说说有锁的种类,下面会有用到这些。
锁的种类
锁的种类真的很多,这里就只列出我认为用的最多的有那几种:
- 公平和非公平
- 重入和不可重入
- 互斥和读写
- 悲观和乐观
- 重量和轻量
- 独享和共享
- 分段
- 。。。
接着就来说说这几种锁的各自含义:
- 公平和非公平、重入和不可重入、独享和共享这几个都是可以根据字面意思来了解他们的各自含义,比如
- 公平和非公平就是决定是否根据线程的先后到达顺序来获取资源;
- 重入和不可重入就是线程获取到锁的情况下能否再次获取锁(也就是防止线程获取锁之后由于一些操作而退出处理,原先的资源也没有处理完,加入是不可重入的则会导致该线程不能再进去)
- 独享和共享就是该锁能否被其他资源所获取
- 互斥和读写:所谓互斥就是一个资源只能存在一个访问者,其他的都得等待,读写锁可以支持多个同时读取,但是写只能存在一个也就是互斥,但是不能读写同时进行。
- 乐观锁和悲观锁:悲观锁及拿数据就加锁,不管你需不需要更改,而乐观锁只有在要更改数据的时候会去判断在此期间有无人更改了数据。
为什么要用锁?或用锁来解决什么问题
锁其实就是用来保证资源的安全性,即便是多线程情况下,一次也只会有一个线程处理(想想如果没有锁,一个线程在处理该数据,还没处理完,另一个进程进来更改了数据,原先线程处理完数据又改了数据,结果中途进来的数据白改数据了)。
怎么实现加锁
这就引进了两个关键字Lock和synchronized 。
先看以下代码实现:
public class LockReview implements Runnable {
private Lock lock = new ReentrantLock();
private CountDownLatch countDownLatch;
public LockReview(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void doing(String name) {
System.out.println("initial:" + name);
lock.lock();
try {
System.out.println("线程名字:" + name);
} finally {
lock.unlock();
}
}
@Override
public void run() {
try {
countDownLatch.await();
doing(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test {
public void doTest() throws InterruptedException {
final int N = 5; // 线程数
CountDownLatch countDownLatch = new CountDownLatch(N);
for (int i = 0; i < N; i++) {
new Thread(new LockReview(countDownLatch)).start();
countDownLatch.countDown();
}
}
public static void main(String[] args) throws Exception {
Test t1 = new Test();
t1.doTest();
}
}
以ReenTrantLock 为例,用CountDownLatch.await来模拟多线程,一共是五个线程。
看来看一下结果
再来看一下把锁注释掉的结果
结果发现顺序开始错乱。
下面再来看看synchronized的实现
public class SynReview implements Runnable {
private CountDownLatch countDownLatch;
public SynReview(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public synchronized void doing(String name) {
System.out.println("initial:" + name);
System.out.println("线程名字:" + name);
}
@Override
public void run() {
try {
countDownLatch.await();
doing(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
同样来看一下注释掉synchronized 的结果:
synchronized的使用方式
除了上述的将关键字放在方法上,也可以放在方法里来修饰代码块
public void doing(String name) {
synchronized (SynReview.class) {
System.out.println("initial:" + name);
System.out.println("线程名字:" + name);
}
}
ReenTrantLock 的使用方式
- 使用 lock()方法之后必须手动 调用unlock()。
- ReentrantLock lock = new ReentrantLock(true);可以实现公平锁
- Condition condition = lock.newCondition(); 一个锁可以实现多个condition,从而实现选择性通知。
ReenTrantLock 和 Synchronized 的异同
- 两个都是可重入锁
- ReenTrantLock 是基于JDK也就是API来实现的,而synchronize是通过JVM来实现的
- ReenTrantLock 比synchronized 多了几种功能
- ReenTrantLock 实现了等待锁可中断的机制,也就是说在不想等的情况下可以终止不等,而去处理其他的事务。可以用过lock.lockInterruptibly()来实现。
- 可以实现公平锁,也就是先来先得,而synchronize都是不公平的。
- ReentrantLock可以实现选择性通知,synchronize都是配合wait/notify、notifyAll来使用,而ReenTrantLock通过Condition接口来实现,一个lock对象可以由多个condition实例,每个condition都有注册自己的线程对象,通过该实例的signalAll来唤醒该Condition中的所有对象,从而实现选择性通知,synchronize则相当于只有一个condition,所有的线程都注册在该condition中,所以实现不了差别通知。