背景知识补充:
- 局部变量是线程安全的,局部变量属于线程私有的,在线程间不共享
- 类变量是非线程安全的,在多个实例之间、多个线程之间都是共享的
- 实例变量属于对象,在多个对象间不共享,当多个线程操作同一个对象的实例变量时,该变量将在多个线程中共享,则会导致非线程安全问题,但当多个线程分别操作同一个类的不同对象时,该变量只存在于当前线程中,故不会导致非线程安全问题
为了解决线程不安全的问题,java中提供了synchronized
关键字,synchronized
可以用于修饰方法或修饰代码块
synchronized 同步方法
其语法格式就是在方法前面加上synchronized
关键字,如下:
synchronized public void methodName(){}
适用场景
当该方法内部有对共享资源进行读写操作时该方法就需要使用synchronized
关键字修饰
synchronized
取得的锁是对象锁,当一个线程调用对象的一个同步方法时,该线程将持有该对象的对象锁,其它线程可以直接调用该对象中没有被synchronized
关键字修饰的方法,其它线程若要调用该对象中被synchronized
关键字修饰的方法时,需要等待前一个线程释放该对象的对象锁后方可调用,在此之前只能排队等待
如果多个线程访问同一个类的多个对象,则JVM会创建多个锁
这里说的会创建多个锁,那这些锁是在什么时候创建的呢?
锁重入
前面背景知识提到当一个线程持有某个对象的对象锁时,其它线程若想获得该对象的对象锁则需要等待前一个线程释放对象锁后才可以获得。
现在我们来分析一个例子
有个类中含有两个被synchronized
关键字修饰的方法,分别为methodA、methodB,methodA中调用methodB,此时程序还能正常执行吗?
public class Services {
synchronized public static void methodA(){
System.out.println("methodA");
new Services().methodB();
}
synchronized public void methodB(){
System.out.println("methodB");
methodC();
}
synchronized public void methodC(){
System.out.println("methodC");
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
Services services = new Services();
services.methodA();
}
}
public class Run {
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
}
}
这里涉及到锁重入
的概念,那什么是锁重入
呢?
当一个线程得到某对象的对象锁后,在还未释放该对象锁时该线程再次请求获取该对象的对象锁,是可以再次得到该对象的对象锁。即synchronized
方法、synchronized
代码块的内部调用本类的其它synchronized
方法、synchronized
代码块时,是永远可以得到对象锁的。
锁重入也适用于有父子关系的继承类中
锁释放
当synchronized方法、synchronize代码块执行完毕后该线程持有的对象锁将自动释放,除此之外,当synchronized方法、synchronized代码块中出现异常时,该线程持有的对象锁也将自动释放
synchronized关键字不具有继承性
父类中的synchronized方法在子类中重写时去掉了synchronized关键字,则子类中的该方法不具有同步功能