阅读《Java多线程编程核心技术》线程间通信时遇到的一个问题,求解

青峰素装 2018-09-14 10:24:46
加精
阅读《Java多线程编程核心技术》线程间通信时遇到的一个问题,求解

package com.qf.test01;

import java.util.ArrayList;
import java.util.List;

/**
* @author qf
* @create 2018-09-13 17:47
*/
public class MyList {
private List list = new ArrayList();
public void add(){
list.add("qf");
}
public int size(){
//System.out.println("MyList.size:"+list.size());
return list.size();
}
}

}

=============================
package com.qf.test01;

/**
* @author qf
* @create 2018-09-13 17:45
*/
public class ThreadA extends Thread {
private MyList myList;

public ThreadA(MyList myList) {
this.myList = myList;
}

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

=============================
package com.qf.test01;

/**
* @author qf
* @create 2018-09-13 17:45
*/
public class ThreadB extends Thread {
private MyList myList;

public ThreadB(MyList myList) {
this.myList = myList;
}

@Override
public void run() {
try {
System.out.println("b run");
while (true){
if(myList.size() == 5){
System.out.println("线程b要退出了");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

=============================
package com.qf.test01;

/**
* @author qf
* @create 2018-09-14 9:23
*/
public class Test {
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
a.setName("a");
a.start();
ThreadB b = new ThreadB(list);
b.setName("b");
b.start();
}
}

---------------------------------------------------------------------------------------------------------
上面代码运行结果:
a run
添加了1个元素
b run
添加了2个元素
添加了3个元素
添加了4个元素
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
-----------------------------------------------------------------------------------------------------------
想问下,为什么b线程中设置抛异常的语句没有执行?
...全文
4978 46 打赏 收藏 转发到动态 举报
写回复
用AI写文章
46 条回复
切换为时间正序
请发表友善的回复…
发表回复
dkwuxiang 2019-11-26
  • 打赏
  • 举报
回复
主要是cpu,主存,与线程自己的工作内存空间 首先,每个线程会从主存中拷贝一份到自己的工作内存中,然后在线程中每次访问该变量时都是访问的线程工作内存(高速缓存)中的变量副本,而不是每次都去主存中读取。 现在的问题是,那么什么时候线程会重新去主存中读取共享变量的值以及什么时候会将工作内存刷写会主存呢? 1、线程中释放锁时 (如:synchronized ) 2、线程切换时 (如:System.out.println) 3、CPU有空闲时间时(比如线程休眠时) 4、volatile 修饰变量
jiawenhe123 2019-11-26
  • 打赏
  • 举报
回复
你的list被两个线程共享使用,这就涉及到list的原子性,一致性和可见性问题。 语句
 if(myList.size() == 5)
{
                    System.out.println("线程b要退出了");
                    throw new InterruptedException();
}
是一个状态发现的过程,这个语句的执行线程必须有机会执行判断,而这个判断取决于CPU的调度,很不可靠, (其实Java.util.ConcurentHashMap的算法就有这个问题,文档中明确进行了说明,这个场景可用于弱一致的判断)。 要正确实现if(myList.size()==5),这个需要保证在执行语句线程调用的时候,其他共享数据线程不会write数据,否则结果不可预知。 如果只有两个线程,即读写线程,那么可以使用wait/notify(All),或者一些高级锁实现。 要解决这样的问题,就要引入支持并发的队列做缓冲区或者使用SynchronsQueue实现。
DazedMen 2019-11-26
  • 打赏
  • 举报
回复
他说的已经很好了,可能的造成这种问题的原因都说了下, 1.cpu执行权的问题,list数量等于5时,线程B可能并没有cpu执行权 2.线程变量副本,每个线程都回copy一份变量副本,B线程可能读的是变量副本 但是我不赞同线程A还在运行,线程B已经运行结束的说法。原因是线程A,线程B都在主线程中运行,从打印结果来说,线程A运行完成,线程B由于主线程结束,打印日志面板看不到输出信息,但线程B并没有结束
引用 14 楼 zoujiawei6 的回复:
刚开始也想到了volatile或者同步synchronized,也确实能够解决。但如果不去弄懂原理,就不符合我逛论坛的初衷。 我模糊地想起了很早以前读过的文章,但是遗忘相当严重。于是又回头翻箱倒柜地把它们找了出来,又读了一遍。鼓掌。 一、多线程是如何执行的之《我是一个线程》 二、多线程的高速缓存之《Java并发编程:volatile关键字解析》 根据以上两篇文章,我提出我的看法,不保证见解的准确性,也欢迎大牛指正: 首先,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。(原文:《Java并发编程:volatile关键字解析》) 其次,是Intel 的MESI协议。当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。(原文:《Java并发编程:volatile关键字解析》) 再者,每个线程只能在CPU上运行一段时间,到了时间就得让别人用了。(原文:《我是一个线程》) 假设有线程A、B,操作共享变量i,A线程进行i++操作,B线程只负责读取和判断。 A线程执行i++操作时,进行了以下步骤: 1、取得CPU的执行权 2、从主存中读出数据进行缓存作为高速缓存 3、使用高速缓存 4、执行i++ 5、通过MESI机制,通知其他CPU将该变量的缓存行置为无效状态 6、重复步骤2 当B线程执行时: 1、取得CPU的执行权 2、从主存中读出数据进行缓存作为高速缓存 3、使用高速缓存 4、执行判断逻辑 5、重复步骤3 在B重复步骤3时,发现缓存为无效状态,从主存中再次读取。 那么在B线程执行第3、4、5步的时候,A线程已经进行了3次计算并写入主缓存,此时B再去拿的时候i=3。 这里只是举个例子,B线程再读的时候,i可能为7或8、9,这取决于线程的执行时间;也有可能,b去读的时候恰好是5. 因此,这个问题不用加锁,不用加volatile就能解决:进行i>5判断

    if(size > 5){
        System.out.println("线程b要退出了");
        throw new InterruptedException();
    }
这种方式是最有效的。 如果只能进行 i==5判断,那么可以考虑保持读写的一致性:
class MyList {
    private int i=0;
    public synchronized void add(){
        i++;
    }
    public synchronized int size(){
        return i;
    }
}
也可以使用volatile放弃缓存,强制读取。
  • 打赏
  • 举报
回复
每次读的都是新的引用,而且多线程读取共享变量时,他只会读取最初的变量,不会实时去获取主存的实时数据;
瘦死的黑骆驼 2019-08-20
  • 打赏
  • 举报
回复
MyList list = new MyList(); ThreadB b = new ThreadB(list); b.setName("b"); b.start(); ThreadA a = new ThreadA(list); a.setName("a"); a.start(); 先运行b再运行a ,a线程启动执行太快,b可能还没运行,或者b运行的时候size已经大于5了,死循环
ITjavaman 2019-08-19
  • 打赏
  • 举报
回复
这个问题,我前阵子刚好研究了一下,我的结论是这样的 都知道java的变量实例都是放在jvm栈堆里面,这里不做具体深究,直接原因不在于此,这里的栈堆我们简单理解都是放在内存上, 电脑运行的时候,cpu会从内存里面读取数据 按照上面的例子,那么cpu会把从内存拿到的变量也就是mylist加载到cpu的高速缓存寄存器中,两个线程A,B在cpu里面都会从内存里面复制一遍mylist,当cpu运行完才会将把结果值赋值回内存 现在重新看一遍,A线程在cpu里面把元素添加完返回内存,这个时候B线程检测mylist的还是cpu里面的缓存,所以答案就出来了 扩展一:为什么加上volatile关键字就没有问题呢,那是因为带有这个关键字修饰的变量,cpu都不会加载访问自身寄存器的缓存,都是直接 范围内存上的值 扩展二:这个模式是由于jvm自身的一些优化(HotSpot目前主要是这个),可以试着把jit或者server模式关闭(相当于取消优化),再去运行也能达到你的效果 水平有限,欢迎探讨
qq_39936465 2019-08-19
  • 打赏
  • 举报
回复
引用 49 楼 xlct88 的回复:
你没看出来吗?你的两个集合不是同一个实例啊,MyList里面的代码第一行就代表着每次有个新的MyList就会有一个新的list对象出来,当然不会互相影响的
是不是同一个实例你要看主程序,明显2个进程添加的参数实例是同一个。
asafer 2019-08-19
  • 打赏
  • 举报
回复
楼主ThreadB为什么要判断size==5,线程A和线程B是两条相互独立的线程,而且计算机执行速度是非常快的,而你在A线程里面塞了10个元素,线程B里面刚好等于5的几率相当于你中500万的记录,你没有加同步处理,那么线程B必需捕捉到线程A写入5个元素的瞬间,这要的概率,你怕是找打
xlct88 2019-07-18
  • 打赏
  • 举报
回复
你没看出来吗?你的两个集合不是同一个实例啊,MyList里面的代码第一行就代表着每次有个新的MyList就会有一个新的list对象出来,当然不会互相影响的
maywin924 2019-07-18
  • 打赏
  • 举报
回复
Object[] objects = new Object[1000000];//耗时不耗cpu
while true 中添加这段代码耗时操作,cpu重新分配执行时间
  • 打赏
  • 举报
回复
A线程和B线程中的list是两个类中的成员变量,A线程的成员变量变化对B线程的成员变量毫无影响,所以B线程中的成员变量就一直是初始状态,if判断就一直不成立!
melsec88 2019-01-29
  • 打赏
  • 举报
回复
新手来看看
vvvwwwq 2019-01-26
  • 打赏
  • 举报
回复
来学习参考下
qq_44465284 2019-01-09
  • 打赏
  • 举报
回复
不如啊 你放弃吧
  • 打赏
  • 举报
回复
学习一下,自学
qq_44213691 2018-12-22
  • 打赏
  • 举报
回复
好好参照查看
qq_14823253 2018-11-02
  • 打赏
  • 举报
回复
引用 29 楼 qq_39936465 的回复:
[quote=引用 27 楼 qq_14823253 的回复:] 不知道大家有没有看清题主的问题:想问下,为什么b线程中设置抛异常的语句没有执行? 这个问题比较有意思,我上大学的时候也遇到过类似的问题:在两个线程A\B中分别打印两个数组(长度一样),发现并不是每次打印A\B都会交替打印,这个涉及到CPU时间片 一般情况下,宏观上线程并行,但实际上只有就绪状态的线程在获得CPU时间片后才会运行,题主的while (true) 可能导致其运行过程中放弃了时间片,放弃过程中myList.size()可能已经大于5了,题主可以尝试while (true) 中输出一些内容(if (myList.size() == 5){}外输出)来获取时间片,在不同系统中结果是不一样的
编程的目的是为了能在大多数机器上运行,并能达到所想要的效果,这需要避免因运行环境对程序产生的影响。题主的问题就是程序的运行环境才生影响造成的,分析运行环境对解决编程来说没有太大意义,因为我们编程不可能让程序只有在特定的运行环境下,才能达到想要的效果,为了解决这个问题所以才引入了同步概念。多进程都会或多或少的受到运行环境影响,都需要加入同步。[/quote] 题主运行的可能是反例,旨在让读者明白线程同步的重要,可是这个程序可能并没有很好的体现反例,并不是要求大家讲解如何同步
qq_39936465 2018-11-01
  • 打赏
  • 举报
回复
引用 27 楼 qq_14823253 的回复:
不知道大家有没有看清题主的问题:想问下,为什么b线程中设置抛异常的语句没有执行? 这个问题比较有意思,我上大学的时候也遇到过类似的问题:在两个线程A\B中分别打印两个数组(长度一样),发现并不是每次打印A\B都会交替打印,这个涉及到CPU时间片 一般情况下,宏观上线程并行,但实际上只有就绪状态的线程在获得CPU时间片后才会运行,题主的while (true) 可能导致其运行过程中放弃了时间片,放弃过程中myList.size()可能已经大于5了,题主可以尝试while (true) 中输出一些内容(if (myList.size() == 5){}外输出)来获取时间片,在不同系统中结果是不一样的
编程的目的是为了能在大多数机器上运行,并能达到所想要的效果,这需要避免因运行环境对程序产生的影响。题主的问题就是程序的运行环境才生影响造成的,分析运行环境对解决编程来说没有太大意义,因为我们编程不可能让程序只有在特定的运行环境下,才能达到想要的效果,为了解决这个问题所以才引入了同步概念。多进程都会或多或少的受到运行环境影响,都需要加入同步。
qq_14823253 2018-11-01
  • 打赏
  • 举报
回复
引用 27 楼 qq_14823253 的回复:
不知道大家有没有看清题主的问题:想问下,为什么b线程中设置抛异常的语句没有执行? 这个问题比较有意思,我上大学的时候也遇到过类似的问题:在两个线程A\B中分别打印两个数组(长度一样),发现并不是每次打印A\B都会交替打印,这个涉及到CPU时间片 一般情况下,宏观上线程并行,但实际上只有就绪状态的线程在获得CPU时间片后才会运行,题主的while (true) 可能导致其运行过程中放弃了时间片,放弃过程中myList.size()可能已经大于5了,题主可以尝试while (true) 中输出一些内容(if (myList.size() == 5){}外输出)来获取时间片,在不同系统中结果是不一样的
说的可能不准,while (true) 的时间片用的比较快,哈哈哈
qq_14823253 2018-11-01
  • 打赏
  • 举报
回复
不知道大家有没有看清题主的问题:想问下,为什么b线程中设置抛异常的语句没有执行? 这个问题比较有意思,我上大学的时候也遇到过类似的问题:在两个线程A\B中分别打印两个数组(长度一样),发现并不是每次打印A\B都会交替打印,这个涉及到CPU时间片 一般情况下,宏观上线程并行,但实际上只有就绪状态的线程在获得CPU时间片后才会运行,题主的while (true) 可能导致其运行过程中放弃了时间片,放弃过程中myList.size()可能已经大于5了,题主可以尝试while (true) 中输出一些内容(if (myList.size() == 5){}外输出)来获取时间片,在不同系统中结果是不一样的
加载更多回复(26)

62,614

社区成员

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

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