转自:https://blog.csdn.net/ygcxydcz/article/details/82314191
并发简单来说,就是CPU在同一时刻执行多个任务。而Java并发则由多线程实现的。在jvm的世界里,线程就像不相干的平行空间,串行在虚拟机中。多线程的存在就是更好地利用CPU资源,提高程序性能,还能减少一定的设计复杂度(用现实的时间思维设计程序)。然而多线程会引出很多难以避免的问题, 如死锁,脏数据,线程管理的额外开销,等等。更大大增加了程序设计的复杂度。
线程安全问题:死锁和脏数据
需要明确一下三点:
1.What:
线程安全就是在多线程环境中,能永远保证程序的正确性。
2.When:
只有存在共享数据时才需要考虑线程安全问题。方法区和堆就是主要的线程共享区域。共享对象只可能是类的属性域或静态域。
3.How:
这里主要讲两种策略:
一.synchronized关键字:
最简单的方式是加入synchronized关键字,只要将操作共享数据的语句加入synchronized关键字,在某一时段只会让一个线程执行完,在执行过程中,其他线程不能进来执行:
方法声明中同步(synchronized )关键字。当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
执行原则:
1.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2.当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
3.尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
4.当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
二、使用锁Lock:
Lock先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。
获取锁:
- 读取表示锁状态的变量
- 如果表示状态的变量的值为0,那么当前线程尝试将变量值设置为1(通过CAS操作完成),当多个线程同时将表示状态的变量值由0设置成1时,仅一个线程能成功,其
它线程都会失败
2.1 若成功,表示获取了锁
2.1.1 如果该线程(或者说节点)已位于在队列中,则将其出列(并将下一个节点则变成了队列的头节点)
2.1.2 如果该线程未入列,则不用对队列进行维护然后当前线程从lock方法中返回,对共享资源进行访问
2.2 若失败,则当前线程将自身放入等待(锁的)队列中并阻塞自身,此时线程一直被阻塞在lock方法中,没有从该方法中返回(被唤醒后仍然在lock方法中,并从下一条语句继续执行,这里又会回到第1步重新开始) - 如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞
释放锁:
- 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点,释放锁的线程从就从unlock方法中返回,继续执行线程后面的代码
- 被唤醒的线程(队列中的队首节点)和可能和未进入队列并且准备获取的线程竞争获取锁,重复获取锁的过程