[重难点]
线程安全问题:
针对上面这个代码:按照正常情况下判断输出的count应该为10000 但是并不是这样
多次运行可以发现这个count的值一直在变化.这就是线程不安全导致的因素!
串行执行:(此时运行结果正确)
并行执行:线程1进行++一半的时候 线程2也在++
明明自增两次但是结果却还是1
线程不安全的原因:
1.线程之间是抢占式执行的(线程不安全的根源 并且无法人为改变)
抢占式:
- 线程之间的调度完全由内核决定 用户根本感知不道 也无法控制
- 线程谁先执行谁后执行谁执行到哪里从CPU上下来 用户同样无法感知
系统负载: 衡量服务器繁忙程度的重要指标(数目越大越繁忙)
当线程/进程数目越多的时候 调度的开销就会越大 线程/进程被第二次调上CPU执行的时间间隔会特别长
2.自增操作不是原子性
每次++都能拆分成3个步骤 (但是这些步骤不是一气呵成执行完的 执行到哪步就没了 会让给其他线程执行)
- 从内存中读取数据到CPU中
- 在CPU中数据++
- 把计算结束的数据返回到内存中
原子的:对于前置++; 后置++ ;+= ;-= ;*= ;/=全不是原子的
直接赋值操作不一定 内置类型是原子的(64位CPU)
引用类型不一定
3.多个线程尝试修改 同一个变量
一个线程修改一个变量 线程安全
多个线程修改不同变量 线程安全
多个线程修改同一变量 线程安全
(如果有多个线程 一个读数据 一个修改数据也可能是线程不安全的
都读数据是线程安全的)
4.内存可见性导致的线程不安全性
5.指令重排序(编译器会对指令进行优化 调整先后顺序 保证原有逻辑不变的情况下 提高运行效率)
解决线程不安全的问题:
1.抢占式执行的方法:是内核决定的 我们无法改变
2.非原子: 可以给自增操作加锁
3.不一定有办法 得看具体的需求
锁:(synchronized)
锁 : 就像是我们进ATM机取钱(这个银行只有一个ATM机 其他都坏了) 当我们进去的时候得把锁锁上 其他想取钱的人就只能巴巴的在外面等 等我出来了才能进去
锁的特点: 互斥的 同一个时刻只能有一个线程获取该锁
其他线程如果想获取该锁就会触发线程等待 得等到刚才的线程将 锁释放了剩下的线程才能竞争刚才的锁
进入increase()之前先加锁 increase()方法执行完毕后会自动解锁synchronized加锁解锁全部包办(好处:可以解决忘记解锁的问题)
加锁的操作不一定会立刻成功 当锁被占用的时候会发生阻塞,此时线程等待之前的锁被释放之后才可能获取到这个锁
加上锁之后结果就正确了
图示解析锁的功能:
synchronized的具体使用: 可以指定给某个对象加锁而不是仅仅把锁加到某个方法上 如果把synchronized写到方法前 相当于给当前对象(this)加锁
代码示例:
synchronized的几种常见用法:
1.加到普通方法前 :表示锁this
2.加到静态方法前: 表示锁当前类的类对象
3.加到某个代码块之前: 显示指定给某个对象加锁
两个线程获取同一把锁就会产生线程阻塞
两个线程获取不同的锁就不会发生互斥了
两个锁引用同一个类对象