千家信息网

java中各类锁的机制是什么

发表于:2025-02-23 作者:千家信息网编辑
千家信息网最后更新 2025年02月23日,这篇文章给大家分享的是有关java中各类锁的机制是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言总结java常见的锁区分各个锁机制以及如何使用使用方法锁名考察线程是
千家信息网最后更新 2025年02月23日java中各类锁的机制是什么

这篇文章给大家分享的是有关java中各类锁的机制是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

前言

总结java常见的锁

区分各个锁机制以及如何使用

使用方法锁名
考察线程是否要锁住同步资源乐观锁和悲观锁
锁住同步资源后,要不要阻塞不阻塞可以使用自旋锁
一个线程多个流程获取同一把锁可重入锁
多个线程公用一把锁读写锁(写的共享锁)
多个线程竞争要不要排队公平锁与非公平锁

1. 乐观锁与悲观锁

悲观锁:不能同时进行多人,执行的时候先上锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁

乐观锁:通过版本号一致与否,即给数据加上版本,同步更新数据以及加上版本号。不会上锁,判断版本号,可以多人操作,类似生活中的抢票。每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的

(乐观锁可以使用版本号机制和CAS算法实现)

通过具体案例演示悲观锁和乐观锁

在redis框架中

执行multi之前,执行命令watch

具体格式如下

watch key1 [key2]

具体代码格式如下

127.0.0.1:6379> flushdbOK127.0.0.1:6379> set add 100OK127.0.0.1:6379> watch addOK127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> incrby add 20QUEUED127.0.0.1:6379(TX)> exec1) (integer) 120127.0.0.1:6379>

flushdb是清空数据库

但如果在另一个服务器上,输入exec,会显示出错

因为用的是乐观锁,被修改了之后版本会发生改变

总的来说:

悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低

乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号

2. 公平锁与非公平锁

公平锁:先来先到

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

  • 公平锁:效率相对低

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

通过这个函数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();    }

具体其非公平锁与公平锁的源码

查看公平锁的源码

static final class FairSync extends Sync {   private static final long serialVersionUID = -3000897897090466540L;  /**  * Acquires only if reentrant or queue is empty.   */  final boolean initialTryLock() {   Thread current = Thread.currentThread();   int c = getState();   if (c == 0) {   if (!hasQueuedThreads() && compareAndSetState(0, 1)) {     setExclusiveOwnerThread(current);      return true;    }    } else if (getExclusiveOwnerThread() == current) {      if (++c < 0) // overflow          throw new Error("Maximum lock count exceeded");         setState(c);         return true;       }    return false;}

通过代码实例具体操作

//第一步  创建资源类,定义属性和和操作方法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();    }}

结果截图如下

都是A线程执行,而BC线程都没执行到,出现了非公平锁

具体改变其设置可以通过可重入锁中的一个有参构造方法

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

代码截图为

3. 可重入锁

可重入锁也叫递归锁

而且有了可重入锁之后,破解第一把之后就可以一直进入到内层结构

Object o = new Object();new Thread(()->{    synchronized(o) {        System.out.println(Thread.currentThread().getName()+" 外层");        synchronized (o) {            System.out.println(Thread.currentThread().getName()+" 中层");            synchronized (o) {                System.out.println(Thread.currentThread().getName()+" 内层");            }        }    }},"t1").start();

synchronized (o)代表锁住当前{ }内的代码块

以上都是synchronized锁机制

下面讲解lock锁机制

public class SyncLockDemo {    public synchronized void add() {        add();    }    public static void main(String[] args) {        //Lock演示可重入锁        Lock lock = new ReentrantLock();        //创建线程        new Thread(()->{            try {                //上锁                lock.lock();                System.out.println(Thread.currentThread().getName()+" 外层");                try {                    //上锁                    lock.lock();                    System.out.println(Thread.currentThread().getName()+" 内层");                }finally {                    //释放锁                    lock.unlock();                }            }finally {                //释放做                lock.unlock();            }        },"t1").start();        //创建新线程        new Thread(()->{            lock.lock();            System.out.println("aaaa");            lock.unlock();        },"aa").start();        } }

在同一把锁中的嵌套锁,内部嵌套锁没解锁还是可以输出,但是如果跳出该线程,执行另外一个线程就会造成死锁

要把握上锁与解锁的概念,都要写上

4. 读写锁(共享锁与独占锁)

读锁是共享锁,写锁是独占锁

