JAVA中有关锁的详解

 Java锁使用

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而JavaSE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

Lock 锁的基本用法, l.lock()方法进行上锁, l.unlock()方法进行解锁,如下所示。 

Lock lock = new ReentrantLock();  
lock.lock();  
try {  
    // ..
} finally {
    lock.unlock();
}

 Lock相较于Synchronized优势如下:

  • 可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。
  • 可非阻塞获取锁:使用synchronized关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。
  • 可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。
  • 同一个所对象上可以有多个等待队列(Conditin,类似于Object.wait(),支持公平锁模式)。

下面是有关lock的api 

两个锁的性能比较:

package Text;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReadWriteLockExample {
    private static int synValue;

    public static void main(String[] args){
        int threadNum =0;
        int maxValue =100000;
        testSyns(threadNum,maxValue);
        testLock(threadNum,maxValue);
    }

    private static void testLock(int threadNum, int maxValue) {
        int[] lock = new int[0];
        Long begin = System.nanoTime();
        Thread[] t = new Thread[threadNum];
        for (int i = 0; i < threadNum; i++) {
            synValue = 0;
            t[i] = new Thread(() -> {
                for (int j = 0; j < maxValue; j++) {
                    synchronized (lock) {
                        ++synValue;
                    }
                }
            });
        }
        for (int i = 0; i < threadNum; i++) {
            t[i].start();
        }
        //main线程等待前面开启的所有线程结束
        for (int i = 0; i < threadNum; i++) {
            try {
                t[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("使用synchronized所花费的时间为:" + (System.nanoTime() - begin));
    
    }

    private static void testSyns(int threadNum, int maxValue) {
        Thread[] t =new Thread[threadNum];
        Long begin =System.nanoTime();
        for(int i =0;i<threadNum;i++){
            Lock locks =new ReentrantLock();
            synValue=0;
            t[i]=new Thread(()->{
                for(int j =0;j<maxValue;j++){
                    locks.lock();;
                    try{
                        synValue++;
                    }finally {
                        locks.unlock();
                    }
                }
            });
        }
        for(int i =0;i<threadNum;i++){
            t[i].start();
        }
        for (int i =0;i<threadNum;i++){
            try {
                t[i].join();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));
        }

    }


测试结果的差异还是比较明显的,Lock的性能明显高于synchronized。本次测试基于JDK1.8。

使用lock所花费的时间为:436667997
使用synchronized所花费的时间为:616882878

JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。

到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

公平锁和非公平锁

公平锁:先来先到
非公平锁:不是按照顺序,可插队

  • 公平锁:效率相对低
  • 非公平锁:效率高,但是线程容易饿死

通过这个函数Lock lock = new ReentrantLock(true);。创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁

通过查看源码
带有参数的ReentrantLock(true)为公平锁
ReentrantLock(false)为非公平锁
主要是调用NonfairSync()与FairSync()

public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

通过代码具体操作: 

//第一步  创建资源类,定义属性和和操作方法
class LTicket {
    //票数量
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock(true);
    //卖票方法
    public void sale() {
        //上锁
        lock.lock();
        try {
            //判断是否有票
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class LSaleTicket {
    //第二步 创建多个线程,调用资源类的操作方法
    //创建三个线程
    public static void main(String[] args) {

        LTicket ticket = new LTicket();

new Thread(()-> {
    for (int i = 0; i < 40; i++) {
        ticket.sale();
    }
},"AA").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"BB").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}
AA :卖出:30 剩下:29
AA :卖出:29 剩下:28
AA :卖出:28 剩下:27
AA :卖出:27 剩下:26
AA: 卖出:26 剩下:25
AA :卖出:25 剩下:24

 都是A线程执行,而BC线程都没执行到,出现了非公平锁
具体改变其设置可以通过可重入锁中的一个有参构造方法

修改代码为private final ReentrantLock lock = new ReentrantLock(true);

lock() 方法,unlock()方法

创建一个 Lock 锁:

Lock l = new ReentrantLock();

给代码块上锁:

        l.lock();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": ");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        l.unlock();

全部代码: 

package lock;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class MyLockStudy implements Runnable {
 
    private int count;
    Lock l = new ReentrantLock();
 
    @Override
    public void run() {
        l.lock();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": ");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        l.unlock();
    }
 
    public static void main(String args[]) {
        MyLockStudy runn = new MyLockStudy();
        Thread thread1 = new Thread(runn, "thread1");
        Thread thread2 = new Thread(runn, "thread2");
        Thread thread3 = new Thread(runn, "thread3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

ReentrantLock

 `ReentrantLock` 是 Java 中 `java.util.concurrent.locks` 包下的一个类,它提供了一种比 `synchronized` 更灵活的线程同步控制方式。`ReentrantLock` 允许更细粒度的锁操作,例如尝试非阻塞获取锁、可中断的锁获取、超时获取锁以及公平性(即按照请求锁的顺序来获取锁)。 

重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。避免死锁问题的,synchronized也可重入。

synchronized重入测试


public class ReentrantDemo {
    public synchronized  void method1() {
        System.out.println("synchronized method1");
        method2();
    }
    public synchronized void method2() {
        System.out.println("synchronized method2");
    }
    public static void main(String[] args) {
        ReentrantDemo reentrantDemo = new ReentrantDemo();
        reentrantDemo.method1();
    }

 运行结果:

synchronized method1
synchronized method2
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int sharedResource = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " has acquired the lock.");
            sharedResource++; // 临界区代码
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public int getSharedResource() {
        return sharedResource;
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        // 创建并启动线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final value of shared resource: " + example.getSharedResource());
    }
}

 在这个示例中,我们创建了一个 ReentrantLockExample 类,它有一个 increment 方法,该方法通过 lock 来同步对 sharedResource 的访问。我们创建了两个线程,它们都尝试增加 sharedResource 的值。由于使用了 ReentrantLock,即使两个线程同时运行,sharedResource 的最终值也将是 2000。

尝试非阻塞获取锁

boolean isLockAcquired = lock.tryLock();
if (isLockAcquired) {
    try {
        // 执行临界区代码
    } finally {
        lock.unlock(); // 确保释放锁
    }
} else {
    // 锁不可用,执行其他操作
}


 可中断的锁获取

java
try {
    if (lock.tryLock(10, TimeUnit.SECONDS)) {
        try {
            // 执行临界区代码
        } finally {
            lock.unlock();
        }
    } else {
        // 锁获取超时,执行其他操作
    }
} catch (InterruptedException e) {
    // 线程在获取锁的过程中被中断
    Thread.currentThread().interrupt(); // 重新设置中断状态
}

ReentrantLock重入测试 

public class ReentrantDemo implements Runnable {
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        set();
    }
    public void set() {
        try {
            lock.lock();
            System.out.println("set 方法");
            get();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 必须在finally中释放
        }
    }
 
    public void get() {
 
        try {
            lock.lock();
            System.out.println("get 方法");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        ReentrantDemo reentrantDemo = new ReentrantDemo();
        new Thread(reentrantDemo).start();
    }
}

测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功。

测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功

在这里插入图片描述

ReentrantReadWriteLock(读写锁) 

读写锁是一种同步机制,用于控制对共享资源的并发访问。读写锁允许多个线程同时读取资源,但是在写入的时候则需要独占访问,这种锁非常适合读多写少的场景,因为它可以提高并发性能。

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    private int sharedResource = 0;

    public void readData() {
        readLock.lock();
        try {
            // 读取共享资源
            System.out.println("Reading data: " + sharedResource);
        } finally {
            readLock.unlock();
        }
    }

    public void writeData(int data) {
        writeLock.lock();
        try {
            // 修改共享资源
            sharedResource = data;
            System.out.println("Writing data: " + sharedResource);
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        for (int i =0;i<5;i++){
            int finalI =i;
            new Thread(()->{
                example.readData();
            }).start();
        }
        //创建一个写入数据的线程
        new Thread(()->{
            example.writeData(10);
        }).start();
        //稍作延迟,以确保写入线程有足够多的时间执行
        try{
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再次创建多个读取数据的线程,以查看写入操作后的数据
        for(int i =0;i<5;i++){
            int finalI =i;
            new Thread(()->{
                example.readData();
            }).start();
        }
    }
}
Reading data: 0
Reading data: 0
Reading data: 0
Reading data: 0
Reading data: 0
Writing data: 10
Reading data: 10
Reading data: 10
Reading data: 10
Reading data: 10
Reading data: 10

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值