欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > Java多线程——对象的共享

Java多线程——对象的共享

2025/2/2 2:55:39 来源:https://blog.csdn.net/qq_35258036/article/details/145395007  浏览:    关键词:Java多线程——对象的共享

可见性

一个线程修改了对象状态后,其他线程能够看到发生的状态变化

public class NoVisibility {private static boolean ready;private static int number;private static class ReaderThread extends Thread {@Overridepublic void run() {while (!ready)Thread.yield();System.out.println(number);}}public static void main(String[] args) {new ReaderThread().start();number = 42;ready = true;}
}

如上,子线程判断ready和输出number,主线程修改后,可能出现

  • 子线程一直循环(未看到ready的值)
  • number输出0(子线程可能先看到写入ready后写入number)

数据在多个线程共享时,就应该使用同步确保可见性

失效数据

对于如下类,可能出现一个线程在set过程中,另外一个线程调用get获取失效数据

class MutableInteger {private int value;public  int getValue() {return value;}public void setValue(int value) {this.value = value;}
}

对于可共享变量,应需要同时对get和set同步

class MutableInteger {@GuardedBy("this")private int value;public synchronized int getValue() {return value;}public synchronized void setValue(int value) {this.value = value;}
}

加锁不仅仅局限于互斥,还包含内存可见性,为了所有线程都能获得共享数据的最新值,其get和set都必须在同一个锁上同步

非原子的64位操作

非volatile类型的64位数值变量(double和long)读写操作分为两个32位操作,多线程下可能出现失效数据,应该用volatile或锁保护

volatile变量

  • 用于确保变量的更新操作通知到其他线程
  • 对该变量的操作不会重排序,不会被缓存在寄存器,
  • 可以理解对其get和set都声明为synchronized,但实际实现不会加锁也不会导致线程阻塞,更为轻量级

经典用法:检测某个状态标志位以判断是否退出循环,此时比用锁更简便

class A {volatile boolean isSleep;public void check() {while (!isSleep) {//......}}
}

加锁和原子变量能确保可见性和原子性,volatile只能确保可见性

使用volatile变量的条件:

  • 对变量的写入操作不依赖变量的当前值,或者能确保只有单个
    线程更新变量的值
  • 该变量不会与其他状态变量一起纳入不变性条件中
  • 在访问变量时不需要加锁

发布与逸出

发布指的是对象在当前作用域之外的代码中使用,当某个不应该发布的对象被发布时称为逸出,常见的逸出有

  • 对象引用保持到公有静态变量,而不是不可变对象
  • 非私有方法返回一个对象引用,而不是其不可变/克隆对象
  • 外部内部类导致外部类逸出

第三种常见形式是this在构造函数中逸出,如下构造函数还未结束,线程就持有了对象实例,这会导致未知状态

class A {public A() {new Thread(new Runnable() {@Overridepublic void run() {A.this.test();}}).start();//...}private void test() {System.out.println(getClass());}
}

只有构造函数返回时,对象才处于可预测和一致的状态,构造函数创建线程不应立即启动,如下等构造完后再启动线程

class A {private void test() {System.out.println(getClass());}private final Runnable mRunnable;private A() {mRunnable = new Runnable() {@Overridepublic void run() {A.this.test();}};}public static A getInstance() {A a = new A();new Thread(a.mRunnable).start();return a;}
}

线程封闭

仅在单线程内访问数据,就不需要同步,称为线程封闭

栈封闭

局部变量封闭在执行线程的栈中,其他线程无法访问这个栈

class A {public int test() {int num = 0;//...Set<String> set = new HashSet<>();return num;}
}

如上,基本数据类型num无引用,可封闭在线程内,若是返回set则封闭性被破坏导致对象逸出

ThreadLocal

使线程中的某个值与保存值的对象关联起来,每个使用该变量的线程都存有一个独立的副本,ThreadLocal<T>相当于Map<Thread, T>

当将单线程应用移植到多线程环境中,可以将共享的全局变量转换为ThreadLocal对象,以维持线程安全性

不变性

不可变对象一定是线程安全的,不可变对象需满足

  • 对象创建后其状态不能修改
  • 对象所有域都是final
  • 对象创建期间,this引用没有逸出

可通过volatile和不可变对象确保线程安全性和可见性

public class A {@GuardedBy("this")private long count;@GuardedBy("this")private boolean isOdd;public long getCount() {return count;}public void test() {//....synchronized (this) {count++;isOdd = getCount() % 2 == 0;}}
}

对于如上加锁代码,提取不可变类,用volatile确保数据更新后的可见性,每次更新创建新的不可变对象

public class A {private volatile Counter mCounter = new Counter(0);private long currentCount = 0;public void test() {//....currentCount++;if (currentCount != mCounter.getCount()) {mCounter = new Counter(currentCount);}}
}class Counter {private final long count;private final boolean isOdd;Counter(long count) {this.count = count;this.isOdd = count % 2 == 0;}public long getCount() {return count;}
}

安全发布

要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见

常用模式

一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用,如pubic static A a = new A()
  • 将对象的引用保存到volatile类型的域或者AtomicReferance对象
  • 将对象的引用保存到某个正确构造对象的final类型域中
  • 将对象的引用保存到一个由锁保护的域中,如系统自带的线程安全库

线程安全库中的容器类提供了以下的安全发布保证:

  • 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
  • 通过将某个元素放入 Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程
  • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程

事实不可变对象

如果对象从技术上来看是可变的,但其状态在发布后不会再改变,如下将可变的Date放入Map就不会改变,访问时不需要额外的同步

public Map<String, Date> lastLogin = Collections.synchronizedMap(new HashMap<>());

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com