- 问:当一个对象中有2个方法同时用synchronized修饰,那么当线程一在访问方法1时,其他线程是否可以访问方法二?
- 验证一:只有方法一使用synchronized修饰时,其他线程可以随意访问方法二。
- 验证二:方法一和方法二同时使用synchronized修饰时,必须等线程一释放对象锁之后其他线程才能获取锁访问方法二。
- 应用:在属性的get和set方法上面加synchronized,那么这个属性就是线程安全的
- 思考:根据上述内容,我们能总结的说:加锁(synchronized)不仅仅保证执行顺序同步,还保证了内存的可见性。
- 扩展:对象锁跟类锁有什么区别?
- 扩展一:不同对象使用synchronized修饰的2个方法是否能保证同步?
- 扩展二:不同对象使用synchronized修饰的2个方法如何能保证同步?
问:当一个对象中有2个方法同时用synchronized修饰,那么当线程一在访问方法1时,其他线程是否可以访问方法二?
答案:由于对象的内置锁(监视器锁)是唯一的,所以当线程一在访问对象的方法1时,持有了该对象的内置锁,那么再线程一释放该内置锁之前,其他线程是无法获取该对象内置锁,所以其他线程无法访问方法二。
验证一:只有方法一使用synchronized修饰时,其他线程可以随意访问方法二。
/**
* 我们测试,如果统一个对象,有2个方法都使用synchronized。
* 验证:每个对象有唯一的对象锁;(称为:内置锁或监视器锁)
* 那么当线程一在访问方法一时,已经持有方法该对象锁。
* 其他线程若想执行方法二,必须等待线程一释放该对象锁。
* @Author: dhcao
* @Version: 1.0
*/
public class SynchronizedFac {
public synchronized void methodOne() throws Exception {
String threadName = Thread.currentThread().getName();
Thread.sleep(3000);
System.out.println(threadName + " 执行方法1");
}
/**
* 第一次测试,方法二不加锁,我们预期:
* 当线程一执行方法1时,其他线程是可以访问方法二的。
*/
public void methodTwo() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行方法2");
}
public static void main(String[] args) throws Exception{
// 保证对象相同
final SynchronizedFac fac = new SynchronizedFac();
for (int i = 0; i < 5; i++) {
// 我们让线程一来访问方法一
if (i == 0) {
new Thread(new Runnable() {
public void run() {
try {
fac.methodOne();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}else{
// 其他线程则访问方法二
new Thread(new Runnable() {
public void run() {
try {
fac.methodTwo();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}
}
}
}
运行结果:由于方法1中有线程等待,所以其他线程率先执行完了方法二
thread -- 1 执行方法2
thread -- 2 执行方法2
thread -- 3 执行方法2
thread -- 4 执行方法2
thread -- 0 执行方法1
验证二:方法一和方法二同时使用synchronized修饰时,必须等线程一释放对象锁之后其他线程才能获取锁访问方法二。
package org.dhcao.relax.synchronizedOneInstance;
/**
* 我们测试,如果统一个对象,有2个方法都使用synchronized。
* 验证:每个对象有唯一的对象锁;(称为:内置锁或监视器锁)
* 那么当线程一在访问方法一时,已经持有方法该对象锁。
* 其他线程若想执行方法二,必须等待线程一释放该对象锁。
* @Author: dhcao
* @Version: 1.0
*/
public class SynchronizedFac {
public synchronized void methodOne() throws Exception {
String threadName = Thread.currentThread().getName();
Thread.sleep(3000);
System.out.println(threadName + " 执行方法1");
}
/**
* 第二次测试,方法二加锁,我们预期:
* 当线程一执行方法1时,其他线程是不可以访问方法二的;
* 必须等待线程一释放锁
*/
public synchronized void methodTwo() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行方法2");
}
public static void main(String[] args) throws Exception{
final SynchronizedFac fac = new SynchronizedFac();
for (int i = 0; i < 5; i++) {
// 我们让线程一来访问方法一
if (i == 0) {
new Thread(new Runnable() {
public void run() {
try {
fac.methodOne();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}else{
// 其他线程则访问方法二
new Thread(new Runnable() {
public void run() {
try {
fac.methodTwo();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}
}
}
}
执行结果:在线程1(thread — 0)访问方法1时,该对象锁已经被线程一持有,则其他线程需等待该线程退出方法一,释放对象锁,才能进入被synchronized修饰的方法;
thread -- 0 执行方法1
thread -- 4 执行方法2
thread -- 3 执行方法2
thread -- 2 执行方法2
thread -- 1 执行方法2
应用:在属性的get和set方法上面加synchronized,那么这个属性就是线程安全的
/**
* 这是一个线程安全类
* @Author: dhcao
* @Version: 1.0
*/
public class SynchronizedInteger {
/** 定义属性 */
private int value;
public synchronized int getValue() {
return value;
}
public synchronized void setValue(int value) {
this.value = value;
}
}
若是没有前面的所说的synchronized的使用方法,那么有可能会认为这个类是线程不安全的。但是现在可知:当有线程在调用setValue方法时,由于它持有了对象的内置锁,那么其他线程是无法通过getValue方法访问到value值的。只有在set方法调用完成,value值更新,线程释放内置锁后,其他线程才能调用get方法,这时,value值已经更新了!
思考:根据上述内容,我们能总结的说:加锁(synchronized)不仅仅保证执行顺序同步,还保证了内存的可见性。
首先对内存的可见性要有一定的了解:
https://www.cnblogs.com/dhcao/p/10982278.html
工作线程和主内存的关系如上篇博客(虽然其他写得不太好,但是这个内存关系应该容易理解)。分别有x、y、z三条线程分别从主线程中读取数据a=1。那么对于线程x、y、z来说,a=1是独立的。即,当线程x执行a=2,那么线程y和线程z会知道a已经更新成为2了么。答案是不知道,因为在x执行a=2之前,线程y和z已经从主内存中加载了a=1这个信息。那么这是称为线程x和线程y、z的内存是不可见的!即:工作线程之间内存不可互相察觉!
但是通过上述分析我们可知,如果变量使用了synchronized来修饰。那么得到的内存结果是:当线程x在读取主内存中a=1信息时,由于对象锁被线程x持有,那么线程x、y只能等待,而不能从主内存中加载a=1这个信息。当x线程执行完set方法:a=2。然后将a=2这个值写到主内存中去,再释放对象的内置锁,此时线程x、y才能读取到主内存中的a的值,读取的结果是a=2。
这个现象我们可以理解为:线程x的工作内存将a=1修改为a=2。并且线程x、y知晓了这个修改。可以叫做线程x的内存对于线程y和z是可见的!
扩展:对象锁跟类锁有什么区别?
如上文,都是使用的同一个实例来执行。如果使用不同的实例,new出不同的对象来,那么上述顺序还能保证么。
答案肯定是不能,对象锁的作用域只在该对象中
扩展一:不同对象使用synchronized修饰的2个方法是否能保证同步?
package org.dhcao.relax.synchronizedOneInstance;
/**
* @Author: dhcao
* @Version: 1.0
*/
public class SynchronizedFac {
public synchronized void methodOne() throws Exception {
String threadName = Thread.currentThread().getName();
Thread.sleep(3000);
System.out.println(threadName + " 执行方法1");
}
public synchronized void methodTwo() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行方法2");
}
public static void main(String[] args) throws Exception{
// 不再使用统一个对象
// final SynchronizedFac fac = new SynchronizedFac();
for (int i = 0; i < 5; i++) {
// 我们让线程一来访问方法一
if (i == 0) {
new Thread(new Runnable() {
public void run() {
try {
// 每次new一个对象来执行
new SynchronizedFac().methodOne();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}else{
// 其他线程则访问方法二
new Thread(new Runnable() {
public void run() {
try {
// 每次new一个对象来执行
new SynchronizedFac().methodTwo();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}
}
}
}
运行结果:如我们所料,synchornized在方法上默认使用对象的内置锁,当不同的对象时,自然不能保证同步。
thread -- 1 执行方法2
thread -- 2 执行方法2
thread -- 3 执行方法2
thread -- 4 执行方法2
thread -- 0 执行方法1
扩展二:不同对象使用synchronized修饰的2个方法如何能保证同步?
如上所说,不同对象处理2个方法时,使用对象的内置锁必然是失效的,那么如果我就要保证同步呢,那么我们可以看如下代码:使用类锁(class)
public class SynchronizedFac {
/**
*使用锁:this.getClass
*/
public void methodOne() throws Exception {
synchronized (this.getClass()){
String threadName = Thread.currentThread().getName();
Thread.sleep(3000);
System.out.println(threadName + " 执行方法1");
}
}
/**
* 使用锁:this.getClass
*/
public void methodTwo() {
synchronized (this.getClass()){
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行方法2");
}
}
public static void main(String[] args) throws Exception{
for (int i = 0; i < 5; i++) {
// 我们让线程一来访问方法一
if (i == 0) {
new Thread(new Runnable() {
public void run() {
try {
new SynchronizedFac().methodOne();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}else{
// 其他线程则访问方法二
new Thread(new Runnable() {
public void run() {
try {
new SynchronizedFac().methodTwo();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread -- " + i).start();
}
}
}
}
运行结果:如我们所料,依然能保证同步,因为此时锁使用的是class的对象锁,就算不同的实例,那么class对象依然是相同的!当然还可以用其他的锁,只要能保证使用同一个锁。
thread -- 0 执行方法1
thread -- 4 执行方法2
thread -- 3 执行方法2
thread -- 2 执行方法2
thread -- 1 执行方法2