天天看点

Java juc系列7 —— 线程安全何为线程安全如何解决线程安全结语

Java JUC系列目录链接

Java 线程池核心原理解析

  • 何为线程安全
  • 如何解决线程安全
  • 结语

何为线程安全

直接上个伪代码:

boolean a = 多个线程共享变量;
boolean b = 存在写操作;
boolean c = 写操作导致数据冲突;

if( a && b && c ) {
	存在线程安全问题;
}
           

文字描述就是,只有

多个线程共享变量

线程存在写操作

操作导致数据冲突

同时满足的情况下,才会存在线程安全问题。

如何解决线程安全

解决线程安全还不简单,不就是让上面的if的条件不为真嘛!显而易见,只要a,b,c中有一个或多个恒为false,就不会出现线程安全问题。

下面我们就给出具体解决方案:

  • ‘a’ always == false:

    即:多个线程不共享变量。在这之前我们先来想一下,为什么多个线程会共享变量?联系Java虚拟机模型,我们知道,如果我们的变量是存在堆区的,那么这个变量就会被多个线程所共享。明白了这一点就好办了,

    *方案1

    我们直接把变量定义到栈,即局部变量,那不就不会跟其他线程共享啦,自然旧解决了线程安全的问题。

    伪代码如下:

    class A {
    // int a = 1;
    	public void test(){
    		int a = 1;
    	}
    }
               

    我们把原来的全局变量a移到了局部变量,这样a就会被存放到方法’test’的方法栈中,自然就不会有线程安全的问题了。

    如果说你非要犟,我就是要把变量存到堆中去,也要不让线程之间共享变量,那怎么办?

    别慌,我们还是有办法的,如果大家听过ThreadLocal这个类,那一定不会束手无策了。

    *方案2

    ThreadLocal为每一个线程需要的变量提供一个本地的副本,这样就不会有线程安全问题啦。什么意思?简单来说就是,你线程不是想要a变量嘛,我给你们线程没人一个a变量,但是你们都只能各玩各的,不许去玩别人的。但是使用ThreadLocal有一点要注意,你创建的这个对象如果存放在堆中的话,还是可能会导致线程安全问题的。
  • ‘b’ always == false:

    即:线程不存在写操作。既然你说写操作会有线程安全问题,那我们来个绝的,既然写了问题这么多,大家都别想写了!

    *方案3

    直接把变量变成final类型,直接上伪代码:
    class A {
    	final int a = 1;
    	}
               
    虽然这个解决方案有点绝,但也不失为一总可选方案呀。
  • ‘c’ always == false:

    即:写操作不导致数据冲突。写操作不冲突?emm…让我想想,既然你们都一起来搞会有冲突,那这样,你们排队一个个来玩,那不就完了嘛。这就是我们最熟悉的——‘锁’。

    *方案4

    我们使用锁来确保线程依次对变量进行操作,这样就避免了线程安全问题,至于怎么加锁synchronized,Lock啥的随便你选,我就不说了。

    你说什么?加锁性能低?没办法了,只能掏出这个了——‘CAS’,即为乐观锁。想要确保线程的写操作不导致数据冲突,加锁确实是性能较低的一种解决方案,乐观锁看起来名字像是锁,其实是一种无锁机制,原理想必你们背面试题肯定是十分熟练了。顺便一提的是,传统CAS可能会导致‘ABA’问题,所以有人在乐观锁的实现中加入了时间戳(版本号)。

结语

本文只是从广度上简介了关于线程安全的一些问题,关于文中提到的一些深度问题,比如jvm模型,ThreadLocal啥的与本文无关,这里就不再赘述了。