Java 将对象分配给在同步块外部定义的字段-是否线程安全?
这个java代码的线程安全性有什么问题吗?线程1-10通过sample.add()添加数字,线程11-20调用removeAndDouble()并将结果打印到stdout。我记得有人说过,在同步块之外使用removeAndDouble()以与我在removeAndDouble()中相同的方式分配项可能不是线程安全的。编译器可能会对指令进行优化,以使它们不按顺序出现。这里是这样吗?我的removeAndDouble()方法不安全吗 从并发性的角度来看,这段代码还有其他错误吗?我试图通过java(1.6以上)更好地理解并发性和内存模型Java 将对象分配给在同步块外部定义的字段-是否线程安全?,java,multithreading,concurrency,thread-safety,Java,Multithreading,Concurrency,Thread Safety,这个java代码的线程安全性有什么问题吗?线程1-10通过sample.add()添加数字,线程11-20调用removeAndDouble()并将结果打印到stdout。我记得有人说过,在同步块之外使用removeAndDouble()以与我在removeAndDouble()中相同的方式分配项可能不是线程安全的。编译器可能会对指令进行优化,以使它们不按顺序出现。这里是这样吗?我的removeAndDouble()方法不安全吗 从并发性的角度来看,这段代码还有其他错误吗?我试图通过java(1
import java.util.*;
导入java.util.concurrent.*;
公共类样本{
私有最终列表=新的ArrayList();
公共空添加(整数o){
已同步(列表){
列表。添加(o);
list.notify();
}
}
公共无效waitUntilEmpty(){
已同步(列表){
而(!list.isEmpty()){
试试{
列表。等待(10000);
}catch(InterruptedException ex){}
}
}
}
公共void waitUntilNotEmpty(){
已同步(列表){
while(list.isEmpty()){
试试{
列表。等待(10000);
}catch(InterruptedException ex){}
}
}
}
公共整数removeAndDouble(){
//在同步块外声明的项
整数项;
已同步(列表){
waitUntilNotEmpty();
项目=列表。删除(0);
}
//是否会从列表中删除(0)?
返回整数.valueOf(item.intValue()*2);
}
公共静态void main(字符串[]args){
最终样品=新样品();
对于(int i=0;i<10;i++){
线程t=新线程(){
公开募捐{
while(true){
System.out.println(getName()+”找到:“+sample.removeAndDouble());
}
}
};
t、 集合名(“消费者-”+i);
t、 setDaemon(true);
t、 start();
}
最终ExecutorService生产者=Executors.newFixedThreadPool(10);
对于(int i=0;i<10;i++){
最终整数j=i*10000;
线程t=新线程(){
公开募捐{
对于(int c=0;c<1000;c++){
样品.添加(j+c);
}
}
};
t、 集合名(“生产者-”+i);
t、 setDaemon(false);
生产者。执行(t);
}
生产者。关闭();
试一试{
生产者。等待终止(600,时间单位。秒);
}捕捉(中断异常e){
e、 printStackTrace();
}
sample.waitUntilEmpty();
System.out.println(“完成”);
}
}
我觉得它是线程安全的。这是我的理由
每次访问列表
时,都会同步执行该操作。这太棒了。即使您在项目
中拉出列表
的一部分,该项目
也不会被多个线程访问
只要您在同步时只访问
列表
,就应该很好(在您当前的设计中)。您的代码实际上是线程安全的。这背后的原因有两部分
首先是相互排斥。您的同步正确地确保了一次只有一个线程将修改集合
第二个问题与您对编译器重新排序的担忧有关。您担心编译实际上会重新安排分配顺序,而在这种情况下它不会是线程安全的。在这种情况下你不必担心。在列表上同步将创建“发生在之前”关系。所有从列表中删除的操作都发生在写入
整数项之前。这会告诉编译器它无法在该方法中对写入项重新排序。您的同步状态良好,不会导致任何无序执行问题
然而,我确实注意到一些问题
首先,如果在列表之后添加list.notifyAll()
,则您的waitUntilEmpty
方法将更加及时。在removeanddough
中删除(0)
。这将消除等待(10000)
过程中长达10秒的延迟
其次,您的列表.notify
在add(Integer)
中应该是notifyAll
,因为notify
只唤醒一个线程,它可能唤醒在waitUntilEmpty
中等待的线程,而不是waitUntilNotEmpty
第三,以上这些都不是应用程序活性的终点,因为您使用了有界等待,但如果您进行了上述两项更改,您的应用程序将具有更好的线程性能(waitUntilEmpty
),并且有界等待将变得不必要,并且可以变成普通的无参数等待。您的代码是线程安全的,但不是并行的(如并行的)。由于所有内容都是在单个互斥锁下访问的,因此您正在序列化所有访问,实际上,对结构的访问是单线程的
如果您需要生产代码中描述的功能,那么java.util.concurrent
包已经提供了一个BlockingQueue
,它具有(固定大小)数组和(可扩展的)基于链表的实现。至少对于实现思想来说,这些都是非常有趣的。在notifyAll
上的调用很好。我不是序列化所有访问,只是访问队列。生产者和消费者是平行的(在一个典型的场景中会消耗更多的处理时间)
import java.util.*;
import java.util.concurrent.*;
public class Sample {
private final List<Integer> list = new ArrayList<Integer>();
public void add(Integer o) {
synchronized (list) {
list.add(o);
list.notify();
}
}
public void waitUntilEmpty() {
synchronized (list) {
while (!list.isEmpty()) {
try {
list.wait(10000);
} catch (InterruptedException ex) { }
}
}
}
public void waitUntilNotEmpty() {
synchronized (list) {
while (list.isEmpty()) {
try {
list.wait(10000);
} catch (InterruptedException ex) { }
}
}
}
public Integer removeAndDouble() {
// item declared outside synchronized block
Integer item;
synchronized (list) {
waitUntilNotEmpty();
item = list.remove(0);
}
// Would this ever be anything but that from list.remove(0)?
return Integer.valueOf(item.intValue() * 2);
}
public static void main(String[] args) {
final Sample sample = new Sample();
for (int i = 0; i < 10; i++) {
Thread t = new Thread() {
public void run() {
while (true) {
System.out.println(getName()+" Found: " + sample.removeAndDouble());
}
}
};
t.setName("Consumer-"+i);
t.setDaemon(true);
t.start();
}
final ExecutorService producers = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int j = i * 10000;
Thread t = new Thread() {
public void run() {
for (int c = 0; c < 1000; c++) {
sample.add(j + c);
}
}
};
t.setName("Producer-"+i);
t.setDaemon(false);
producers.execute(t);
}
producers.shutdown();
try {
producers.awaitTermination(600, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
sample.waitUntilEmpty();
System.out.println("Done.");
}
}