1、一套清晰的规则java
不管是加了锁的方法仍是加了锁的代码块,不管加的是对象锁仍是加的是类锁,只要上的是同一把锁,那么它们的访问规则就应该是相同的。也就是上了同一把锁的东西,一个线程进来要么一块儿容许被访问,要么一块儿禁止访问。所以,若是想搞清楚访问的规则,咱们首先要搞清楚锁的类型。而后判断,只要上的是同一把锁,访问的规则就应该相同。那么java中的锁有那些类型呢。能够简单的总结为两种类型:缓存
一)java中锁的类型:多线程
1.类锁:只有synchronized修饰静态方法或者修饰一个类的chass对象时,才是类锁。性能
2.对象锁:除了类锁,全部其余上锁方式都认为是对象锁,好比:synchronized修饰普通方法或者synchronized(this)给代码块上锁。测试
应该注意的是,由于一个类只有一个.class对象,所以全部的访问者在访问被加了类锁的方法或者代码块时候,都是共用同一把锁,而类的实例却能够有不少个,所以不一样对象访问加了对象锁的方法或者代码块,它们的访问互不干扰。this
二)知道了锁的类型,那么咱们就能够总结出一个通用且清晰的规则了。以下:线程
1.加了相同锁的方法或者代码块,他们的访问规则是相同的,即当否个访问者得到该锁的时候,他们(上锁的方法或者代码块)一块儿向访问者开放(当前访问者能够调用这些方法)同时其余访问者若是访问(上相同锁的这些方法访问)只能等待。对象
2.加了不一样锁的代码或者代码块,多线程访问互不影响继承
3.没有加锁的方法或者代码块能够随意访问不受限制字符串
三)而后再来看怎么判断什么状况下是相同的锁。以下:
1.不一样类型的锁不是同一把锁
2.加的是对象锁,那么必须是同一对象实例才是同一把锁
3.加的是类锁,那必须是同一类才是同一把锁
四)咱们判断访问的规则,就是基于个步骤:
1.首先判断是否是同一把锁
2.而后判断各自的访问规则
五)锁的重入
锁重入:好比2个方法one、two都加了synchronized,one里调用了two,那么能够执行two方法(即便one没有执行完)。继承也能够用锁重入。
2、使用这个规则
1.例子1
//对象锁
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test1方法。");
}
}
private void test2(){
synchronized(this){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test2方法。");
}
}
}
private void name() {
System.out.println("我没有锁,随便访问。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
在代码中,能够看到咱们给test1方法上了对象锁,加锁的方式是给方法加锁,加的是当前对象锁;而给test2方法上也上了对象锁,加锁的方式给代码块加锁,注意上的是当前对象锁,你也能够将this替换成任意其余对象。而后咱们在main方法中看到,新建了两个线程,都是用同一个对象来调用这两个方法。所以,能够断定线程t1和t2使用的是同一把锁,所以访问规则就是t1得到了这把锁后,tes1方法和test2中被加锁的代码块都容许t1访问,都拒绝t2访问,等t1运行完,t2才会得到该锁,进行访问。所以输出的结果很明显了,t1执行完,再执行t2。而sync.name()不受限制,咱们运行下程序,看看咱们按照规则来推理的是否正确,运行结果以下:
0:咱们是test1方法。
我没有锁,随便访问。
1:咱们是test1方法。
2:咱们是test1方法。
0:咱们是test2方法。
1:咱们是test2方法。
2:咱们是test2方法。
由于t1访问test1后上个对象锁,t2这时候也去访问test2,发现test2也被上了同一个对象的锁,没有钥匙,进不去,只能等t1将对象锁释放后才能进去
而后咱们再作个试验,好比将main方法中的代码改为下面的:
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test1方法。");
}
}
private void test2(){
synchronized(this){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test2方法。");
}
}
}
public static void main(String arg[]) {
final SyncSameObjectTest1 st=new SyncSameObjectTest1();
final SyncSameObjectTest1 st2=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
st.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
st2.test2();
//或者
//st2.test1();
}
});
t1.start();
t2.start();
}
只是多出一个实例而已,而后线程t2经过st2来调用test2。那么就test1和test2加的都是当前对象的锁,显然它们的当前对象不一样吧。因此它们不是同一把锁,互相不干扰。那么咱们运行程序,效果以下:它们各自运行各自的,因此没什么顺序。
关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)看成锁,因此示例代码中的那个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁(Lock),两个对象,线程得到的就是两个不一样的锁,他们互不影响。
0:咱们是test1方法。
0:咱们是test2方法。
1:咱们是test2方法。
2:咱们是test2方法。
1:咱们是test1方法。
2:咱们是test1方法。
例子2:
//类锁
private static synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test1方法。");
}
}
//类锁
private void test2(){
synchronized(SyncSameObjectTest1.class){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test2方法。");
}
}
}
private void name() {
System.out.println("我没有锁,随便访问。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
SyncSameObjectTest1.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
很简单,test1是一个静态方法,因此它加的锁是类锁, test2也加了一个类锁,是加在了一个代码块中,所以他们加的是相同的锁。一个类只有一个.class对象,所以全部的访问者在访问被加了类锁的方法或者代码块时候,都是共用同一把锁,即t1访问完,t2再访问。
有一种状况是全部对象都是相同的锁, 即在静态方法上加synchronized关键字,static方法是类独有,表示锁定.class类,类一级别的锁(独占.class类)。而不是对象,这样就是synchronized的.class类,全部对象都拥有共同的锁。
运行程序,结果以下:
0:咱们是test1方法。
我没有锁,随便访问。
1:咱们是test1方法。
2:咱们是test1方法。
0:咱们是test2方法。
1:咱们是test2方法。
2:咱们是test2方法。
例3://类锁
private static synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test1方法。");
}
}
//对象锁
private synchronized void test2(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test2方法。");
}
}
private void name() {
System.out.println("我没有锁,随便访问。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
SyncSameObjectTest1.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
代码中很显然了,test1是静态方法,它上的是类锁。而test2是普通方法,它上的对象锁。这是不一样的锁。因此t1访问test1时,test2方法是不受干扰的,t2确定能够同时访问test2.所以打印顺序为任意顺序。以下:
0:咱们是test1方法。
我没有锁,随便访问。
1:咱们是test1方法。
0:咱们是test2方法。
2:咱们是test1方法。
1:咱们是test2方法。
2:咱们是test2方法。
为乱序。
此时若是你的打印结果为先打印出t1的结果再是t2的结果,也没必要惊讶,由于程序简单,循环次数少,CPU性能高,因此极可能t2刚启动就瞬间运行完了t1。
例子3:
//对象锁
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test1方法。");
}
}
//类锁
private void test2(){
synchronized(SyncSameObjectTest1.class){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱们是test2方法。");
}
}
}
private void name() {
System.out.println("我没有锁,随便访问。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
很明显一个类锁,一个对象锁,访问规则互不干扰。即,t1访问test1方法时,并不影响t2访问test2(可是会禁止t2访问test1,由于t2用的也是sync对象,此时t1已给sync对象上锁了)。因此打印顺序可能为任意顺序,还有若是sync同时访问test2方法,因为test2是类锁,因此全部对象共有一把锁,会按照顺序打印。好了,运行程序,结果以下:
0:咱们是test1方法。
我没有锁,随便访问。
0:咱们是test2方法。
1:咱们是test2方法。
2:咱们是test2方法。
1:咱们是test1方法。
2:咱们是test1方法。
还有不少的实验咱们就再也不作了,都是根据上面总结的规则来作的。相信这下,你对上锁和上锁后的访问规则都清楚了吧。
3、锁非this对象 如:(锁String对象)
在Java中是有常量池缓存的功能的,就是说若是我先声明了一个String str1 = “a”; 再声明一个同样的字符串的时候,取值是从原地址去取的,也就是说是同一个对象。这也就致使了在锁字符串对象的时候,能够会取得意料以外的结果(字符串同样会取得相同锁),下面看一个例子介绍。都是将“xc”字符串传给上面的测试方法。下面看下测试结果。
//对象锁
private void lord(String str){
try {
synchronized(str){
while (true) {
System.out.println(Thread.currentThread().getName());
Thread.sleep(100);
}
}
} catch (Exception e) {
}
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.lord("xc");
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.lord("xc");
}
});
t1.start();
t2.start();
}
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
从结果能够就看到,线程B并无进来,也就说明两个线程持有的是同一个锁,线程1一直循环没释放锁,其余线程也进不来,即字符串对象是同一个。这就是String常量池会带来的问题。因此在大多数状况下,同步代码块synchronized代码块不使用String做为锁对象,而采用其余。稍加改造,每次都建立一个新的字符串对象,不一样对象的锁:
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.lord(new String("xc"));
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.lord(new String("xc"));
}
});
t1.start();
t2.start();
}
Thread-0
Thread-1
Thread-1
Thread-0
Thread-1
Thread-0
4、若是你还不理解的话
若是你还不理解的话,这是某位大牛作的一个很好的比喻。你能够看看。摘抄以下: