Java多线程中的锁机制
一、锁机制
为什么要使用锁?
多线程为了保证线程安全,让多个线程执行的情况和单线程一样,在读的时候是不影响线程安全的,但如果对数据操作,增加或者删除,几个线程同时进行,就会发生删多或者增加多的情况,这时候为了保证线程安全,就可以加锁。
二、锁的种类
一、重量级锁
在1.6版本之前,加锁的操作是涉及到操作系统进行互斥操作,就是会把当前线程挂起,然后操作系统进行互斥操作修改,由mutexLock来完成,之后才唤醒。操作系统来判断线程是否加锁,所以它是一个重量级操作。挂起、唤醒这两个操作进行了两次上下文切换,消耗CPU,降低性能。其实在这个版本之前已经考虑到CAS操作,但是默认是没有开启的。
当然,除了操作系统维护锁的状态使当前线程挂起外,只要是synchronized,一有竞争也会引起阻塞,阻塞和唤醒操作又涉及到了上下文操作,大量消耗CPU,降低性能。
总之,重量级锁是需要依靠操作系统来实现互斥锁的,这导致大量上下文切换,消耗大量CPU,影响性能。
既是思想,也是对老版本sycvhronized的称呼。
如何使用
以index++这个非原子性操作为例,启动十个线程,每个线程++一万次,打印最终结果
public class Main {
private static CountDownLatch latch = new CountDownLatch(10);//count = 10
private static int index = 0;
private static final Object monitor = new Object();
public static void increase(){
synchronized (monitor){
index++;//非原子性操作
}
}
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
latch.countDown(); //-1
}
}.start();
}
try {
latch.await(); //等count减为0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index);// 主线程打印
}
}
二、重入锁
重入锁即是思想,也是ReentrantLock的翻译。
jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
ReentrantLock(类)与synchronized(关键字)的区别:
- 性能上:lock性能更好(大部分情况下,1.8版本),但后来jvm对sync性能进行了优化;在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized
- 使用上:lock只能在代码中且需要手动释放,并且代码要写入try finally代码块,sync可以在函数和代码块中,lock提供了更加丰富的api
- 功能上:lock可以实现公平锁和非公平锁,sync只是非公平锁,lock提供condition类,可以分组notify(精准唤醒解决方案)
- 原理上:lock是基于aqs队列,sync是在JVM层面实现
- 异常上:发生异常时,sync会自动释放线程占有的锁,因此不会导致死锁现象发生,lock需要手动unlock
如何使用:
lock(); //获取锁
unlock(); //释放锁
trylock(); //尝试获取锁,获取到了返回true,获取不到返回false; 可轮询
trylock(long time ,TimeUnit unit); // 在一定时间内获取锁,获取到了返回true,获取不到返回false;可定时的
lockInterruptibly(); //获取锁,但是可以被中断 可响应中断
//具体意思就是这个方法和lock是一样的,
//都是获取锁的方法,但是它可以被interrupt中断,中断了获取锁的状态,
//就好比直接将当前线程抛弃。
Condition newCondition(); //生成一个Condition,可以对锁对象的进行等待,唤醒等操作,也就是用于线程通信
trylock(long time,TimeUnit unit)代码:
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockTest {
private Lock lock =new ReentrantLock();
public static void main(String[] args) {
final TryLockTest test= new TryLockTest();
//t0 先拿到锁
new Thread() {
public void run(){
test.insert(Thread.currentThread());//Thread.currentThread() 获取当前正在运行的线程
}
}.start(); //thread 0
Thread t1 = new Thread(){
public void run() {
test.insert(Thread.currentThread());
}
};
try {
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
t1.start();
public void insert(Thread thread) {
try{
if(lock.tryLock(7,TimeUnit.SECONDS)){//尝试加锁,如果获取成功。如果获取失败则等待7秒
try{
System.out.println(thread.getName()+"得到了锁");
TimeUnit.SECONDS.sleep(5);
}catch(Exception e){
// TODO: handle exception
} finally{
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
}else{
System.out.println(thread.getName()+"获取锁失败");
}
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
设置等待时间为7秒,睡眠时间5秒,结果为:
设置等待时间为4秒,睡眠时间为5秒,结果为:
lockInterruptibly代码:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread extends Thread {
private lockInterruptibly test = null;
public MyThread(lockInterruptibly test){
this.test= test;
}
@Override
public void run(){
try{
test.insert(Thread.currentThread());
}catch(InterruptedException e){
System.out.println(Thread.currentThread().getName()+"被中断");
}
}
}
public class lockInterruptibly{
private Lock lock = new ReentrantLock();
public static void main(String[] args){
lockInterruptibly test=new lockInterruptibly();
MyThread thread0=new MyThread(test);
MyThread thread1 = new MyThread(test);
thread0.start();
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
thread1.start();
try{
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedException e){
e.printStackTrace();
}
thread1.interrupt();
}
public void insert(Thread thread) throws InterruptedException{
lock.lockInterruptibly();//t1 阻塞再这里
try{
System.out.println(thread.getName()+"得到了锁");
long startTime=System.currentTimeMillis();//11:24:33
while(true){
if(System.currentTimeMillis()-startTime>=Integer.MAX_VALUE){
//1 2 3
break;
}
}
}finally{
lock.unlock();
System.out.println(thread.getName()+"释放了锁");
}
}
}
二、重入锁与不可重入锁
所谓不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。重入锁恰好与之相反,在方法中尝试再次获取锁时,不会被阻塞。Reentrantlock和synchronized都是可重入锁。
代码证明:Reentrantlock是重入锁
import java.util.concurrent.locks.ReentrantLock;
public class ReLockDemo implements Runnable {
ReentrantLock rlock = new ReentrantLock();
Object o = new Object();
public static void main(String[] args) {
ReLockDemo lockDemo = new ReLockDemo();
lockDemo.nonReLock();
}
@Override
public void run() {
System.out.println("ReentrantLock演示:");
}
public void nonReLock() {
synchronized (o) {
System.out.println("第一次获取锁!(外部锁)");
get();
}
System.out.println("内部锁解锁!");
}
public void get() {
synchronized (o) {
System.out.println("第二次获取锁!(内部锁)");
}
System.out.println("外部锁解锁!");
}
public void reLock() {
rlock.lock();
try {
System.out.println("第一次获取锁!(外部锁)");
rlock.lock();//如果不是重入锁就会阻塞在这
try {
System.out.println("第二次获取锁!(内部锁)");
} finally {
System.out.println("内部锁解锁!");
rlock.unlock();
}
} finally {
rlock.unlock();
System.out.println("外部锁解锁!");
}
}
}
运行结果:
三、公平锁和非公平锁
公平锁:新进程发出请求,如果此时一个线程正持有锁,或有其他线程正在等待队列中等待这个锁,那么新的线程将被放入到队列中被挂起。相当于一堆嗜睡的低血糖病人排队看医生,进入的病人门一关,外面的人便排队候着打瞌睡,轮到他时再醒醒进去。
非公平锁:新进程发出请求,如果此时一个线程正持有锁,新的线程将被放入到队列中被挂起,但如果发出请求的的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获取锁。相当于排队看医生,进去的病人门一关,外面的人便排着队打瞌睡,这时新人来了,碰巧门一开,外面的人还没完全醒来,他就乘机冲了进去。
代码实现公平锁非公平锁:
import java.util.concurrent.locks.ReentrantLock;
public class FairTest implements Runnable {
ReentrantLock reentrantLock;
public FairTest(ReentrantLock reentrantLock) {
this.reentrantLock=reentrantLock;
}
@Override
public void run(){
while(true){
reentrantLock.lock();
try{
System.out.println(Thread.currentThread().getName());
}finally{
reentrantLock.unlock();
}
}
}
public static void main(String[] args){
ReentrantLock reentrantLock=new ReentrantLock(false);//切换公平与非公平
FairTest fairTest=new FairTest(reentrantLock);
for(int i=0;i<2;i++){
Thread thread=new Thread(fairTest,"线程"+i);
thread.start();
}
}
}
true为公平锁,运行结果为:
false非公平锁,运行结果为:
生产者消费者模型:
使用ReentrantLock实现精准唤醒。
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者
*/
public class ProducerDemo extends Thread {
private LinkedList<Integer> C;
private ReentrantLock lock;
private Condition proCondition;
private Condition cumCondition;
private Random random = new Random();
public ProducerDemo(LinkedList<Integer> c, ReentrantLock lock, Condition proCondition, Condition cumCondition) {
C = c;
this.lock = lock;
this.proCondition = proCondition;
this.cumCondition = cumCondition;
}
@Override
public void run() {
while (true) {
//生产者线程 阻塞
lock.lock();
try {
//判断仓库是否满了,满了则阻塞
while (C.size() == 5) {
try {
System.out.println("仓库已满,生产者线程阻塞");
proCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int count = random.nextInt(1000);
System.out.println("生产者线程往仓库放数据:" + count);
try {
Thread.sleep(count);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将生产数据放入仓库
C.addLast(count);
//通知消费者
cumCondition.signalAll();
} finally {
lock.unlock();
}
}
}
}
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 消费者
*/
public class ConsumerDemo extends Thread {
private LinkedList<Integer> C;
private Random random = new Random();
private ReentrantLock lock;
private Condition proCondition;
private Condition cumCondition;
public ConsumerDemo(LinkedList<Integer> c, ReentrantLock lock, Condition proCondition, Condition cumCondition) {
C = c;
this.lock = lock;
this.proCondition = proCondition;
this.cumCondition = cumCondition;
}
@Override
public void run() {
while (true) {
lock.lock();
//判断仓库是否为空,空时则阻塞
try {
while (C.size() == 0) {
System.out.println("仓库已空,消费者线程则塞");
try {
cumCondition.await(); //消费者线程 阻塞在这里
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从仓库中获取数据
Integer count = C.removeFirst();
System.out.println("消费者线程从仓库消费:" + count);
//通知生产者线程生产数据
//C.notifyAll();
proCondition.signalAll();
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
}
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
LinkedList<Integer> C = new LinkedList<>();
ReentrantLock lock = new ReentrantLock();
Condition proCondition = lock.newCondition();
Condition cumCondition = lock.newCondition();
for (int i = 0; i < 3; i++) {
ProducerDemo producer = new ProducerDemo(C, lock, proCondition, cumCondition);
ConsumerDemo consumer = new ConsumerDemo(C, lock, proCondition, cumCondition);
producer.start();
consumer.start();
}
}
}