咸鱼的自我修养 - Java中的同步锁(类锁、对象锁、方法锁)

今天,又是自我救赎的一天。。。

顶着副热带高压导致的高温上下班,在公司忙了一星期,也没学到多少东西,还是得靠自己下功夫修炼啊,早日把功夫练到化境,天下公司,大可去得!

在这里插入图片描述

好了好了,吐槽完毕,回归正题。

这次跟大家讨论的是Java中的同步锁。

特别提醒一下,如果有读者不想看太多的文字,可以直接拷贝下面的代码到自己的编译器里,运行一下,差不多就能看懂了。

同步锁

我们先看看同步锁的概念:

同步锁是为了保证每个线程都能正常执行原子不可更改操作,同步监听对象/同步锁/同步监听器/互斥锁的一个标记锁.
每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,当消费者线程试图执行以带有synchronized(this)标记的代码块时,消费者线程必需先获得this关键字引用的Stack对象的锁.

emmmm,说人话!!!

其实同步锁就是为了解决多线程环境下的数据共享、同步的线程安全问题的。

假设有下面的一个场景:

某一天,你(线程共享对象)正在苦逼的工作,然后你的两个领导(线程)同时来找你,让你帮他们干活。

在这里插入图片描述
那么,你要怎么办呢?先去项目经理那干一半,再跑到技术总监那干一半,然后就这样来回跑着把活干完?

不合适。

为此,项目经理和技术总监开始互相撕逼(卧槽,你的身价可以啊,能让两个Leader为你做到这一步),

接下来好戏开始了

最后项目经理赢了(拿到同步锁),

大笑着把你带到他的办公室,开始干活,等你帮项目经理干完活(线程执行完毕),

项目经理笑眯眯的对技术总监说:“现在轮到你了”(释放同步锁),

你又去技术总监的办公室接着干活(另一个线程拿到了同步锁),

终于,你把所有的活都干完了,技术总监说,小王啊,你去忙你的吧(没事赶紧爬。。。释放了同步锁)

你很无奈的回到自己的工位上,继续喝着咖啡加着班(CPU空闲)。

这充满着血与泪的小故事,应该能帮你理解同步锁的作用。

同步锁的作用大概了解了,下面谈谈同步锁的分类

在Java中,同步锁大致分为两类:

  • 对象锁
  • 类锁

那我们常听到的方法锁是什么呢?方法锁在概念上是属于对象锁的。

对象锁

类声明后,我们可以 new 出来很多的实例对象。这时候,每个实例对象在 JVM 中都有自己的引用地址和堆内存空间,这时候,我们就认为这些实例都是独立的个体,很显然,在实例对象上加的锁和其他的实例对象是没有关系的,互不影响。

在Java中,通过synchronized关键字实现对象锁有3种方式:

  1. 非静态变量
  2. 非静态方法
  3. this关键字

还是以上面的场景为例,我用代码实现了这3种方式的对象锁。

环境

先说明一下我的环境,因为用了lambda表达式简化匿名内部类,所以要求JDK是1.8及以上,我用的8u261。

同时,为了方便观察线程执行的过程,最好在输出的结果上面加上时间戳,如果使用常规的System.out.println()打印结果,需要自己显式的加入时间戳,可以通过Java中提供的DateCalendar等日期相关类或者System.currentTimeMillis()实现,但我为了方便,引入了slf4jlombok进行日志打印。

maven坐标如下:

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.18</version>
    <scope>provided</scope>
</dependency>

<!-- slf4j是接口层依赖,logback是slf4j的实现 -->

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

Worker类:苦逼工作的你

package cn.skywalker.test.thread.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/** 
 * @author SkyWalker
 * @date 2020 - 08 - 14 - 9:44
 */
@Slf4j
public class Worker {
    /**
     * 线程睡眠时间,单位为秒
     */
    public static final Integer SLEEP_TIME = 1;

