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理解错误的地方希望大家指正,不要骂我就好~