今天,又是自我救赎的一天。。。
顶着副热带高压导致的高温上下班,在公司忙了一星期,也没学到多少东西,还是得靠自己下功夫修炼啊,早日把功夫练到化境,天下公司,大可去得!
好了好了,吐槽完毕,回归正题。
这次跟大家讨论的是Java中的同步锁。
特别提醒一下,如果有读者不想看太多的文字,可以直接拷贝下面的代码到自己的编译器里,运行一下,差不多就能看懂了。
同步锁
我们先看看同步锁的概念:
同步锁是为了保证每个线程都能正常执行原子不可更改操作,同步监听对象/同步锁/同步监听器/互斥锁的一个标记锁.
每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,当消费者线程试图执行以带有synchronized(this)标记的代码块时,消费者线程必需先获得this关键字引用的Stack对象的锁.
emmmm,说人话!!!
其实同步锁
就是为了解决多线程
环境下的数据共享、同步
的线程安全问题的。
假设有下面的一个场景:
某一天,你(线程共享对象)正在苦逼的工作,然后你的两个领导(线程)同时来找你,让你帮他们干活。
那么,你要怎么办呢?先去项目经理那干一半,再跑到技术总监那干一半,然后就这样来回跑着把活干完?
不合适。
为此,项目经理和技术总监开始互相撕逼(卧槽,你的身价可以啊,能让两个Leader为你做到这一步),
接下来好戏开始了:
最后项目经理赢了(拿到同步锁),
大笑着把你带到他的办公室,开始干活,等你帮项目经理干完活(线程执行完毕),
项目经理笑眯眯的对技术总监说:“现在轮到你了”(释放同步锁),
你又去技术总监的办公室接着干活(另一个线程拿到了同步锁),
终于,你把所有的活都干完了,技术总监说,小王啊,你去忙你的吧(没事赶紧爬。。。释放了同步锁)
你很无奈的回到自己的工位上,继续喝着咖啡加着班(CPU空闲)。
这充满着血与泪的小故事,应该能帮你理解同步锁的作用。
同步锁的作用大概了解了,下面谈谈同步锁的分类
。
在Java中,同步锁大致分为两类:
- 对象锁
- 类锁
那我们常听到的方法锁是什么呢?方法锁
在概念上是属于对象锁
的。
对象锁
类声明后,我们可以 new 出来很多的实例对象。这时候,每个实例对象在 JVM 中都有自己的引用地址和堆内存空间,这时候,我们就认为这些实例都是独立的个体,很显然,在实例对象上加的锁和其他的实例对象是没有关系的,互不影响。
在Java中,通过synchronized
关键字实现对象锁有3种方式:
- 非静态变量
- 非静态方法
- this关键字
还是以上面的场景为例,我用代码实现了这3种方式的对象锁。
环境
先说明一下我的环境,因为用了lambda表达式简化匿名内部类,所以要求JDK是1.8及以上,我用的8u261。
同时,为了方便观察线程执行的过程,最好在输出的结果上面加上时间戳,如果使用常规的System.out.println()
打印结果,需要自己显式的加入时间戳,可以通过Java中提供的Date
、Calendar
等日期相关类或者System.currentTimeMillis()
实现,但我为了方便,引入了slf4j
和lombok
进行日志打印。
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种方式:
- 静态变量
- 静态方法
- 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类)中,我注释掉了这行代码:
不妨尝试解开看看运行结果,这个可以帮助你观察到对象锁和类锁的区别与联系。