  • 共享锁的一种具体实现

  • 读写锁管理一组锁,一个是只读的锁,一个是写锁。

读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享(写锁独占,读锁共享,写锁优先级高于读锁)

读写锁ReentrantReadWriteLock

读锁为ReentrantReadWriteLock.ReadLock,readLock()方法

写锁为ReentrantReadWriteLock.WriteLock,writeLock()方法

创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();

写锁 加锁 rwLock.writeLock().lock();,解锁为rwLock.writeLock().unlock();

读锁 加锁rwLock.readLock().lock();,解锁为rwLock.readLock().unlock();

案例分析:

模拟多线程在map中取数据和读数据

完整代码如下

//资源类class MyCache {    //创建map集合    private volatile Map map = new HashMap<>();    //创建读写锁对象    private ReadWriteLock rwLock = new ReentrantReadWriteLock();    //放数据    public void put(String key,Object value) {        //添加写锁        rwLock.writeLock().lock();        try {            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);            //暂停一会            TimeUnit.MICROSECONDS.sleep(300);            //放数据            map.put(key,value);            System.out.println(Thread.currentThread().getName()+" 写完了"+key);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            //释放写锁            rwLock.writeLock().unlock();        }    }    //取数据    public Object get(String key) {        //添加读锁        rwLock.readLock().lock();        Object result = null;        try {            System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);            //暂停一会            TimeUnit.MICROSECONDS.sleep(300);            result = map.get(key);            System.out.println(Thread.currentThread().getName()+" 取完了"+key);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            //释放读锁            rwLock.readLock().unlock();        }        return result;    }}public class ReadWriteLockDemo {    public static void main(String[] args) throws InterruptedException {        MyCache myCache = new MyCache();        //创建线程放数据        for (int i = 1; i <=5; i++) {            final int num = i;            new Thread(()->{                myCache.put(num+"",num+"");            },String.valueOf(i)).start();        }        TimeUnit.MICROSECONDS.sleep(300);        //创建线程取数据        for (int i = 1; i <=5; i++) {            final int num = i;            new Thread(()->{                myCache.get(num+"");            },String.valueOf(i)).start();        }    }}

5. 互斥锁

互斥锁是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥锁并初始化pthread_mutex_lock(&mutex);//对线程上锁,此时其他线程阻塞等待该线程释放锁//要执行的代码段pthread_mutex_unlock(&mutex);//执行完后释放锁

6. 自旋锁

查看百度百科的解释,具体如下 :

它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名

通俗的来说就是一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。

其特点:

  1. 持有锁时间等待过长,消耗CPU

  2. 无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在"线程饥饿"问题

  3. 自旋锁不会使线程状态发生切换,处于用户态(不会到内核态进行线程的状态转换),一直都是活跃,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。

其模拟算法如下

do{        b=1;        while(b){                lock(bus);                b = test_and_set(&lock);                unlock(bus);        }        //临界区        //lock = 0;        //其余部分}while(1)

7. 无锁 / 偏向锁 / 轻量级锁 / 重量级锁

  • 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功

  • 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价

  • 轻量级锁:锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能

  • 重量级锁:线程并发加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,还有线程要访问

感谢各位的阅读!关于"java中各类锁的机制是什么"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

线程 数据 资源 机制 版本 乐观 代码 方法 时候 悲观 多个 状态 阻塞 同时 同步 内层 就是 效率 源码 轻量 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 如何导入本地数据库文件 CS徐亮服务器 财务软件服务器网络要求 服务器不正常关机硬盘文件丢失 sql 数据库关系是什么 vps与云服务器区别 象山应用软件开发服务 河南网络安全信息化招聘岗位信息 怎么防止服务器被坑 服务器内存不够的表现有哪些 安徽交友软件开发收费报价表 门禁系统平台软件开发 软件开发都有哪些学校 ibmi2 如何访问数据库 数据库第三章实验2 网络安全法由 于2016年 小学会用什么网络安全设备 基层网络安全管理制度及措施 同花顺服务器崩了是什么原因 嵌入式软件开发技能树 戴尔r540服务器报价 破晓服务器 网络安全建设的合规性是什么 电脑管家出现服务器正在运行 天姬变怎么找到以前的服务器 精通网络技术有限公司怎么样 国家网络安全法知识竞赛题库 kv数据库一般用什么软件好 merops数据库使用方法 电脑维护与网络技术
0