简单总结
- synchronized(this|object)以及synchronized修饰非静态方法是对象锁
- synchronized(类.class)以及修饰静态方法是类锁。
- 同一个类的不同对象的对象锁不是一把锁
- 同一个类的同一对象的对象锁,同一个类的同一对象的类锁以及同一个类的不同对象的类锁,分别是同一把锁。
- 类锁和对象锁是独立的,互不干扰。
- 判断方法间是否是同步的,关键是判断获取的是否是同一把锁!
概念
synchronized是java的关键字,是利用锁的机制来实现同步的
对象锁和类锁
- 对象锁
在java中,每个对象都有一个monitor对象,这个对象是java对象的锁。
类的对象有多个,每个对象都有其独立的对象锁,互不干扰。 - 类锁
在java中,每个类也有一个锁,成为”类锁“,类锁实际上是通过对象锁实现的,即类的Class对象锁。
每个类只有一个class对象,也就只有一个类锁。
用法
- 修饰代码块
synchronized(object){} //对象锁
synchronized(xxx.class){} //类锁 - 修饰方法
非静态方法 //对象锁
静态方法 //类锁
具体例子
对象锁
比较不加锁,锁住同步代码块,锁住非静态方法的区别。
对于同一个对象
具体代码
import java.util.Date;
public class Main {
public static void main(String[] args) {
//同一个对象
Test test = new Test();
Thread threada1 = new Thread(test,"A1");
Thread threada2 = new Thread(test,"A2");
Thread threadb1 = new Thread(test,"B1");
Thread threadb2 = new Thread(test,"B2");
Thread threadc1 = new Thread(test,"C1");
Thread threadc2 = new Thread(test,"C2");
threada1.start();
threada2.start();
threadb1.start();
threadb2.start();
threadc1.start();
threadc2.start();
}
}
class Test implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if(threadName.startsWith("A")){
async();
}else if(threadName.startsWith("B")){
sync1();
}else if(threadName.startsWith("C")){
sync2();
}
}
private synchronized void sync2() {
//修饰非静态方法
System.out.println(Thread.currentThread().getName()+ "sync2:"+new Date());
try {
System.out.println(Thread.currentThread().getName()+" sync2_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync2_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void sync1() {
//使用同步代码块 synchronized(this)
System.out.println(Thread.currentThread().getName()+ "sync1:"+new Date());
synchronized (this){
try {
System.out.println(Thread.currentThread().getName()+" sync1_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync1_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void async() {
//异步方法
try {
System.out.println(Thread.currentThread().getName()+" async_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" async_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
结果分析:
- 没有同步代码块,则该代码块是异步的,有线程在访问对象的同步代码块时,其他线程可以访问该对象的非同步代码块。所以在C线程的结果之间插入了A线程的执行结果。
- 锁住同步代码块时(synchronized(this)),同步代码块内的内容时同步的,代码块外的内容是异步的,所以B类线程代码块外代码的执行结果和代码块内的执行结果之间插入了其他线程的执行结果。
- 锁住非静态方法时,方法内的所有代码都是同步的。
- Bstart和Bend之间没有插入c类线程的执行结果,同样的C类线程的所有执行结果之间都没有插入Bstart和Bend的结果,因为获取的是同一个锁(对象锁)。
不同对象
测试方法修改为
public static void main(String[] args) {
//不同对象
// Test test = new Test();
Thread threada1 = new Thread(new Test(),"A1");
Thread threada2 = new Thread(new Test(),"A2");
Thread threadb1 = new Thread(new Test(),"B1");
Thread threadb2 = new Thread(new Test(),"B2");
Thread threadc1 = new Thread(new Test(),"C1");
Thread threadc2 = new Thread(new Test(),"C2");
threada1.start();
threada2.start();
threadb1.start();
threadb2.start();
threadc1.start();
threadc2.start();
}
运行结果
结果分析:
两个线程访问不同对象的synchronized(this){}代码块和synchronized修饰的非静态方法是异步的,因为获取的不是同一把锁,不同对象的对象锁互不干扰。
类锁
import java.util.Date;
public class Main {
public static void main(String[] args) {
//不同对象
Test test = new Test();
// Thread threada1 = new Thread(new Test(),"A1");
// Thread threada2 = new Thread(new Test(),"A2");
// Thread threadb1 = new Thread(new Test(),"B1");
// Thread threadb2 = new Thread(new Test(),"B2");
// Thread threadc1 = new Thread(new Test(),"C1");
// Thread threadc2 = new Thread(new Test(),"C2");
Thread threada1 = new Thread(test,"A1");
Thread threada2 = new Thread(test,"A2");
Thread threadb1 = new Thread(test,"B1");
Thread threadb2 = new Thread(test,"B2");
Thread threadc1 = new Thread(test,"C1");
Thread threadc2 = new Thread(test,"C2");
threada1.start();
threada2.start();
threadb1.start();
threadb2.start();
threadc1.start();
threadc2.start();
}
}
class Test implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if(threadName.startsWith("A")){
async();
}else if(threadName.startsWith("B")){
sync1();
}else if(threadName.startsWith("C")){
sync2();
}
}
private synchronized static void sync2() {
//修饰非静态方法
System.out.println(Thread.currentThread().getName()+ "sync2:"+new Date());
try {
System.out.println(Thread.currentThread().getName()+" sync2_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync2_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void sync1() {
//使用同步代码块 synchronized(this)
System.out.println(Thread.currentThread().getName()+ "sync1:"+new Date());
synchronized (Test.class){
try {
System.out.println(Thread.currentThread().getName()+" sync1_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync1_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void async() {
//异步方法
try {
System.out.println(Thread.currentThread().getName()+" async_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" async_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
结果分析:
同一个对象情况下,类锁和对象锁是一致的。
不同对象
修改测试方法
public static void main(String[] args) {
//不同对象
// Test test = new Test();
Thread threada1 = new Thread(new Test(),"A1");
Thread threada2 = new Thread(new Test(),"A2");
Thread threadb1 = new Thread(new Test(),"B1");
Thread threadb2 = new Thread(new Test(),"B2");
Thread threadc1 = new Thread(new Test(),"C1");
Thread threadc2 = new Thread(new Test(),"C2");
threada1.start();
threada2.start();
threadb1.start();
threadb2.start();
threadc1.start();
threadc2.start();
}
运行结果
结果分析:
两个线程访问同一个类的不同对象的synchronized(类.class){}代码块或synchronized修饰的静态方法还是同步的,和同一个对象的类锁以及同一个对象的对象锁是一致的,因为同一个类的不同对象的类锁还是同一个锁。
类中同时有对象锁和类锁
具体代码
import java.util.Date;
public class Main {
public static void main(String[] args) {
//不同对象
Test test = new Test();
Thread threadb1 = new Thread(test,"B1");
Thread threadc1 = new Thread(test,"C1");
threadb1.start();
threadc1.start();
}
}
class Test implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if(threadName.startsWith("B")){
sync1();
}else if(threadName.startsWith("C")){
sync2();
}
}
private synchronized void sync2() {
//修饰非静态方法
System.out.println(Thread.currentThread().getName()+ "sync2:"+new Date());
try {
System.out.println(Thread.currentThread().getName()+" sync2_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync2_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized static void sync1() {
//修饰静态方法
System.out.println(Thread.currentThread().getName()+ "sync1:"+new Date());
try {
System.out.println(Thread.currentThread().getName()+" sync1_start: "+ new Date());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" sync1_end: "+ new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
线程B和线程C是异步的,sychronized修饰静态方法和非静态方法是异步的,也就是说对象锁和类锁是独立的,互不干扰。
其他
- synchronized关键字不能继承
对于父类中synchronized修饰的方法,子类在覆盖该方法时必须显式使用synchronized关键字修饰才是同步的。 - 定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字修饰,但可以使用synchronized锁住同步代码块。
参考资料
https://juejin.im/post/594a24defe88c2006aa01f1c#heading-5