首页 > 编程知识 正文

java局部变量和线程变量,怎么保证成员变量的线程安全

时间:2023-05-06 14:41:02 阅读:29581 作者:1057

看到局部变量和成员变量的线程安全分析前言黑马程序员全面深入学习java并并行编程,我感到怀疑并总结了一下。 同时,我找到了一篇视频总结也很好的文章。 3359 blog.csdn.net/m0 _ 37989980/article/details/111400237

线程安全分析成员变量和静态变量是否线程安全?

如果未共享,则为线程安全

在它们被共享的情况下,根据它们的状态是否能够改变,也可以分为两种情况

仅读取操作时为线程安全

有读写操作时,该代码为临界区域,需要考虑线程安全

局部变量是否线程安全?

局部变量是线程安全的

然而,局部变量引用的对象并不总是这样

如果对象没有方法的访问权限,则为线程安全

如果此对象退出方法范围,则必须考虑线程安全

成员变量示例static final int THREAD_NUMBER=2; 静态final int loop _ number=200; publicstaticvoidmain (字符串[ ] args ) threadunsafetest=new thread unsafe ); for(intI=0; i THREAD_NUMBER; I ) newthread((-) test.method1) loop_number );' Thread'I(.start ); } classthreadunsafe { ArrayList string list=new ArrayList (; 公共语音方法1 (Intloopnumber ) for ) intI=0; i loopNumber; 在I ()//)临界区域,竞争状态的条件method2); method3(; //}临界区域} }私密void method2() list.add ) '1); }私有语音方法3 () list.remove ) ) 0; }} 报错分析

因为list是成员变量,无论哪个线程中的 method2 引用的都是同一个堆中的 list 成员变量

此时list存在并发问题,因为多个Thread使用的同一个list,在并发情况下add操作可能被覆盖,导致remove的比add的多从而报错

3358 www.Sina.com/class thread safe { publicfinalvoidmethod1(intloopnumber ) ArrayList string list=new ArrayList ); for(intI=0; i loopNumber; I ) {方法2 (列表); 方法3 (列表; } privatevoidmethod2(阵列列表字符串列表) list.add('1); } privatevoidmethod3(阵列列表字符串列表) list.remove(0; }} 局部变量示例

list是局部变量,对每个线程调用不会创建和共享不同的实例,因此list没有并发问题

method2的参数是从method1传递的,与method1中引用的对象相同

分析上重写代码的method3后,出现线程并不安全

classthreadsafesubclassextendsthreadsafe { @ overridepublicvoidmethod3(ArrayList字符串列表) { new Thread ) (-{List.reest} ) }} 继承父类线程不安全示例

重写父类的method3后,meth

od3新开线程操作list,所以每个thread中method1线程和method2是主线程,到了method3新开线程,method2和method3是不同的线程共享list,存在线程安全问题

然而list是局部变量,每个method1的thread不共享list,为什么还会不安全?

本来猜测是不是多线程并发导致的list的add出现覆盖问题,但是局部变量的list是线程私有,不该有问题,于是我把主函数就设了一个线程,加大了method1中循环次数,发现仍然有问题,错误锁定在了method2和method3的执行顺序问题

因为在method1中的for循环,由于method3新开了线程,所以这里method2和method3没有防止指令重排,执行顺序会出问题。

在上一个局部变量示例中,没对method3进行重写,我对线程进行了输出,1,0交错打印

public void method2(ArrayList<String> list) { System.out.println((Thread.currentThread() + "1")); } public void method3(ArrayList<String> list) { System.out.println((Thread.currentThread() + "0")); }

在重写之后,我发现for循环并不是按顺序执行的,可能method3会在method2之前执行,这就导致了remove发生错误

这里我猜测是因为原本的局部变量示例method2和method3是在同一个线程中,需根据as-if-serial语义,进行了指令重排,保证method3在method2之后执行

而重写method3之后,method2和method3在不同线程,所以可能导致method3被指令重排到之前执行,出现问题

public void method3(ArrayList<String> list) { new Thread(() -> { System.out.println((Thread.currentThread() + "0")); }).start(); }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUXLLMGS-1617846620121)(https://i.loli.net/2021/04/08/1SQpaOPHfRNrG58.png)]


完整代码 package com.apollo.day02;import java.util.ArrayList;public class demo1 { public static void main(String[] args) { ThreadSafeSubClass test = new ThreadSafeSubClass(); for(int i = 0 ; i < 1; i++){ new Thread(()->{ test.method1(20000); }, "Thread" + (i + 1)).start(); } }}class ThreadSafe { public void method1(int loopNumber) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < loopNumber; i++) { method2(list); method3(list); } } public void method2(ArrayList<String> list) {// list.add("1"); System.out.println((Thread.currentThread() + "1")); } public void method3(ArrayList<String> list) {// list.remove(0); System.out.println((Thread.currentThread() + "0")); }}class ThreadSafeSubClass extends ThreadSafe{ @Override public void method3(ArrayList<String> list) { new Thread(() -> {// list.remove(0); System.out.println((Thread.currentThread() + "0")); }).start(); }} final和private的必要性

根据上一个例子,我们可以用final禁止类被继承,防止子类重写方法搞事情,也可以直接把方法设为private禁止重写。

方法访问修饰符带来的思考: 如果把上述例子2中的method2和method3 的方法修改为public 会不会导致线程安全问题;

分情况:
情况1:有其它线程调用 method2 和 method3
只修改为public修饰,此时不会出现线程安全的问题, 即使线程2调用method2/3方法, 给2/3方法传过来的list对象也是线程2调用method1方法时,传递给method2/3的list对象, 不可能是线程1调用method1方法传的对象。

情况2:在情况1 的基础上,为ThreadSafe 类添加子类,子类覆盖method2 或 method3方法,即如下所示: 从这个例子可以看出 private 或 final 提供【安全】的意义所在,请体会开闭原则中的【闭】

如果改为public, 此时子类可以重写父类的方法, 在子类中开线程来操作list对象, 此时就会出现线程安全问题:
子类和父类共享了list对象 如果改为private, 子类就不能重写父类的私有方法, 也就不会出现线程安全问题;
所以所private修饰符是可以避免线程安全问题. 所以如果不想子类, 重写父类的方法的时候, 我们可以将父类中的方法设置为private,
final修饰的方法, 此时子类就无法影响父类中的方法了!

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。