synchronized原理
大白话
synchronized锁住一个对象,谁能拿到这个对象谁就有访问权,可以说是得倚天屠龙者得天下,谁拿到谁就说了算。
例子
许多大佬都喜欢拿房子举例子,那么我也来拿这个举例子
- 现在有一套房子,你和你的哥们都想进去睡觉,你手快抢到了钥匙(获得锁),于是你就进去,锁上门,开始睡觉了。
- 你哥们这时候也想进来啊,于是他去开门,发现门锁了,打不开,他只能在门口等着你(阻塞)。
- 你饿了,饿的撑不住了,想出去吃饭(CPU时间片切换)
- 但是你怕出去房子就被你哥们占了,所以你爬窗户出去的,门依旧没开锁(CPU时间片切换不会释放锁)
- 最终你在房子里吃饱喝足了,你就开门出去了(释放锁)
- 出门的时候你把锁给了你哥们(唤醒),你哥们就可以进去进行你刚才的操作。
局部变量
常说局部变量在线程内部,就是线程安全的,真的是这样吗?
先举一个不安全的例子
大家可以试试这段代码,会有各种奇奇怪怪的结果。原因呢,就是因为可能某一次加操作还没完成,就执行了减操作,具体的其实就牵扯到了JAVA代码在JVM中是如何执行的问题。
大概就是 i++在JVM中分成了【获取常数,准备常数,计算常数,保存常数】这四步,CPU时间片会不定时切换,无法保证这四步一口气执行完,所以就会有了不安全的存在
public class Test {
private static int num = 0 ;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
for (int i = 0; i < 30000 ; i++){
num ++;
}
}).start();
new Thread(()->{
for (int i = 0; i < 30000 ; i++){
num --;
}
}).start();
System.out.println(num);
}
}
再举一个线程内局部变量OK的例子,无论如何执行,结果都是 30000 与 -30000,说明是安全的
public class Test {
private int num = 0;
public static void main(String[] args) throws InterruptedException {
Test test1 = new Test();
Test test2 = new Test();
new Thread(test1::test1).start();
new Thread(test2::test2).start();
}
private void test1(){
for (int i = 0; i < 30000 ; i++){
num ++;
}
System.out.println("num:" + num + ",线程:" +Thread.currentThread().getName());
}
private void test2(){
for (int i = 0; i < 30000 ; i++){
num --;
}
System.out.println("num:" + num + ",线程:" +Thread.currentThread().getName());
}
}
为什么这样就OK呢?实际就是各个线程带着各自的变量各玩各的,互不影响
但是我既然记录这个事情,就说明局部变量也有不行的,那就是参数为对象的时候,是有异常的,先上代码!
public class Test {
List<Integer> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
Test test1 = new Test();
for (int i = 0 ;i<10000;i++){
new Thread(()->{
test1.test1();
test1.test2();
}).start();
}
}
private void test1(){
list.add(1);
}
private void test2(){
list.remove(0);
}
}
大家执行一下会发现,果然是有异常的
Exception in thread "Thread-5252" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:659)
at java.util.ArrayList.remove(ArrayList.java:498)
at com.test.main.Test.test2(Test.java:24)
at com.test.main.Test.lambda$main$0(Test.java:14)
at java.lang.Thread.run(Thread.java:748)
问题出在哪呢?本质原因就是JVM中存储的位置。八大基础类型是存在于栈的,可以跟着线程玩(这么说也不是很严谨)。但是对象不行,他是存在堆中的,栈帧中只是存了一个指向
说到这大家看着眼熟吗?相信如果你有开发经验,一定遇到过map或者list循环赋值计算,最后发现都是拿最后的值去处理的这样的bug,本质就是这样的问题,这里不展开这块,后面一起。
浅尝辄止~慢慢来才比较快,共勉!如果哪里不对请及时指出