一、前言
synchronized关键字,我们一般称之为”同步锁“,用它来修饰需要同步的方法和需要同步代码块,默认是当前对象作为锁的对象。在修饰类时(或者修饰静态方法),默认是当前类的Class对象作为所的对象故存在着方法锁、对象锁、类锁 这样的概念
1、普通同步方法,是实例锁,锁是当前实例对象。
不同的实例对象调用非静态同步方法不存在竞争关系,同一个实例对象调用非静态同步方法时存在竞争关系。注意这里是类的当前实例, 类的两个不同实例就没有这种约束了。
举例:线程A和线程B都用了同一个Student对象,Student对象有一个/多个非静态的同步方法,那么同一时刻只能有一个线程访问这些非静态同步方法,其他线程都不能访问所有的非静态的同步方法。
2、静态同步方法,是类锁,锁是当前类的Class对象。
调用静态同步方法和其他静态方法存在竞争关系,与非静态方法不存在竞争关系。
限制多线程中该类的所有实例同时访问该类所对应的静态同步方法。
举例:线程A用了Student1对象,线程B用了Student2对象,Student对象有一个/多个静态同步方法,那么同一时刻只能有一个线程访问这些静态同步方法,因为同一时刻只允许一个实例对象来访问,要么是Student1对象,要么是Student2对象,反正不能多个实例同时访问静态同步方法。
3、同步代码块,锁是Synchonized括号里配置的对象
synchronized(obj):obj称之为同步监视器,是一个Java对象
- 同步监视器必须是引用数据类型,不能是基本数据类型。
- 可以改变同步监视器堆中属性的值,但是不能改变指向堆中的地址。
- 尽量不要用String和包装类型作为同步监视器,容易造成指向堆中地址的变化。
- 一般使用共享资源作为同步监视器,也可以专门创建一个没有任何语义化含义的同步监视器。
同步方法中无需同步监视器;
因为同步方法的同步监视器就是this,即对象本身,或者说class(反射);
同步监视器执行过程:
- 线程1访问,锁定同步监视器,执行代码;
- 线程2访问,同步监视器已被锁定,无法执行代码块;
- 线程1访问完毕,解锁同步监视器;
- 线程2访问,同步监视器未被访问,线程2锁定同步监视器,执行代码。
synchronized(this):锁的是该方法所在类的实例对象
当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,其他线程对object中所有synchronized(this)同步代码块的访问将被阻塞。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
ps:不管如何加锁,都是控制都这个方法或代码块的访问,而不是整个类,这点不要忘记了。
二、原理
Synchronized方法锁(也叫对象锁)
1.修饰在方法上,多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。(java对象的内存地址是否相同)
public synchronized void obj3() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
2.修饰代码块,这个this就是指当前对象(类的实例),多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。(java对象的内存地址是否相同)
public void obj2() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
3.修饰代码块,这个str就是指String对象,多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。(java对象的内存地址是否相同)
public void obj2() {
String str=new String("lock");
//在方法体内,调用一次就实例化一次,多线程访问不会阻塞,因为不是同一个对象,锁是不同的
synchronized (str) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public static void main(String[] args) throws InterruptedException {
test test=new test();
new Thread(new Runnable() {
@Override
public void run() {
test.obj2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.obj2();
}
}).start();
}
//两个方法之间交替执行 没有阻塞
Thread-0 : 4
Thread-1 : 4
Thread-1 : 3
Thread-0 : 3
Thread-1 : 2
Thread-0 : 2
Thread-1 : 1
Thread-0 : 1
Thread-0 : 0
Thread-1 : 0
共用一个对象,多线程调用obj2同步方法,因为使用的是一个对象锁,会阻塞。
String str=new String("lock"); //对象放在方法外,调用方法的时候不会新创建一个对象。
public void obj2() {
synchronized (str) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
Thread-0 : 4
Thread-0 : 3
Thread-0 : 2
Thread-0 : 1
Thread-0 : 0
Thread-1 : 4
Thread-1 : 3
Thread-1 : 2
Thread-1 : 1
Thread-1 : 0
Synchronized类锁
1.Synchronized修饰静态的方法
public static synchronized void obj3() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
2.synchronized (test.class) ,锁的对象是test.class,即test类的锁。
public void obj1() {
synchronized (test.class) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
看完上面的例子,应该可以分清楚什么是对象锁和类锁了。
那么问题来了:在一个类中有两方法,分别用synchronized 修饰的静态方法(类锁)和非静态方法(对象锁)。多线程访问两个方法的时候,线程会不会阻塞?
public static synchronized void obj3() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public synchronized void obj4() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
答案是???
Thread-0 : 4
Thread-1 : 4
Thread-0 : 3
Thread-1 : 3
Thread-1 : 2
Thread-0 : 2
Thread-1 : 1
Thread-0 : 1
Thread-1 : 0
Thread-0 : 0
不会阻塞。