由此可见,volatile不能保证原子性 。
那么,如何解决这个问题呢?
答:使用synchronized关键字 。
改造后的代码如下:
public class VolatileTest {public int count = 0;public synchronized void add() {count++;}public static void main(String[] args) {final VolatileTest test = new VolatileTest();for (int i = 0; i < 20; i++) {new Thread() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {test.add();}};}.start();}while (Thread.activeCount() > 2) {//保证前面的线程都执行完Thread.yield();}System.out.println(test.count);}}
4. 死锁死锁可能是大家都不希望遇到的问题,因为一旦程序出现了死锁,如果没有外力的作用,程序将会一直处于资源竞争的假死状态中 。
死锁代码如下:
public class DeadLockTest {public static String OBJECT_1 = "OBJECT_1";public static String OBJECT_2 = "OBJECT_2";public static void main(String[] args) {LockA lockA = new LockA();new Thread(lockA).start();LockB lockB = new LockB();new Thread(lockB).start();}}class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}} catch (InterruptedException e) {e.printStackTrace();}}}}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_2) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_1) {System.out.println("LockB");}} catch (InterruptedException e) {e.printStackTrace();}}}}
一个线程在获取OBJECT_1锁时,没有释放锁,又去申请OBJECT_2锁 。而刚好此时,另一个线程获取到了OBJECT_2锁,也没有释放锁,去申请OBJECT_1锁 。由于OBJECT_1和OBJECT_2锁都没有释放,两个线程将一起请求下去,陷入死循环,即出现死锁的情况 。
那么如果避免死锁问题呢?
4.1 缩小锁的范围出现死锁的情况,有可能是像上面那样,锁范围太大了导致的 。
那么解决办法就是缩小锁的范围 。
具体代码如下:
class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}}}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_2) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (DeadLockTest.OBJECT_1) {System.out.println("LockB");}}}
在获取OBJECT_1锁的代码块中,不包含获取OBJECT_2锁的代码 。同时在获取OBJECT_2锁的代码块中,也不包含获取OBJECT_1锁的代码 。
4.2 保证锁的顺序出现死锁的情况说白了是,一个线程获取锁的顺序是:OBJECT_1和OBJECT_2 。而另一个线程获取锁的顺序刚好相反为:OBJECT_2和OBJECT_1 。
那么,如果我们能保证每次获取锁的顺序都相同,就不会出现死锁问题 。
具体代码如下:
class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}} catch (InterruptedException e) {e.printStackTrace();}}}}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockB");}} catch (InterruptedException e) {e.printStackTrace();}}}}
两个线程,每个线程都是先获取OBJECT_1锁,再获取OBJECT_2锁 。
5. 没释放锁在java中除了使用synchronized关键字,给我们所需要的代码块加锁之外,还能通过Lock关键字加锁 。
使用synchronized关键字加锁后,如果程序执行完毕,或者程序出现异常时,会自动释放锁 。
但如果使用Lock关键字加锁后,需要开发人员在代码中手动释放锁 。
例如:
public class LockTest {private final ReentrantLock rLock = new ReentrantLock();public void fun() {rLock.lock();try {System.out.println("fun");} finally {rLock.unlock();}}}
代码中先创建一个ReentrantLock类的实例对象rLock,调用它的lock方法加锁 。然后执行业务代码,最后再finally代码块中调用unlock方法 。
但如果你没有在finally代码块中,调用unlock方法手动释放锁,线程持有的锁将不会得到释放 。
6. HashMap导致内存溢出HashMap在实际的工作场景中,使用频率还是挺高的,比如:接收参数,缓存数据,汇总数据等等 。
但如果你在多线程的环境中使用HashMap,可能会导致非常严重的后果 。
@Servicepublic class HashMapService {private Map<Long, Object> hashMap = new HashMap<>();public void add(User user) {hashMap.put(user.getId(), user.getName());}}
推荐阅读
- 聊聊 HTTP/2 的多路复用
- 糖尿病|肥胖可能引起糖尿病等50多种并发症!老人也能减肥缓解慢性病
- 聊聊Mybatis的binding模块
- 聊聊|炉石传说:是时候来聊聊这版本最脏职业到底是谁了
- 大学生|传奇世界:聊聊三职业终极技能,每一个都是经典!(上)
- 一个程序小BUG产生的原因
- 今日全民健身日,聊聊你不知道的健身锻炼冷知识
- 从三点进行分析说明,零基础看完也能明白 学习编程有什么用
- 创始人是王思聪,聊聊ig那些事 ig战队是王思聪的吗?
- 月经量少有哪些并发症?月经量少的患者应避免饮食