并发编程的核心问题就是分工、互斥、同步。
锁的使用
锁是为了保证共享资源只能同时被一个线程使用而存在,其根本是为了实现互斥,使线程阻塞。大部分锁的失效都是因为没有理解什么叫共同资源导致,拿A家的锁去锁B家的门想要保护C家的东西。
锁到底锁住了什么?
- 实例对象。(普通方法、new Object())
- class类。(静态方法、静态对象)
锁的范围、类型
范围
- 方法锁。
- 对象锁。
- 类锁。
方法锁
如上代码其实就是方法锁,字面意思就是方法上的锁,锁一个方法,因为这里是一个普通方法(非静态方法),所以他锁住的就是一个实例对象 test1。
类锁
类锁一般有三种形式。因为在java虚拟机中一个类的静态对象只有一份,所以static修饰的方法就是一种类锁,修饰的变量也是类锁,直接使用class也是类锁。
对象锁
对象锁,既可以锁一个实例,也可以锁一个类。如下图:
其实这里主要体现一个粒度范围的概念,方法锁是作用在方法上,范围就是整个方法。对象锁一般通过synchronized(obj) 代码块来使用,范围为代码块的范围。很明显,对象锁会更灵活一些,效率相对也会好一点(如果一个方法仅有几行代码需要加锁,使用方法锁其实就没有什么必要了)。
问题来了
- 锁一个实例对象与锁一个类有什么区别?锁一个实例对象与锁一个类会互斥么?
- 同一个类中两个方法锁(都是静态或者都是非静态),两个线程使用同一实例访问不同方法会互斥么?
- this关键字锁的是实例还是类?
- 静态方法A使用方法锁,与非静态方法B使用静态变量对象锁之间互斥么?
解答
锁一个实例对象与锁一个类有什么区别?锁一个实例对象与锁一个类会互斥么?
区别:
synchronized 写在方法上有两种情况。
1、在普通方法上锁 当前实例对象。再新建一个实例去调用就不会有冲突。
2、在静态方法上 表示锁一个类class。此时不管新建多少个对象都冲突。
如下代码:
方法methodS1作为一个非静态方法锁,锁的只是 Synchronized 类的某一个实例,所以在线程 thread1,thread2分别使用不同实例 test1、test2 调用时,是不会互斥的。
但是如果调用静态方法 methodStatic2,则会互斥。
另外,锁一个实例对象(普通方法)与锁一个类(静态方法) 两者并不互斥,thread1使用test1实例调用methodStatic2方法持有class类锁,thread2仍然可以用test1实例调用method1方法成功。可以这么理解:静态方法是多实例共享资源(方法区)。普通方法为单实例资源(堆),两者并不相同(锁的并不是同一个对象),所以不互斥。
public class Synchronized {
// 方法锁 所的是 某一个 Synchronized 实例
public synchronized void method1() throws InterruptedException {
System.out.println("method1");
Thread.sleep(10000);
}
// 类锁 锁的是 Synchronized.class对象
public static synchronized void methodStatic2() throws InterruptedException {
System.out.println("methodStatic2");
Thread.sleep(10000);
}
public static void main(String[] args) {
// 普通方法对于两个对象来讲,没有互斥。因为锁的是各自的实例对象 test1 和 test2 。
// 静态方法 锁的是整个class 无论哪个对象只要属于一个class都互斥。
Synchronized test1 = new Synchronized();
Synchronized test2 = new Synchronized();
try {
Thread thread1 = new Thread(() -> {
try {
test1.method1();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
test2.method1();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
});
thread1.start();
Thread.sleep(100);
thread2.start();
}catch (Exception ex){
}
}
}
答: 锁一个实例对象场景中,使用不同实例访问就不会互斥。比如new两个实例对象Synchronized(); 锁一个类则不一样,即使new两个实例对象来去访问一样会互斥。
同一个类中两个方法锁(都是静态或者都是非静态),两个线程使用同一实例访问不同方法会互斥么?
public class SynchronizedTest1 {
// public static synchronized void methodStatic1() {
public synchronized void method1() {
System.out.println("test1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("test1 end");
}
// public static synchronized void methodStatic2() {
public synchronized void method2() {
System.out.println("test1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("test1 end");
}
public static void main(String[] args) {
SynchronizedTest1 test1 = new SynchronizedTest1();
new Thread(()->{
test1.method1();
}).start();
new Thread(()->{
test1.method2();
}).start();
new Thread(() -> {
while (true) {
System.out.println("------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
答:会互斥。因为对于是否互斥判断而言,主要看锁的对象是否一样。
非静态方法情况下,锁的是实例对象,而两个线程使用的是同一实例对象 test1,所以非静态会互斥。
静态情况下,锁的是类,同一实例对象必然属于同一个类,因此也会互斥。
this关键字锁的是实例还是类?
答:实例。因为this代指当前实例,而不是当前类。在写代码时,SynchronizedTest1.静态属性正常可用,this.静态属性编译报错,而且this在静态方法中无法使用。如下代码 肯定是互斥的。
public class SynchronizedTest1 {
public void method1() {
synchronized (this) {
System.out.println("test1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void method2() {
System.out.println("test2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
SynchronizedTest1 test1 = new SynchronizedTest1();
new Thread(()->{
test1.method1();
}).start();
new Thread(()->{
test1.method2();
}).start();
new Thread(() -> {
while (true) {
System.out.println("------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
扩展一下: 静态方法锁,与对象锁是否互斥:
public class SynchronizedTest1 {
public void method1() {
synchronized (SynchronizedTest1.class) {
System.out.println("test1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static synchronized void method2() {
System.out.println("test2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
静态方法A使用方法锁,与非静态方法B使用静态变量对象锁之间互斥么?
public class SynchronizedTest1 {
private static final String LOCK = "LOCK";
public void method1() {
synchronized (LOCK) {
System.out.println("test1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void method2() {
System.out.println("test2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
SynchronizedTest1 test1 = new SynchronizedTest1();
new Thread(()->{
test1.method1();
}).start();
new Thread(()->{
test1.method2();
}).start();
new Thread(() -> {
while (true) {
System.out.println("------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
答:不互斥。继续重复一句话,对于是否互斥判断而言,主要看锁的对象是否一样。重点是锁的对象是否一样,锁同一个对象必然互斥,锁不同对象必然不互斥。
静态方法A使用方法锁,锁的对象是Class对象,非静态方法B锁的是静态变量对象,虽然从类型上来讲都是属于类锁,但是不是同一对象,等价于锁了另外一个Class,所以肯定不互斥。