静态锁: 在静态方法前面加上synchronized方法表示锁定此类,当多个线程调用这个类中的静态方法时会阻塞。
实例锁: 在实例方法前面加上synchronized方法表示锁定类的单个实例,当多个线程调用一个类申明的同一个实例的实例方法是会阻塞。
但静态锁和实例锁两者间的同步并不会受到互相干扰
首先什么是类锁?
就像可以对类的每一个实例(对象)获取一个对象锁一样,对于每一个类都可以获取一个锁,我们称之为类锁。
然后为什么要有静态锁?
简而言之,一个非静态方法获取静态锁通常是为了在防止静态数据上发生竞态条件。因为静态方法是属于类的,即对于静态方法而言是没有对象引用的概念的,那么此时就无法使用对象来对静态方法进行锁定了。我们可以做这样的考虑,就是既然静态方法是属于类的,那么我们是否可以使用类对象来做锁定依据呢?答案是肯定的,我们可以使用表示当前类的类对象或者从属于当前类静态域的静态对象来做锁的判断依据。
// 这是一个很简单的类,里面共享静态变量 num,然后一个静态 和 非静态方法,都加上锁
// 我们假设有两个线程同时操作这两个方法,那么数据能互斥吗?
Java代码
public class Walk {
public static int num = 100;
public static Walk walk = new Walk();
// 静态
public synchronized static int run(){
int i = 0;
while (i < 10) {
try {
num --;
i++;
System.out.println(Thread.currentThread().getName()+":"+num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return num ;
}
// 非静态
public synchronized int walk(){
int i = 0;
while (i < 10) {
try {
num --;
i++;
System.out.println(Thread.currentThread().getName()+":"+num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return num ;
}
}
// 先建立两个测试类,这里我们默认循环10次
public class T3 implements Runnable {
@Override
public void run() {
Walk walk = new Walk();
//Walk walk = Walk.walk;
walk.walk();
}
}
public class T1 implements Runnable{
@Override
public void run() {
Walk walk = new Walk();
//Walk walk = Walk.walk;
// 这里我依然用的new
walk.run();
}
}
Java代码
// 测试方法
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new T1());
Thread t3 = new Thread(new T3());
ExecutorService es = Executors.newCachedThreadPool();
es.execute(t1);
es.execute(t3);
es.shutdown();
}
}
// 测试数据 我就不完全列出了
pool-1-thread-1:98
pool-1-thread-2:98
pool-1-thread-2:97
pool-1-thread-1:96
.....
可以看出两个线程没有互斥,这是为什么呢?
OK,我们将static 关键字去掉,代码我就不贴了,直接看结果。。
pool-1-thread-1:98
pool-1-thread-2:98
pool-1-thread-2:96
结果还是没有出现互斥现象,因此我们默认要先让一个线程执行10次的,假设我们这个是买票系统这是不允许的。为什么会出现这状况呢,方法都加上的锁的。
这里先引一下锁的理解,然后从后向前解释。
JAVA 的锁机制说明:每个对象都有一个锁,并且是唯一的。假设分配的一个对象空间,里面有多个方法,相当于空间里面有多个小房间,如果我们把所有的小房间都加锁,因为这个对象只有一把钥匙,因此同一时间只能有一个人打开一个小房间,然后用完了还回去,再由JVM 去分配下一个获得钥匙的人。
第二次实验,我们是对方法进行加锁了,但是没得到想要的结果,原因在于房间与钥匙。因为我们每个线程在调用方法的时候都是new 一个对象,那么就会出现两个空间,两把钥匙,而静态变量只有一个,相当于我们有两把钥匙,从不同的房间开门取共享的值,因此出错。
如果我们使用静态变量walk 呢?这代码放开,也就是我们统一使用一个对象去操作变量,那么结果..
使用 Walk.walk.walk(); 和 Walk.run();
结果:还是没有互斥
pool-1-thread-1:99
pool-1-thread-2:98
pool-1-thread-1:97
...
如果我们把静态方法关键字 去掉: 就可以看见互斥现象了
pool-1-thread-1:99
pool-1-thread-1:98
pool-1-thread-1:96
结果发现还是会重复,因此我们可以得出,在静态方法上加锁,和普通方法上加锁,他们用的不是同一把所,不是同一把钥匙。从而得出 他们的对象锁是不同的,对象也是不同的。
这里再次引出一个概念:对象锁 和 类锁
对象锁:JVM 在创建对象的时候,默认会给每个对象一把唯一的对象锁,一把钥匙
类锁:每一个类都是一个对象,每个对象都拥有一个对象锁。
呵呵,概念感觉混淆了,其实都是锁,取两个名词,下面区分方便,效果是一样的,如果我们这样实现。
Java代码
// 静态,这里仅仅将方法所 变成了 类锁。
public static int run(){
synchronized(Walk.class) {
int i = 0;
while (i < 10) {
try {
num --;
i++;
System.out.println(Thread.currentThread().getName()+":"+num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return num ;
}
}
结果:
pool-1-thread-1:98
pool-1-thread-2:98
pool-1-thread-2:97
pool-1-thread-1:97
...
发现结果还是不是互斥的,说明在静态方法上加锁,和 实例方法加锁,对象锁 其实不一样的。如果我们改成:
synchronized(walk) {
//....略
}
结果:
pool-1-thread-2:99
pool-1-thread-2:98
pool-1-thread-2:97
这样就互斥了,因为T1 是通过静态变量walk 调用的,默认就是用的walk 对象这把锁,而静态方法 强制让他也使用 walk这把锁,就出现了互斥现象,因为钥匙只有一把。
如果我们两个方法都是静态方法呢?
一定要注意哪个对象正被用于锁定:
1、调用同一个对象中非静态同步方法的线程是互斥的。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
2、调用同一个类中的静态同步方法的线程将是互斥的,它们都是锁定在相同的Class对象上。
3、静态同步方法和非静态同步方法将永远不是互斥的,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将是互斥的,在不同对象上锁定的线程将永远不会互斥。