关于java:Java-多线程不平安场景举例

7次阅读

共计 2403 个字符,预计需要花费 7 分钟才能阅读完成。

Java 多线程不安全场景举例

** 线程不安全的例子
对象引用的逸出
藏匿的逸出
多个线程操作同一非线程安全对象
使用不同的锁锁定同一对象
同时操作多个关联的线程安全对象
总结
线程不安全的例子 **

1. 对象引用的逸出

即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的

public void Calculator{
    private ImmutableValue currentValue = null;
    public ImmutableValue getValue(){return currentValue;}
    public void setValue(ImmutableValue newValue){this.currentValue = newValue;}
    public void add(int newValue){this.currentValue = this.currentValue.add(newValue);
    }
}

复制代码
Calculator 类持有一个指向 ImmutableValue 实例的引用。通过 setValue() 方法和 add()方法可能会改变这个引用。因此,即使 Calculator 类外部使用了一个不可变对象,但 Calculator 类本身还是可变的,因此 Calculator 类不是线程安全的。换句话说:ImmutableValue 类是线程安全的,但使用它的类不是。

2. 藏匿的逸出

public class ThisEscape {public ThisEscape(EventSource source) {
        // 隐式的使 this 引用逸出
        source.registerListener(new EventListener() {public void onEvent(Event e) {doSomething(e);
            }
        });
    }
    void doSomething(Event e) {//do sth.}
    interface EventSource {void registerListener(EventListener e);
    }
    interface EventListener {void onEvent(Event e);
    }
    interface Event {}}

复制代码
当线程 1 和线程 2 同时拜访 ThisEscape 构造方法,这时线程 A 初始化还未实现,此时因为 this 逸出,导致 this 在 1 和 2 中都具备可见性,线程 2 就可能通过 this 拜访 doSomething(e) 方法,导致修改 ThisEscape 的属性。

3. 多个线程操作同一非线程安全对象

public class NotThreadSafe{StringBuilder builder = new StringBuilder();
    
    public add(String text){this.builder.append(text);
    }    
}
// 调用方
NotThreadSafe sharedInstance = new NotThreadSafe();
new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();
public class MyRunnable implements Runnable{
  NotThreadSafe instance = null;
  
  public MyRunnable(NotThreadSafe instance){this.instance = instance;}
  public void run(){this.instance.add("some text");
  }
}

复制代码
线程 1 和 线程 2 同时操作了同一个对象。并且这个对象并没有做线程安全的处理。

4. 在同一对象上使用不同的锁
public class ListHelper {

public List list = 
    Collections.synchronizedList(new ArrayList());
    
// 与 List 使用的不是同一把锁
public synchronized boolean putIfAbsent(E e) {boolean absent = !list.contains(x);
    if (absent) {list.add(x);
    }
    return absent;
}

}
复制代码
诚然从代码上看像是线程安全代码,然而因为使用的锁与 list 的锁不是同一把锁,因此在多线程下仍然会出问题。

5. 同时操作多个关联的同步对象
public class NumberRange{

private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);

public void setLower(int i) {
    // 不安全的先查看,后执行
    if (i > upper.get()) {throw new Exception("");
    }
    
    lower.set(i);
}

public void setUpper(int i) {if (i < lower.get()) {throw new Exception("");
    }
    
    upper.set(i);
}

public boolean isInRange(int i) {return (i >= lower.get() && i <= upper.get());
}   

}
复制代码
诚然 lower 和 upper 都是线程安全的对象。然而它们之间并不是独立的。存在当线程 1 调用 setLower(5), 线程 2 调用 setUpper(4),那么有可能产生的后果是两个线程均判断通过的「时序问题」,而后出现 lower 大于 upper 的情况。

总结

以上就是线程不安全的代码常见场景举例,在实际的开发中,很有可能是较为费解的,然而场景基本都是下面的一种或多种场景独特出现。因此实际排查线程安全问题的过程中,查看代码中有无上述的场景即可。

正文完
 0