1.当 synchronized A方法被一个线程调用的时候(运行过程中), 另外一个线程调用A方法会block住,而并不是请求失败,如果此时在block住的线程实例上调用interrupt方法就会触发InterruptedException,然后请求的序列会被cache在请求队列中,在队列中的顺序并不一定是你代码从上到下的运行顺序,而是未知的,这个队列可以cache住的任务数量可以很大,
直到耗光所有内存为止。
public class Test_synchronized { private int a = 0; public static void main(String[] args) { final Test_synchronized sy = new Test_synchronized(); for (int i = 0; i < 99; i++) { final int tmp = i; new Thread(new Runnable() { @Override public void run() { sy.A(tmp); } }).start(); } //当控制台输出 --->end的时候 就意味着所有的线程都开始运行了 System.out.println("----> end"); } //改变共享变量 a 这个方法会sleep 3 秒钟 public synchronized void A(int num) { try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } a = num; System.out.println(a); } }
运行结果:
0 2 3 1 4 5 6 7 8 ----> end 9
2. synchronized 不能直接锁定一个变量(private synchronized int c 这个是错误的),因为java的同步时基于对象的对象锁(官方叫法为监视器),而变量不是对象,这里应该这样理解,Object o = new Object(); o 是指向一定对象的,也就是“=” 右边的东西是对象, 而a只是一个占用4个字节的指针, 所以a并不是对象,那么也就不能锁住变量了。
但是如果用synchronized(变量){}这里可以用变量,因为这里是一个引用,就是指针指向的那个对象。
3. synchronized (obj){ ... } 其中obj就是对象锁,这个对象锁可以理解为是一个标记,那为什么要用对象而不用普通类型做标记,因为java是基于对象的语言,即使同一个类的不同对象实例也会拥有不同的hashcode,也就是唯一的标示,就像身份证一样。
当synchronized 方法运行的时候,JVM会读对象锁的计数器,如果为0就可以放行,如果不为0就要等别人释放这个方法的对象锁。
其实可以理解为对象锁可以和synchronized 方法没有任何关系,对象锁仅仅只是一个标记,并且这个标记必须为对象,就像是你每回去电影院看电影都会买到一张不同的票,票是完整的就证明没看过,检票后,票会被撕开一部分,就证明这张票看过了。
所以 synchronized 只要传入的是一样的对象锁,那么就可以锁住不同的代码块,字符串也行,就是 "" 这样的空字符串也行。
public class Test_synchronized_2 { //如果运行结果都是一般长的就代表线程同步了,如果出现b --- > thread id == 1 2a ---> thread id == 8 1这样的结果 //就证明a方法走到了print后还没有运行println, b方法就运行print了,就是说是费线程同步的 public static void main(String[] args) { final Test_synchronized_2 sy = new Test_synchronized_2(); new Thread(new Runnable() { @Override public void run() { while(true) { sy.a(1); } } }).start(); while(true) { sy.b(2); } } public void a(int flag) { synchronized("123") { //注意这里不会输出换行符哦~!这是print和println的区别啦 System.out.print("a ---> thread id == " + Thread.currentThread().getId() + " " + flag); //注意这里会自动输出换行符哦 System.out.println(); } } public void b(int flag) { synchronized("123") { System.out.print("b --- > thread id == " + Thread.currentThread().getId() + " " + flag); System.out.println(); } } }
运行结果:
a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2
这里有容易犯错的地方,"123"实际上是创建的匿名对象,那就是说a和b方法里锁住的对象锁不一样啦,因为每次都创建了一个新对象,相当于2个"123"对象,那为什么还能同步呢,是以为java里有String池的概念,所以"123"都是从池子里拿出来的同一个对象,只要在第一次时是创建,后面就是取了。如果把"123"换成new String("123");就不会线程同步了
public class Test_synchronized_2 { //如果运行结果都是一般长的就代表线程同步了,如果出现b --- > thread id == 1 2a ---> thread id == 8 1这样的结果 //就证明a方法走到了print后还没有运行println, b方法就运行print了,就是说是费线程同步的 public static void main(String[] args) { final Test_synchronized_2 sy = new Test_synchronized_2(); new Thread(new Runnable() { @Override public void run() { while(true) { sy.a(1); } } }).start(); while(true) { sy.b(2); } } public void a(int flag) { synchronized(new String("123")) { //注意这里不会输出换行符哦~!这是print和println的区别啦 System.out.print("a ---> thread id == " + Thread.currentThread().getId() + " " + flag); //注意这里会自动输出换行符哦 System.out.println(); } } public void b(int flag) { //换成new String("123" synchronized(new String("123")) { System.out.print("b --- > thread id == " + Thread.currentThread().getId() + " " + flag); System.out.println(); } } }
运行结果:
b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2a ---> thread id == 8 1 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1 a ---> thread id == 8 1b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2 b --- > thread id == 1 2
4.对于synchronized 的非 static 方法而已,java编译器隐式的用了this这个对象锁, 对于synchronized 的 static 方法而言,编译器隐式用了 方法所在类的class对象作为对象锁。
5.如果一个类里面的方法都是synchronized的,即都是用的this对象锁的时候,那么当一个方法执行的时候另外一个方法就只有等的份了,可以理解为所有方法都是一个互斥的整体,一个上,其他的就得全部等。如果我又一个类,我想要里面的所有方法之对自己是同步的,对别人不影响,就是说a方法被调用时,其他线程调用a方法时就block住了,但是还可以调用b方法。也就是单方法的同步。有什么场景是需要使用这样的功能的呢? 当我取钱的时候账户里只有100元,当我调用取钱方法a取100时,其他人就不能同时调用a方法取钱了,要不然就会取出多余账户余额的数目,而当我取的时候,显然账户还可以同时接收网购给我的退款,存和取互不影响,但是取和存自己是互斥的。
public class Test_synchronized_3 { public static void main(String[] args) { final Test_synchronized_3 sy = new Test_synchronized_3(); new Thread(new Runnable() { @Override public void run() { while(true) { sy.a(); } } }).start(); while(true) { sy.a(); } } //如果出现11 或者 111或者更多1在同一行,证明该方法没有被锁住. public void a() { synchronized("1") { System.out.print("1"); System.out.println(); } } }
运行结果:
1 1 1 1 1 1 1 1
运行结果全部是单行1,即a方法同一时间只能被同一线程调用。如果去掉synchronized就会出现11或者111或者更多1在同一行的情况
如果增加b方法且是用和a方法一样的对象锁,那么a和b方法就同步,同一时间只能有一个线程运行a和b其中之一,如果a和b的对象锁不一样, 那么就不会互斥,就可能出现12或者21或者其他的很多数在同一行的情景.
public class Test_synchronized_3 { public static void main(String[] args) { final Test_synchronized_3 sy = new Test_synchronized_3(); new Thread(new Runnable() { @Override public void run() { while(true) { sy.a(); } } }).start(); while(true) { sy.b(); } } //如果出现11 或者 111或者更多1在同一行,证明该方法没有被锁住. public void a() { synchronized("1") { System.out.print("1"); System.out.println(); } } public void b() { synchronized("2") { System.out.print("2"); System.out.println(); } } }
运行结果:
1 1 1 1 1 2 2 21 1 1 2 2 1
这样就实现了 方法自身同步 方法之间不同步的效果。
JAVA 5 以后增加了 Lock的类 实现类似于过程化的同步功能。以后研究研究在聊聊, synchronized理解错误的地方希望大家指正,不要骂我就好~