其实在聊线程的锁(synchronized和lock)现在再补充一些东西,比如可重入锁,公平锁。
可重入锁
其实synchronized(隐式)和Lock(显示)两中锁其实都是可重入。
其实这个听着很神奇,用一个简单的例子来说就是,比如如果一座大楼锁门了,然后在里面的人还是可以随便在公司中打开公司大门的锁的,而公司门锁了里面的人自然可以在房间里面随便打开锁然后进去。说白了这个就是一个”套锁“的现象所以可重入锁又被称为递归锁:
其实这个说白了就是,你锁住了最外面一层,里面所以加锁等操作了。
还是老规矩演示:
public class test {
public static void main(String[] args) {
// 前面聊过锁到底锁什么,所以就随便弄一个锁的对象,没有什么具体意义
Object obj = new Object();
// 这里用lambda表达式,如果不懂可以翻看一下前面我的文章
new Thread(() -> {
synchronized (obj) {
System.out.println("写字楼大门锁");
synchronized (obj) {
System.out.println("公司大门锁");
synchronized (obj) {
System.out.println("房间大门锁");
}
}
}
}).start();
}
}
看一下结果
这个可重入锁使用synchronized还好会自动释放锁,但是使用lock的话会有一个一些要注意的点。不过还是老规矩,用lock在重现上面的代码一次。
public class test {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 这里用lambda表达式,如果不懂可以翻看一下前面我的文章
new Thread(() -> {
lock.lock();
try {
System.out.println("写字楼大门锁");
lock.lock();
try {
System.out.println("公司大门锁");
lock.lock();
try {
System.out.println("房间大门锁");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}).start();
}
}
结果一样,但是如果这样做呢,将其中的一个锁锁了然后忘了关闭,又如何?
如果最里面的一层忘了解锁(无论如何同一个lock在锁了多少次一般要解锁多少次,不然会出现错误)
演示这个:
public class test {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 这里用lambda表达式,如果不懂可以翻看一下前面我的文章
new Thread(() -> {
lock.lock();
try {
System.out.println("写字楼大门锁");
lock.lock();
try {
System.out.println("公司大门锁");
lock.lock();
try {
System.out.println("房间大门锁");
} finally {
// 如果最里面的一层忘了解锁
// lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}).start();
// 再次来一个线程:
new Thread(()->{
lock.lock();
try{
System.out.println("第二个次要进入");
}finally {
lock.unlock();
}
}).start();
}
}
会发现无法运行第二个线程,以及程序一直处于运行状态。所以在使用lock加锁解锁的时候一定要成对出现,不然浪费性能以外还会影响其它程序的运行。
公平锁和非公平锁
公平锁和非公平锁,这个其实很好理解的,其实最重要的词语在于公平,毕竟多线程说白就是抢资源。还是老规矩,用例子来测试。
非公平锁:
- 先用synchronized试一下:
public class test implements Runnable {
static Integer number=30;
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(new test(),"AA").start();
}
for (int i = 0; i < 30; i++) {
new Thread(new test(),"BB").start();
}
}
@Override
public void run() {
synchronized (number){
if(number>0){
System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
}}
}
}
可以看出一个事情,那就是所有的票都被AA卖完了。其实聊公平和非公平锁与synchronized关系不太大,因为synchronized的锁很多时候程序员很难操作的。
- Lock
现在用lock,可以如下看到:
Lock lock= new ReentrantLock();
通过这个构造方法可以查看:
会发现再创建Lock的时候,会有两个方法,一个公平同步,一个是非公平。不带参数的为非公平锁,所以如下尝试:
public class test implements Runnable {
static Integer number=30;
Lock lock= new ReentrantLock();
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(new test(),"AA").start();
}
for (int i = 0; i < 30; i++) {
new Thread(new test(),"BB").start();
}
}
@Override
public void run() {
lock.lock();
try{
if(number>0){
System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
}}finally {
lock.unlock();
}
}
}
两种方式都可以看出一件事情,那就是所有的票都被AA卖完了。
公平锁
上面说过lock在创建锁的时候可以带有参数,所以如下:
public class test implements Runnable {
static Integer number=30;
Lock lock= new ReentrantLock(true);
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(new test(),"AA").start();
}
for (int i = 0; i < 30; i++) {
new Thread(new test(),"BB").start();
}
}
@Override
public void run() {
lock.lock();
try{
if(number>0){
System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
}}finally {
lock.unlock();
}
}
}
但是公平锁只能说是相对式公平,而不是绝对公平,看结果:
可以看出BB虽然也参与了卖票,但是也不是说与AA线程的平分所有卖票数据。
总结:
- 非公平锁:
其效率会高,毕竟疯狂的抢占资源,但是其有一个问题那就是有些线程会被饿死(也就是运行无效) - 公平锁:
效率相对较低,其运行的时候只是打个招呼说有没有在等,如果人等着运行自己就开始运行,所以公平也不是绝对的公平运行所有数据。