    /**
     * 非静态变量作为锁对象
     */
    public final Object OBJECT_LOCK = new Object();
 
    /**
     * 对象锁之非静态变量
     */
    public void workObjectLock() {
        synchronized (OBJECT_LOCK) {
            try {
                String threadName = Thread.currentThread().getName();
                log.info(threadName + "  --->  ready to work");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + "  --->  working...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + "  --->  work complete");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 对象锁之this
     */
    public void workThisLock() {
        synchronized (this) {
            try {
                String threadName = Thread.currentThread().getName();
                log.info(threadName + "  --->  ready to work");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + "  --->  working...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + "  --->  work complete");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 对象锁之非静态方法
     */
    public synchronized void workSync() {
        try {
            String threadName = Thread.currentThread().getName();
            log.info(threadName + "  --->  ready to work");
            TimeUnit.SECONDS.sleep(SLEEP_TIME);
            log.info(threadName + "  --->  working...");
            TimeUnit.SECONDS.sleep(SLEEP_TIME);
            log.info(threadName + "  --->  work complete");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } 
}

ObjectLock类:测试对象锁的类

package cn.skywalker.test.thread.lock;

/**
 * @author SkyWalker
 * @date 2020 - 08 - 14 - 9:58
 */
public class ObjectLock {
    private static Worker worker = new Worker();

    public static void main(String[] args) {
        workObjectLock();
        //workThisLock();
        //workSync();
    }

    private static void workObjectLock() {
        new Thread(() -> {
            worker.workObjectLock();
        }, "jack").start();

        new Thread(() -> {
            //Worker worker = new Worker();
            worker.workObjectLock();
        }, "rose").start();
    }

    private static void workThisLock() {
        new Thread(() -> {
            worker.workThisLock();
        }, "jack").start();

        new Thread(() -> {
            //Worker worker = new Worker();
            worker.workThisLock();
        }, "rose").start();
    }

    private static void workSync() {
        new Thread(() -> {
            worker.workSync();
        }, "jack").start();

        new Thread(() -> {
            //Worker worker = new Worker();
            worker.workSync();
        }, "rose").start();
    }
}

运行结果

main方法中的三个函数随便调用哪一个,最后的结果都应该是:
在这里插入图片描述

可以看到,两个线程 jack 和 rose 的执行是有序的。

类锁

类锁是加在类上的,而类的相关信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。该类的所有实例对象也共享这个锁,也就意味着这把锁会影响到每一个实例对象。

在Java中,通过synchronized关键字实现类锁也有3种方式:

  1. 静态变量
  2. 静态方法
  3. Class对象

这次我们的场景如下:

今天是发工资的日子,
但是财务部的人不想让办公室那么拥挤和混乱,
规定一次只能进来一个人领工资。

jack 和 rose 同时到了财务部办公室门口,
两人决定石头剪刀布决胜负。
(这里注意,是两个不同的人)

结果jack 赢了(拿到类锁),

进去领了工资,美滋滋出来了(释放类锁)。

然后rose才进去领工资。

代码实现:

Worker类:jack和rose(和我一样可怜的CRUDer)

package cn.skywalker.test.thread.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author SkyWalker
 * @date 2020 - 08 - 14 - 9:44
 */
@Slf4j
public class Worker {
    /**
     * 线程睡眠时间,单位为秒
     */
    public static final Integer SLEEP_TIME = 1;
 
    /**
     * 静态变量作为锁对象
     */
    public static final Object CLASS_LOCK = new Object();
 
    /**
     * 类锁之静态变量
     */
    public static void getSalaryObjectLock() {
        synchronized (CLASS_LOCK) {
            try {
                String threadName = Thread.currentThread().getName();
                log.info(threadName + " walked into office ...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + " got salary...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + " out of office...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 类锁之Class对象
     */
    public static void getSalaryClassLock() {
        synchronized (Worker.class) {
            try {
                String threadName = Thread.currentThread().getName();
                log.info(threadName + " walked into office ...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + " got salary...");
                TimeUnit.SECONDS.sleep(SLEEP_TIME);
                log.info(threadName + " out of office...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 类锁之静态方法
     */
    public static synchronized void getSalarySync() {
        try {
            String threadName = Thread.currentThread().getName();
            log.info(threadName + " walked into office ...");
            TimeUnit.SECONDS.sleep(SLEEP_TIME);
            log.info(threadName + " got salary...");
            TimeUnit.SECONDS.sleep(SLEEP_TIME);
            log.info(threadName + " out of office...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ClassLock类:测试类锁的类

package cn.skywalker.test.thread.lock;

/**
 * @author SkyWalker
 * @date 2020 - 08 - 14 - 10:59
 */
public class ClassLock {
    private static Worker worker = new Worker();

    public static void main(String[] args) {
        getSalaryObjectLock();
        //getSalaryClassLock();
        //getSalarySync();
    }

    private static void getSalaryObjectLock() {
        new Thread(() -> {
            worker.getSalaryObjectLock();
        }, "jack").start();

        new Thread(() -> {
            Worker anotherWorker = new Worker();
            anotherWorker.getSalaryObjectLock();
        }, "rose").start();
    }


    private static void getSalaryClassLock() {
        new Thread(() -> {
            worker.getSalaryClassLock();
        }, "jack").start();

        new Thread(() -> {
            Worker anotherWorker = new Worker();
            anotherWorker.getSalaryClassLock();
        }, "rose").start();
    }


    private static void getSalarySync() {
        new Thread(() -> {
            worker.getSalarySync();
        }, "jack").start();

        new Thread(() -> {
            Worker anotherWorker = new Worker();
            anotherWorker.getSalarySync();
        }, "rose").start();
    }
}

运行结果

在这里插入图片描述
也是与我们的预期相符。

结束语

如果通过上面的两个案例没能发现点什么,那我可以说一点文章中没有具体提到的。

在第一个案例的对象锁测试类(ObjectLock类)中,我注释掉了这行代码:
在这里插入图片描述
不妨尝试解开看看运行结果,这个可以帮助你观察到对象锁和类锁的区别与联系。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java 使用 MySQL 的行级可以通过以下步骤实现: 1. 获取数据库连接 使用 JDBC 驱动程序连接到 MySQL 数据库。可以使用 DriverManager 类的 getConnection() 方法来获取连接对象。 ```java Connection conn = DriverManager.getConnection(url, user, password); ``` 2. 开启事务 为了使用行级,需要在事务执行 SQL 语句。可以使用 Connection 对象的 setAutoCommit() 方法将自动提交关闭,然后使用 beginTransaction() 方法开启一个事务。 ```java conn.setAutoCommit(false); conn.beginTransaction(); ``` 3. 执行 SQL 语句 使用 PreparedStatement 对象执行 SQL 语句,其包含需要加的行的条件。 ```java String sql = "SELECT * FROM table WHERE id = ? FOR UPDATE"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, id); ResultSet rs = stmt.executeQuery(); ``` 其,FOR UPDATE 是 MySQL 用于加行级的关键字。在执行 SELECT 语句时,MySQL 会为符合条件的行加上排它,直到事务提交或回滚为止。 4. 提交事务或回滚事务 如果获取后需要修改数据,则可以在获取后执行更新操作。在更新完成后,使用 commit() 方法提交事务,或者使用 rollback() 方法回滚事务。 ```java String sql = "UPDATE table SET name = ? WHERE id = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, name); stmt.setInt(2, id); stmt.executeUpdate(); conn.commit(); ``` 5. 关闭连接 使用完 Connection 对象后,应该将其关闭。 ```java conn.close(); ``` 需要注意的是,行级在执行 SQL 语句时可能会造成死。因此,在使用行级时需要谨慎考虑的粒度和范围,避免出现死现象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值