我们都知道在并发编程学习中锁的概念和使用是必须要学会的,那这个锁到底锁住的是什么呢?它又是如何保证线程之间的并发?
锁的分类
- java中的锁分为对象锁和类锁
- 一个类可以有多个对象,所以一个类可以有多个对象锁
- 一个类只有一个class,所有一个类只能有一个类锁
锁的机制(以synchronized为例)
- 修饰一个代码块时,该代码块称为同步代码块,同一时刻只能有一个线程进入该同步代码块,锁住的是包含这个代码块的对象。
- 修饰一个普通方法时,该方法称为同步方法,同一时刻只能有一个线程进入该方法,锁住的是包含该方法的对象。
- 修饰一个静态方法时,同一时刻只能有一个线程进入该方法,锁住的是包含该方法的类对象。
- 修饰一个类,同一时刻只能有一个线程访问,锁住的是类对象。
对象锁
1. 修饰一个代码块
public class Synchronize {
public static void main(String[] args) {
Print print = new Print();
new Thread(print::printThread).start();
new Thread(print::printThread).start();
}
}
class Print {
public void printThread() {
synchronized (this) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
}
执行结果:
Thread-0正在执行
Thread-1正在执行
一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
注意:不同的对象拥有不同的对象锁,可能导致线程同步失败
public static void main(String[] args) {
Print print1 = new Print();
Print print2 = new Print();
new Thread(print1::printThread).start();
new Thread(print2::printThread).start();
}
执行结果:
Thread-1正在执行
Thread-0正在执行
2. 修饰一个普通方法
public class Synchronize {
public static void main(String[] args) {
Print print = new Print();
new Thread(print::printThread).start();
new Thread(print::printThread).start();
}
}
class Print {
public synchronized void printThread() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
synchronized修饰普通方法和修饰代码块类似,下面两种方式是等价的:
public synchronized void method(){
...
}
public void method(){
synchronized (this){
...
}
}
当一个线程访问对象的synchronized修饰的代码块或方法时,另一个线程虽然不能访问synchronized修饰的作用域的代码,但可以访问其他的代码。
public class Synchronize {
public static void main(String[] args) {
Print print = new Print();
new Thread(print::printThread).start();
new Thread(print::printThread2).start();
}
}
class Print {
public synchronized void printThread() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
}
public void printThread2() {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
执行结果:
Thread-1正在执行
Thread-0正在执行
上面两种方式就是对象锁的主要实现方法,对象锁可以有多个,不同的对象拥有各自的对象锁。
类锁
1. 修饰一个静态方法
public class Synchronize {
public static void main(String[] args) {
Print print1 = new Print();
Print print2 = new Print();
new Thread(()->print1.printThread()).start();
new Thread(()->print2.printThread()).start();
}
}
class Print {
public synchronized static void printThread() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
执行结果:
Thread-0正在执行
Thread-1正在执行
静态方法时属于类的,因此对静态方法使用synchronized修饰后锁住的就是class对象,而该类的每个实例对象都由该class构造而来。synchronized修饰的静态方法锁住的是这个类的所有对象。
2. 修饰一个类
synchronized还可作用于一个类
public class Synchronize {
public static void main(String[] args) {
Print print = new Print();
new Thread(print::printThread).start();
new Thread(print::printThread).start();
}
}
class Print {
public void printThread() {
synchronized (this.getClass()){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
}
这种形式和修饰静态方法是一样的,因为class对象只有一个,该类的所有对象都共用一把锁。