synchronized锁和lock锁
首先对于Java多线程加锁机制,有两种:
• Synchronized
• 显式Lock
一、synchronized锁
1.1synchronized锁是什么?
synchronized是Java的一个关键字,它能够将代码块(****方法)锁起来
• 它使用起来是非常简单的,只要在代码块(方法)添加关键字synchronized,即可以实现同步的功能~
public synchronized void test() {
// doSomething
}
synchronized是一种互斥锁
• 一次只能允许一个线程进入被锁住的代码块
synchronized是一种**内置锁****/**监视器锁
• Java中每个对象都有一个内置锁****(****监视器,****也可以理解成锁标记),而synchronized就是使用**对象的内置锁****(****监视器)**来将代码块(方法)锁定的! (锁的是对象,但我们同步的是方法/代码块)
1.2synchronized用处是什么?
• synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
• synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)
Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。
1.3synchronized的原理
我们首先来看一段synchronized修饰方法和代码块的代码:
public class Main {
//修饰方法
public synchronized void test1(){
}
public void test2(){
// 修饰代码块
synchronized (this){
}
}
}
同步代码块:
• monitorenter和monitorexit指令实现的
同步方法(在这看不出来需要看JVM底层实现)
• 方法修饰符上的ACC_SYNCHRONIZED实现。
synchronized底层是是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有。
1.4synchronized如何使用
synchronized一般我们用来修饰三种东西:
• 修饰普通方法
• 修饰代码块
• 修饰静态方法
1.4.1修饰普通方法:
用的锁是Java3y对象(内置锁)
public class Java3y {
// 修饰普通方法,此时用的锁是Java3y对象(内置锁)
public synchronized void test() {
// 关注公众号Java3y
// doSomething
}
}
1.4.2修饰代码块:
用的锁是Java3y对象(内置锁)—>this
public class Java3y {
public void test() {
// 修饰代码块,此时用的锁是Java3y对象(内置锁)--->this
synchronized (this){
// doSomething
}
}
}
当然了,我们使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁)
所以,我们可以这样干:
public class Java3y {
// 使用object作为锁(任何对象都有对应的锁标记,object也不例外)
private Object object = new Object();
public void test() {
// 修饰代码块,此时用的锁是自己创建的锁Object
synchronized (object){
// doSomething
}
}
}
上面那种方式(随便使用一个对象作为锁)在书上称之为–>客户端锁,这是不建议使用的。
1.4.3修饰静态方法
获取到的是类锁(类的字节码文件对象):Java3y.class
public class Java3y {
// 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-->Java3y.class
public static synchronized void test() {
// 关注公众号Java3y
// doSomething
}
}
1.4.4类锁与对象锁
synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的是对象锁。
• 它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的!
public class SynchoronizedDemo {
//synchronized修饰非静态方法
public synchronized void function() throws InterruptedException {
for (int i = 0; i <3; i++) {
Thread.sleep(1000);
System.out.println("function running...");
}
}
//synchronized修饰静态方法
public static synchronized void staticFunction()
throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
System.out.println("Static function running...");
}
}
public static void main(String[] args) {
final SynchoronizedDemo demo = new SynchoronizedDemo();
// 创建线程执行静态方法
Thread t1 = new Thread(() -> {
try {
staticFunction();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 创建线程执行实例方法
Thread t2 = new Thread(() -> {
try {
demo.function();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动
t1.start();
t2.start();
}
}
1.5重入锁
我们来看下面的代码:
public class Widget {
// 锁住了
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
// 锁住了
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
-
当线程A进入到LoggingWidget的doSomething()方法时,此时拿到了LoggingWidget实例对象的锁。
-
随后在方法上又调用了父类Widget的doSomething()方法,它又是被synchronized修饰。
-
那现在我们LoggingWidget实例对象的锁还没有释放,进入父类Widget的doSomething()方法还需要一把锁吗?
不需要的!
因为锁的持有者是“线程”,而不是“调用”。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续*“****开锁”**进去的!
这就是内置锁的可重入性。记住,持有锁的是线程。
1.6释放锁的时机
-
当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作。
-
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
不会由于异常导致出现死锁现象~
二、Lock显式锁
2.1Lock显式锁简单介绍
Lock显式锁是JDK1.5之后才有的,它的特点有:
• Lock方式来获取锁支持中断、超时不获取、是非阻塞的
• 提高了语义化,哪里加锁,哪里解锁都得写出来
• Lock显式锁可以给我们带来很好的灵活性,但同时我们必须手动释放锁
• 支持Condition条件对象
• 允许多个读线程同时访问共享资源
2.2synchronized锁和Lock锁使用哪个
前面说了,Lock显式锁给我们的程序带来了很多的灵活性,很多特性都是Synchronized锁没有的。那Synchronized锁有没有存在的必要??
必须是有的!!Lock锁在刚出来的时候很多性能方面都比Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化(毕竟亲儿子,牛逼)
• 优化操作:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁。
所以,到现在Lock锁和Synchronized锁的性能其实差别不是很大!而Synchronized锁用起来又特别简单。Lock锁还得顾忌到它的特性,要手动释放锁才行**(如果忘了释放,这就是一个隐患)**
所以说,我们绝大部分时候还是会使用Synchronized锁,用到了Lock锁提及的特性,带来的灵活性才会考虑使用Lock显式锁~
2.3公平锁
公平锁理解起来非常简单:
• 线程将按照它们发出请求的顺序来获取锁
非公平锁就是:
• 线程发出请求的时可以插队获取锁
Lock和synchronize都是默认使用非公平锁的。如果不是必要的情况下,不要使用公平锁
• 公平锁会来带一些性能的消耗的
三、Java锁简单总结
本文讲了synchronized内置锁和简单描述了一下Lock显式锁,总得来说:
• synchronized好用,简单,性能不差
没有使用到Lock显式锁的特性就不要使用Lock锁了。
四、最后的话
最近对于多线程的锁有些困惑,所以针对性地搬运了歪哥一篇干货非常硬的的文章《三歪教你学多线程》,有兴趣的朋友想了解更多关于多线程的可以进入这个网址去获取这篇文章https://mp.weixin.qq.com/s/r7IrmvBxG5W0hswfcgFjcQ