synchronized是一种互斥锁
-
一次只能允许一个线程进入被锁住的代码块
synchronized是一种内置锁/监视器锁
-
Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的
synchronized锁作用
-
synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
-
synchronized还保证了可见性
Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全
synchronized底层是是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有。
synchronized一般我们用来修饰三种东西:
-
修饰普通方法
-
修饰代码块
-
修饰静态方法
public class SyncDemo { // 修饰普通方法,此时用的锁是SyncDemo对象(内置锁) public synchronized void test1(){} public void test2(){ // 修饰代码块,此时用的锁是SyncDemo对象(内置锁)-->this synchronized (this){} } // 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-->SyncDemo.class public synchronized static void test3(){} }
类锁和对象锁
synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的是对象锁。
-
它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的
重入锁
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实例对象的锁了,当再需要的时候可以继续“开锁”进去的!
这就是内置锁的可重入性
释放锁的时机
-
当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作。
-
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
-
不会由于异常导致出现死锁现象