您的位置:首页 > 编程语言 > Java开发

java多线程之多线程安全问题分析和解决方案

2019-08-02 18:27 567 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_42422465/article/details/98215245

经过几天多线程的研究发现java多线程有一些安全问题:举个例子:

[code]public class Main{
private int a=0;
public int getA(){
a++;
return a;
}
public static void main(String[] args) {
final Main m=new Main();
new Thread(new Thread(){//第一个线程
@Override
public void run() {
// TODO 自动生成的方法存根
while(!interrupted()){
System.out.println(Thread.currentThread().getName()+" "+m.getA());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}).start();
new Thread(new Thread(){//第二个线程
@Override
public void run() {
// TODO 自动生成的方法存根
while(!interrupted()){
System.out.println(Thread.currentThread().getName()+" "+m.getA());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}).start();

}
}

上面的输出结果如果按照我们自己的想法输出应该是1 2 3 4 5 6,就是没有重复的,但是上述代码经过测试结果:

 

就是发现其中有俩个3出现了,如果有兴趣可以继续往下翻,发现还有其他数字的出现

问题出现的原因就是,其中一个线程(任意),抢占方法getA()的时候,其他线程也在同时抢占该资源,因为其中一个线程在执行a++操作a值发生了修改,把它重新写入内存中之后,其他线程的cpu缓冲区中并没有改变a值大小,导致有重复的值出现。

我们可以总结出俩个特性:

1.可见性 2.原子性 也就是该变量并没有实现可见性,并且a++操作是非原子性操作(同时有读写操作),导致才出现了线程安全性问题,接下来如何解决这个问题呢?

方法1:

使用synchronized关键字修饰非原子性操作,使非原子性操作看起来是原子性操作,并且可见:

具体的修改代码如下:

[code]public synchronized int  getA(){//加上synchronized关键字
a++;
return a;
}

这个关键字的用法也是比较复杂总的简单的总结一下:

1.synchronized内部有一个内置锁(隐式锁),并且synchronized是重量级锁,可重入锁,排他锁

2.如果该关键字修饰非静态方法锁对象就是当前对象的实例 this

3.如果该关键字修饰静态方法,锁对象就是该类的class字节码对象 类.class

4.该关键字形式可以是同步代码块,也可以是修饰符,功能就是给被修饰的区域上锁,多线程中,其中一个线程拿到该锁,其他线程由于获取不到锁所以会处于自旋状态(等待别人把锁释放的过程),就能实现把非原子性操作改变成原子性操作,由于某个线程正在使用锁其他线程取不到该锁,使该线程往内存中写的时候,其他线程无法读取,从而间接实现可见性

 

方法二:使用Lock接口(显示锁)

Lock是一个接口,在java中有很多类实现了该接口中的行为

我们就使用其中的ReentrantLock类(该类产生的实例是实现了一个可重入锁):

[code]public class Main{
private int a=0;
Lock lock=new ReentrantLock();//可重入锁
public int  getA(){
lock.lock();//上锁
a++;
lock.unlock();//释放锁
return a;
}
}

把非原子性操作放在上锁和释放锁之间来实现可见性和原子性:

方法三:

使用jdk1.5中的原子类非原子操作变成原子操作

[code]public class Main{
private AtomicInteger a=new AtomicInteger(0);//创建原子类实例对象
public int  getA(){
return a.incrementAndGet();//该方法实现的就是++a非原子操作
}
}

或许有些人可能还会知道另外一个关键字volatile 简要阐述就是实现了修饰对象的可见性:

1.是轻量级锁,被volatile修饰的变量在线程之间是可见的

可见:一个线程修改了该变量的值在另外一个线程中能够读到该线程变量的值:

在多处理器系统上面:

1.将当前处理器缓存行的内容写回到系统内容;

2.写回到内存的操作会使其他cpu里面缓存了该内容地址的数据失效

从而实现了可见性:

但是该关键字的使用是有局限性的,就是只能实现可见性而不能实现原子性

最后总结一下线程安全性问题的出现条件:

1.多线程环境下。

2.必须有共享资源

3.对共享资源进行非原子性操作

 

喜欢的记得点个关注哟!

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: