您当前的位置: 首页 >  安全

变量和类的线程安全

发布时间:2021-09-15 07:33:28 ,浏览量:7

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

如果它们没有共享,则线程安全 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 如果只有读操作,则线程安全 如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全?

局部变量是线程安全的 但局部变量引用的对象则未必 如果该对象没有逃离方法的作用访问,它是线程安全的 如果该对象逃离方法的作用范围,需要考虑线程安全 (经过逃逸分析)

局部变量线程安全分析
package com.dongguo.sync; /**
 * @author Dongguo
 * @date 2021/9/11 0011-14:29
 * @description:
 */ public class SafeDemo { public static void test1() { int i = 10; i++; } public static void main(String[] args) { } } 

每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享

字节码文件

public static void test1(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=0 0: bipush 10 //读取10 2: istore_0 //赋值给i 3: iinc 0, 1 //执行i++ 6: return LineNumberTable: line 10: 0 line 11: 3 line 12: 6 LocalVariableTable: Start Length Slot Name Signature 3 4 0 i I 

如图

image-20210911143736292

局部变量的引用稍有不同

成员变量线程安全分析
package com.dongguo.sync; import java.util.ArrayList; /**
 * @author Dongguo
 * @date 2021/9/11 0011-14:45
 * @description:
 */ public class UnSafeDemo { static final int THREAD_NUMBER = 2; static final int LOOP_NUMBER = 200; public static void main(String[] args) { ThreadUnsafe test = new ThreadUnsafe(); for (int i = 1; i <= THREAD_NUMBER; i++) { new Thread(() -> { test.method1(LOOP_NUMBER); }, "t" + i).start(); } } } class ThreadUnsafe { ArrayList<String> list = new ArrayList<>(); public void method1(int loopNumber) { for (int i = 0; i < loopNumber; i++) { // { 临界区, 会产生竞态条件 method2(); method3(); // } 临界区 } } private void method2() { list.add("1"); } private void method3() { list.remove(0); } } 运行结果 Exception in thread "t2" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:659) at java.util.ArrayList.remove(ArrayList.java:498) at com.dongguo.sync.ThreadUnsafe.method3(UnSafeDemo.java:41) at com.dongguo.sync.ThreadUnsafe.method1(UnSafeDemo.java:31) at com.dongguo.sync.UnSafeDemo.lambda$main$0(UnSafeDemo.java:18) at java.lang.Thread.run(Thread.java:748) 

分析: 无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量 method3 与 method2 分析相同

image-20210911144746849

将 list 修改为局部变量

package com.dongguo.sync; import java.util.ArrayList; /**
 * @author Dongguo
 * @date 2021/9/11 0011-14:45
 * @description:
 */ public class UnSafeDemo { static final int THREAD_NUMBER = 2; static final int LOOP_NUMBER = 200; public static void main(String[] args) { ThreadUnsafe test = new ThreadUnsafe(); for (int i = 1; i <= THREAD_NUMBER; i++) { new Thread(() -> { test.method1(LOOP_NUMBER); }, "t" + i).start(); } } } class ThreadUnsafe { public void method1(int loopNumber) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < loopNumber; i++) { // { 临界区, 会产生竞态条件 method2(list); method3(list); // } 临界区 } } private void method2(ArrayList<String> list) { list.add("1"); } private void method3(ArrayList<String> list) { list.remove(0); } } 

那么就不会有上述问题了

分析: list 是局部变量,每个线程调用时会创建其不同实例,没有共享 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象 method3 的参数分析与 method2 相同

image-20210911144952084

常见线程安全类

String Integer StringBuffer Random Vector Hashtable java.util.concurrent 包下的类 这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为

Hashtable table = new Hashtable(); new Thread(() -> { table.put("key", "value1"); }).start(); new Thread(() -> { table.put("key", "value2"); }).start(); 

它们的每个方法是原子的

即Hashtable的put方法时线程安全的 ,使用synchronized同步方法

public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } 
线程安全类方法的组合

分析下面代码是否线程安全?

package com.dongguo.sync; import java.util.Hashtable; /**
 * @author Dongguo
 * @date 2021/9/11 0011-15:05
 * @description:
 */ public class UnSafeDemo2 { public static void main(String[] args) { } public static void putValue(String value) { Hashtable table = new Hashtable(); // 线程1,线程2 if (table.get("key") == null) { table.put("key", value); } } } 

hashtable的get()、put()都是线程安全的

但是在使用get()和put()之间,有可能其他的线程也会执行get()、put()

image-20210911150744484

不可变类线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的 有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安 全的呢?

以substring 为例

public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); } 

(beginIndex == 0) ? this : new String(value, beginIndex, subLen);

当beginIndex =0,返回这个String对象如果beginIndex !=0创建一个新的String对象

Arrays.copyOfRange(value, offset, offset+count); 将复制的char[]赋值给这个新的String对象

关注
打赏
1688896170
查看更多评论

暂无认证

  • 7浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0507s