This is a hidden message
锁是为了避免多个线程并发访问同一个资源时出现异常情况,也就是在并发控制中满足互斥的要求;
而在JAVA中,提供了两种锁机制来限制线程的并发访问,一种是由JVM实现的synchronized
关键字,而另一种则是由JDK实现的Lock
接口。
几种常见的锁
乐观锁与悲观锁
乐观锁:乐观锁总是会假设最好的情况,每次去获取数据的时候都认为其他线程不会修改数据,所以不会上锁,只是在更新的时候会判断在此期间其他线程有没有更新这个数据;
悲观锁:悲观锁总是会假设最坏的情况,每次去获取数据的时候都认为其他线程会修改数据,所以会上锁,直到这个线程完成操作;
公平锁与非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁;
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁;
可重入锁
可重入锁,也叫做递归锁,是指在一个线程中可以多次获取同一把锁。
synchronized
关键字
synchronized
关键字提供了一种简单的互斥锁,被synchronized修饰的方法或者代码块会同步执行,保证同一时刻只有一个线程会执行某个同步方法或同步代码块;
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
| public class DemoTest extends Thread { static int count = 0;
public synchronized void increase() throws InterruptedException { sleep(1000); count++; System.out.println(Thread.currentThread().getName() + ": " + count); } @Override public void run() { try { increase(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { DemoTest test = new DemoTest(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("threadOne"); t2.setName("threadTwo"); t1. start(); t2. start(); } }
|
TIPS:这里需要注意,如果synchronized
修饰的是普通方法,则锁的是对象,如果在非单例模式下,调用不同对象的同步方法,会导致锁失效。
修饰静态方法:锁的时方法对应的class对象,进入同步方法时需要获取class对象的锁;
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
| public class DemoTest extends Thread { static int count = 0;
public static synchronized void increase() throws InterruptedException { sleep(1000); count++; System.out.println(Thread.currentThread().getName() + ": " + count); } @Override public void run() { try { DemoTest.increase(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { DemoTest test = new DemoTest(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("threadOne"); t2.setName("threadTwo"); t1. start(); t2. start(); } }
|
修饰代码块:锁的是给定的对象;
1 2 3 4 5 6 7 8 9 10 11 12 13
|
static final Object objectLock = new Object(); public static void increase() throws InterruptedException { System.out.println(Thread.currentThread().getName() + "获取到锁,其他线程在我执行完毕之前,不可进入。" ); synchronized (objectLock) { sleep(1000); count++; System.out.println(Thread.currentThread().getName() + ": " + count); } }
|
Lock
接口
相较于synchronized
关键字,Lock
接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand
和锁重排算法)中使用这些规则;主要的实现是 ReentrantLock
。
Lock接口的方法:
1 2 3 4 5 6 7 8
| public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
|
方法介绍:
lock()
:获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态;
lockInterruptibly()
:如果当前线程未被中断,则获取锁;
tryLock()
:仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true
。如果锁不可用,则此方法将立即返回值 false
;
tryLock(long time, TimeUnit unit)
:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁;
unlock()
:释放锁。在等待条件前,锁必须由当前线程保持。调用 Condition.await ()
将在等待前以原子方式释放锁,并在等待返回前重新获取锁;
newCondition()
:返回绑定到此 Lock
实例的新 Condition
实例。
使用方式:
1 2 3 4 5 6 7 8 9
| Lock lock = new ReentrantLock(); lock.lock(); try { } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }
|
死锁
如果两个线程互相申请对方所持有的资源,而进入等待状态,由于线程被无限期的阻塞,所以程序无法正常终止,这就是死锁:

死锁的四个必要条件:
- 互斥条件:任意资源在同一时刻只能由一个线程访问;
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。