lock锁和Synchronized锁

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种

(一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞):

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;  
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Ticket {
private int number = 0;
public synchronized void sale(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+(number++));
}

}
}
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
new Thread(()->{
ticket.sale();
},"aa").start();
new Thread(()->{
ticket.sale();
},"bb").start();
new Thread(()->{
ticket.sale();
},"cc").start();
}
结果:
aa:0
aa:1
aa:2
aa:3
aa:4
bb:5
bb:6
bb:7
bb:8
bb:9
cc:10
cc:11
cc:12
cc:13
cc:14

Lock

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

  1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

  2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

synchronized 的局限性 与 Lock 的优点 

  如果一个代码块被synchronized关键字修饰,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待直至占有锁的线程释放锁。事实上,占有锁的线程释放锁一般会是以下三种情况之一:

  1:占有锁的线程执行完了该代码块,然后释放对锁的占有;

  2:占有锁线程执行发生异常,此时JVM会让线程自动释放锁;

  3:占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。

  试考虑以下三种情况: 

Case 1 :

  在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待,别无他法。这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit)) 或者 能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。

Case 2 :

  我们知道,当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。

Case 3 :

  我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。

上面提到的三种情形,我们都可以通过Lock来解决,但 synchronized 关键字却无能为力。事实上,Lock 是 java.util.concurrent.locks包 下的接口,Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。也就是说,Lock提供了比synchronized更多的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Ticket {
private ReentrantLock lock = new ReentrantLock();
private int number = 0;
public void sale(){
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+(number++));
}
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
new Thread(()->{
ticket.sale();
},"aa").start();
new Thread(()->{
ticket.sale();
},"bb").start();
new Thread(()->{
ticket.sale();
},"cc").start();
}
结果:
aa:0
aa:1
aa:2
aa:3
aa:4
cc:5
cc:6
cc:7
cc:8
cc:9
bb:10
bb:11
bb:12
bb:13
bb:14

lock没有主动用unlock()去释放锁,会造成死锁现象,因此用lock需要在finally块中释放锁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取锁  
void lock()

// 如果当前线程未被中断,则获取锁,可以响应中断
void lockInterruptibly()

// 返回绑定到此 Lock 实例的新 Condition 实例
Condition newCondition()

// 仅在调用时锁为空闲状态才获取该锁,可以响应中断
boolean tryLock()

// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
boolean tryLock(long time, TimeUnit unit)

// 释放锁
void unlock()