Java从入门到放弃31—多线程并发问题/线程同步/Synchronized关键字/同步方法/同步块
01 并发问题
- 同一个对象被多个线程同时操作,每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
- 举例:上万人同时抢100张票,线上线下同时取一张银行卡上的钱,不安全的集合问题。
- 解决方案:
- 加入休眠是为了放大问题的发生性,便于发现问题。
- 锁机制(队列+锁):由于同一线程的多个进程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保护数据在方法中被访问时的正确性,在访问时加入锁机制(Synchronized)
- 采用JUC库并发编程,如保证线程安全的一些类:CopyOnWriterArrayList等。
02 线程同步
-
处理多线程并发问题时 , 多个线程访问同一个对象 , 并且某些线程还想修改这个对象 . 这时候我们就需要线程同步 . 线程同步其实就是一种等待机制 , 多个需要同时访问此对象的线程进入这个对象的等待池形成队列, 等待前面线程使用完毕 , 下一个线程再使用。
-
由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问冲突问题 , 为了保证数据在方法中被访问时的正确性 , 在访问时加入锁机制synchronized , 当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 ,使用后释放锁即可。
-
弊端:1.一个线程持有锁会导致其他所有需要此锁的线程挂起 。
2.在多线程竞争下 , 加锁 , 释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3.如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置 , 引起性能问题。
03 Synchronized关键字
(1)同步方法
-
同步方法 : public synchronized void method(int args) {}
-
如果一个方法用Synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。每个对象对应一把锁 , 每个synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 ,方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行。
-
采用同步方法的弊端:若将一个较大的方法申明为synchronized 将会严重影响效率。
-
注:方法里面需要修改的内容才需要锁,锁的作用域太大,会导致资源浪费,严重影响效率。
(2)同步块
-
同步块 : synchronized (Obj ) { }
-
Obj被称之为同步监视器。
-
监视器的特性:
- 监视器是只包含私有域的类。
- 每个监视器类的对象有一个相关的锁
- 使用该锁对所有的方法进行加锁。
- 该锁可以有任意多个相关条件。
-
同步监视器的执行过程:
- 第一个线程访问 , 锁定同步监视器 , 执行其中代码。
- 第二个线程访问 , 发现同步监视器被锁定 , 无法访问 。
- 第一个线程访问完毕 , 解锁同步监视器 。
- 第二个线程访问, 发现同步监视器没有锁 , 然后锁定并访问。