在多线程访问list中出现的一个问题。

从零单排 2018-05-05 04:28:30
我有两个线程,A线程负责把list.add()执行10次,B线程在list.size()超过5的时候抛异常退出。
下面是代码。
package com.lock;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Create by @author Henry on 2018/5/4
*/
public class NoWait {
static volatile ArrayList list = new ArrayList();
public static void main(String[] args) throws InterruptedException {

ThreadA a = new ThreadA(list);
a.setName("A");
a.start();

ThreadB b = new ThreadB(list);
b.setName("B");
b.start();

Thread.sleep(10000);
System.out.println("A is " + a.isAlive());
System.out.println("B is " + b.isAlive());
}
}

class ThreadA extends Thread{
private ArrayList list;
public ThreadA(ArrayList list){
this.list = list;
}

@Override
public void run(){
try{
for (int i = 0; i < 10; i++) {
list.add(i);
System.out.println("=====================================添加了 "+(i+1)+" 个元素");
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("A finished");
}
}

class ThreadB extends Thread{
String s;
private ArrayList list;
public ThreadB(ArrayList list){
this.list = list;
}

@Override
public void run(){
try{
while(true){
//s += list.size();
//System.out.println(list.size());
if(list.size() > 5){
System.out.println(">5 now, B gonne quit");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


这是结果
这里我的理解是,B线程一只访问的是B线程私有内存上的list,因此list.size()一直没变过。所以不会退出。

但是当我去掉这两行中随便一行的注释时,就会出现正常退出,既当A线程的list.add()执行了5次以上时,B线程随时可能退出。我想请问一下,这两行代码都是怎么起作用的。
...全文
1427 5 打赏 收藏 转发到动态 举报
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
多线程访问同一变量要加锁是不容置疑的,在这不作过多解释 这段代码的重点在于你加了volatile关键字,加入volatile关键字时,会多出一个lock前缀指令 会帮你屏障内存,内存屏障会提供3个功能: 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 2)它会强制将对缓存的修改操作立即写入主存; 3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
dgqjava 2018-05-07
  • 打赏
  • 举报
回复
因为你的代码没有进行正确的同步 导致你的变量的可见性无法得到保证 arraylist的size方法是返回一个size成员变量的值 而这个变量的值在不同线程里无法保证被观察到同一个最新值 你注释的那两句代码打开任意一句 就有可能导致你的线程B能看到最新值 因为两行代码都有对于共享内存的操作 比如第一行代码的字符串连接会导致new StringBuilder 第二行代码会有一个String.valueOf操作把int转为字符串 这种对共享内存的操作(同时第二行代码的println里有加锁解锁操作也会造成很大影响) 可能会对可见性产生干扰导致你的线程B"很走运"的看到了size的最新值 不过归根结底这个程序并不是线程安全的
  • 打赏
  • 举报
回复
首先,这个线程程序并没有加同步锁,其次, 线程B的执行取决于CPU的调度, 这个list.size()是会变的, 因为你是通过有参构造传进去的list, 你注不注释这两行对下面的if判断是没有影响的. 主要是CPU分配时间片的问题.程序由上向下执行, A线程的优先级大于B线程的.
stevenguanhq 2018-05-06
  • 打赏
  • 举报
回复
Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list: List list = Collections.synchronizedList(new ArrayList(...));
oyljerry 2018-05-05
  • 打赏
  • 举报
回复
两个线程是同一个list引用。访问的时候要加锁

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