关于锁,首先就了解一下原子操作,原子操作即该操作在执行时是不可打断的整体,那么将不存在所谓的并发问题。但是实际场景中的大部分共享资源的操作往往不是原子性的,那就意味着在并发状况下很可能出现非预期的结果,这时候就需要引入锁这个概念。锁即保证共享资源的正确调用的一种机制,被锁的区域有点像进程中的临界区的概念,但是锁的功能要更加多样化。
锁相关的概念有点多,比如锁开销,锁竞争,死锁,信号量等等,
锁开销:即实现锁所需要占用的资源,实现开锁解锁操作所需消耗的时间等等,一般来说追求尽可能小的锁开销。
锁竞争:多个调用者发生尝试获取已被占用的锁的情形,锁竞争是无法避免的,但是同样降低锁竞争的发生频次是必要的。
信号量:实质上锁的实现就是依赖信号量的,代表的是锁相关的各种状态,锁的复杂度和适用场景的丰富都依赖于更加多的信号量,锁设计的实质感觉就是信号量的设计
死锁:指的是多个调用者调用资源时产生的相互阻塞的情形,若无外力介入,这种情形将无法打破。关于死锁的产生条件是互斥,请求和保持,不可剥夺,环路等待。无疑,死锁是需要避免的情形。
java锁分类:
(1)公平锁和非公平锁
而非公平锁则无需维护等待队列或不进行队列非空验校。很显然非公平锁流程较短,能获得更高的效率更大的吞吐量,但是很可能会无法控制获取锁的优先级,甚至极端情况下会出现某些线程饿死(即线程的操作有效期已经超时)的情况。
(2)可重入锁和不可重入锁,可重入锁就是对于独享锁,相同的对象尝试再次获得已经获得的锁时将刻直接获得锁。
不可重入锁实例:
public class Test3 {
static MyLock lock = new MyLock();
public static void main(String[] args) throws InterruptedException {
lock.lock();
System.out.println("第一次获得锁");
doSome();
lock.unLock();
}
private static void doSome() throws InterruptedException {
lock.lock();
System.out.println("我想再次拿到锁");
lock.unLock();
}
}
class MyLock {
private boolean isLock = false;
synchronized void lock() throws InterruptedException {
while (isLock) {
System.out.println("锁已经被占用");
wait();
}
isLock = true;
}
synchronized void unLock() {
isLock = false;
}
}
结果为
可重入锁实例:
public class Test3 {
static MyLock lock = new MyLock();
public static void main(String[] args) throws InterruptedException {
lock.lock();
System.out.println("第一次获得锁");
doSome();
lock.unLock();
}
private static void doSome() throws InterruptedException {
lock.lock();
System.out.println("再次拿到锁");
lock.unLock();
}
}
class MyLock {
private boolean isLock = false;
private Thread ownThread;
synchronized void lock() throws InterruptedException {
while (Thread.currentThread() != ownThread & isLock) {
System.out.println("锁已经被占用");
wait();
}
ownThread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "获得锁");
isLock = true;
}
synchronized void unLock() {
ownThread = null;
isLock = false;
}
}
结果为
可重入锁依赖于对于锁拥有者的判断,一般来说,稳定的可重入锁还应当有对应的计数器记录重入的次数以维护加锁状态,保证解锁次数必须和加锁次数相同才能正确解锁。
(3)共享锁和独享锁,即锁是否可被多个对象持有
非共享锁实例:
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Some some = new Some();
new MyThread(some).start();
new MyThread(some).start();
}
}
class Some {
public MyLock lock;
public Some() {
this.lock = new MyLock();
}
}
class MyLock {
private boolean isLock = false;
synchronized void lock() throws InterruptedException {
while (isLock) {
System.out.println("锁已经被占用");
wait();
}
System.out.println(Thread.currentThread().getName() + "获得锁");
isLock = true;
}
synchronized void unLock() {
isLock = false;
}
}
class MyThread extends Thread {
private Some some;
public MyThread(Some some) {
this.some = some;
}
public void run() {
try {
some.lock.lock();
System.out.println(Thread.currentThread().currentThread() + "开始干活");
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
some.lock.unLock();
}
}
}
共享锁实例
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Some some = new Some();
new MyThread(some).start();
new MyThread(some).start();
}
}
class Some {
public MyLock lock;
public Some() {
this.lock = new MyLock();
}
}
class MyLock {
private int isLock = 2;
synchronized void lock() throws InterruptedException {
while (isLock == 0) {
System.out.println("锁已经被占用");
wait();
}
System.out.println(Thread.currentThread().getName() + "获得锁");
isLock -= 1;
}
synchronized void unLock() {
isLock += 1;
}
}
class MyThread extends Thread {
private Some some;
public MyThread(Some some) {
this.some = some;
}
public void run() {
try {
some.lock.lock();
System.out.println(Thread.currentThread().currentThread() + "开始干活");
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
some.lock.unLock();
}
}
}
可见共享锁依赖一个锁可拥有者数量的信号量
(4)自旋锁,即等待线程不会进入阻塞状态,而是保持问询状态
自旋锁实例:
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Some some = new Some();
new MyThread(some).start();
new MyThread(some).start();
}
}
class Some {
public MyLock lock;
public Some() {
this.lock = new MyLock();
}
}
class MyLock {
private boolean isLock = false;
synchronized void lock() throws InterruptedException {
while (isLock) {
Thread.currentThread().sleep(200);
System.out.println("锁已经被占用");
}
System.out.println(Thread.currentThread().getName() + "获得锁");
isLock = true;
}
synchronized void unLock() {
isLock = false;
}
}
class MyThread extends Thread {
private Some some;
public MyThread(Some some) {
this.some = some;
}
public void run() {
try {
some.lock.lock();
System.out.println(Thread.currentThread().getName() + "开始干活");
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
some.lock.unLock();
}
}
}
简而言之自旋锁不会让申请者进入休眠状态,前面的实例都是非自旋锁,对于非自旋锁依赖于解锁时唤醒等待线程,而自旋锁这依赖于等待线程的不停申请,显而易见,自旋锁占用的cpu资源比较多,但响应速度很快,对于持有时间较短且锁竞争不激烈的资源可以使用自旋锁
(5)乐观锁和悲观锁,其实两个不是实际意义上的锁,而是并发情况下的一种处理策略,悲观锁就是实现代码中的实际资源加锁来保证稳定性,乐观锁则放弃实际的加锁操作。乐观锁
悲观锁
大致上就是这样,加入一个初始状态的判断以替代加锁操作,乐观锁常应用于数据库操作中,在where判断中加入初始值判断即乐观锁
(6)分段锁,这也是一种设计锁的策略,如果需要锁的资源过大,则将资源分段分别加锁,减小锁的粒度以获得更大的并发量
(7)偏向锁/轻量级锁/重量级锁,是jvm对synchronized的实现的内置锁,不涉及编程的实现。
后面和分段锁与公平锁的实例分别单独写一下。