判断一个锁的作用时,首先看锁的对象是什么?也即是synchronized锁定了谁的 Monitor 计数器
是类本身(例如静态变量或者静态方法,或者直接对类名.class进行上锁),还是成员变量/方法
再看上锁的方法是否运行在同一个实例之上
如果是对类本身上锁,那么无论是一个实例还是多个实例都是有效的,因为上锁的对象全局唯一
如果是对类的成员变量/方法上锁:
运行在同一个实例对象上,那么这个锁就可以生效
运行在不同的实例对象上,那么这个锁就无效
结合具体代码来看
实例1:
首先add()是一个成员函数,那么synchronized上锁的就是当前的实例对象,而t1和t2的实例对象都是lock,那么它们调用add()时使用的都是同一个的成员变量count
此时synchronized是生效的
也即是说,如果此时不对add()函数上锁,那么count++操作的原子性就得不到保证,也即是
- count从主内存中取出
- count+1
- count放回主内存
这三步并不能保证被一起执行,那么就可能出现t1和t2同时取出count加1后再放回去。
例如:count=1时,t1和t2同时调用add()函数进行操作。t1加1后count等于2,t2加1后count也等于2,所以导致最后答案比预期结果小
package 锁;
public class 锁是否生效 {
int count;
// 定义一个同步方法,确保线程安全
synchronized void add() {
count++;
}
public static void main(String[] args) {
锁是否生效 lock = new 锁是否生效();
// 创建线程t1,并重写其run方法
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000000; i++) {
lock.add();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000000; i++) {
lock.add();
}
}
});
// 启动线程t1
t1.start();
t2.start();
// 等待t1执行完毕,可以在这里加入try-catch来处理可能的中断异常
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的count值
System.out.println("最终的count值为: " + lock.count);
}
}
实例2
add()依然是一个成员函数,synchronized上锁的是当前的实例对象
但是注意,t1和t2此时的实例对象并不相同,它们调用add()时使用的并不是同一个的成员变量count,t1使用的是lock1.count,t2是lock2.count
所以synchronized是不生效的
也即是说,如果此时不对add()函数上锁,也是不会发生错误的
package 锁;
public class 锁是否生效 {
int count;
// 定义一个同步方法,确保线程安全
synchronized void add() {
count++;
}
public static void main(String[] args) {
// 创建线程t1,并重写其run方法
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
锁是否生效 lock1 = new 锁是否生效();
for (int i = 0; i < 10000; i++) {
lock.add();
}
System.out.println("最终的count值为: " + lock.count);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
锁是否生效 lock2 = new 锁是否生效();
for (int i = 0; i < 10000; i++) {
lock.add();
}
System.out.println("最终的count值为: " + lock.count);
}
});
// 启动线程t1
t1.start();
t2.start();
// 等待t1执行完毕,可以在这里加入try-catch来处理可能的中断异常
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的count值
}
}
实例3
如果此时将count设置为静态变量,那么最后输出的值就会发生错误,因为就算有t1和t2使用的是两个实例对象,但t1和t2的add()函数中使用的是同一个count
静态变量具有全局可见性,所以是唯一的