【多线程】Java多线程中的锁机制

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(关键字)的区别:

  1. 性能上:lock性能更好(大部分情况下,1.8版本),但后来jvm对sync性能进行了优化;在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized
  2. 使用上:lock只能在代码中且需要手动释放,并且代码要写入try finally代码块,sync可以在函数和代码块中,lock提供了更加丰富的api
  3. 功能上:lock可以实现公平锁和非公平锁,sync只是非公平锁,lock提供condition类,可以分组notify(精准唤醒解决方案)
  4. 原理上:lock是基于aqs队列,sync是在JVM层面实现
  5. 异常上:发生异常时,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();
        }
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值