赞
踩
每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈,但线程之间可以共享内存,他们可以访问和操作相同的对象。
package com.claa.javabasic.Thread; import java.util.ArrayList; import java.util.List; /** * @Author: claa * @Date: 2020/03/29 21:21 * @Description: */ public class ShareMemoryDemo { private static int shared = 0; private static void incrShared() { shared ++; } static class ChildThread extends Thread { List<String> list; public ChildThread(List<String> list) { this.list=list; } @Override public void run() { incrShared(); list.add(Thread.currentThread().getName()); } } public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); Thread t1 = new ChildThread(list); Thread t2 = new ChildThread(list); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(shared); System.out.println(list); } }
输出结果
2
[Thread-0, Thread-1]
通过这个例子,想强调说明执行流、内存、程序代码之间的关系。
(1)有两条执行流,一条执行main方法,另外两条执行ChildThread的run方法
(2)不同执行流可以访问和操作相同的变量,如本例中的shared和list变量。
(3)不同的执行流可以执行相同的程序代码,如本例中incrShared方法,ChildThread的run方法,被两条ChildThread执行流执行,incrShared方法是在外部定义的,但被ChildThread 的执行流执行。在分析代码执行过程时,理解代码在哪个线程执行时很重要的。
(4)当多条执行流执行相同的程序代码时,每条执行流都有单独的栈,方法中的参数和局部变量都有自己的一份。
当多条执行流可以操作相同的变量时,可能会出现一些意料之外的结果,包括竞态条件和内存可见性问题。
所谓竞态条件是指多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,可能正确也可能不正确。
package com.claa.javabasic.Thread; /** * @Author: claa * @Date: 2020/03/29 22:10 * @Description: */ public class CountThread extends Thread{ private static int counter = 0; @Override public void run() { for(int i=0; i< 1000;i++) { counter++; } } public static void main(String[] args) throws InterruptedException { int num =1000; Thread[] threads = new Thread[num]; for(int i = 0; i < num; i++) { threads[i] = new CountThread(); threads[i].start(); } for(int i =0; i < num; i++) { threads[i].join(); } System.out.println(counter); } }
期望的结是100万,但实际执行,发现每次输出的结果都不一样,一般都不是100万,经常99万,为什么呢?
因为counter++ 这个操作不是原子操作,它分为三步:
(1)取counter的当前值;
(2)在当前值基础上加1;
(3)将新值重新赋值给counter。
两个线程可能同时执行第一步,取到了相同的counter值,比如都取到了100,第一个线程执行完后counter变为101,而第二个线程执行完后还101,最终结果就是与期望不符。
怎么解决呢?
使用synchronized关键字;
使用显示锁;
使用原子变量。
多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就可以看到,甚至永远也看不到。
package com.claa.javabasic.Thread; /** * @Author: claa * @Date: 2020/03/29 22:35 * @Description:内存可见性 */ public class VisibilityDemo { private static boolean shutdown = false; static class HelloThread extends Thread{ @Override public void run() { while(! shutdown) { // do nothing } System.out.println("exit hello"); } } public static void main(String[] args) throws InterruptedException{ new HelloThread().start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } shutdown = true; System.out.println("exit main"); } }
输出结果
exit main
期望的结果是两个线程都退出,但实际执行时,很可能发现HelloThread 永远都不退出,也就是说在HelloThread执行流看来shutdown永远为false,即使main线程已经更改为true。
这是因为内存可见性问题。在计算机系统中,除了内存,数据还会被缓冲存在cpu的寄存器以及各级缓冲中,当访问一个变量时,可能直接从寄存器或cpu中获取,不一定到内存中去取,当修改为一个变量时,也可能是先写到缓冲中,稍后才同步更新到内存中。在单线程的程序中,这一般不是问题,但在多线程的程序中,尤其是在多cpu 的情况下,这就是严重的问题。一个线程对内存的修改,另一个线程看不到,一是修改没有及时同步到内存中,二是另外一个线程根本没有从内存读。
怎么解决呢?
使用volatile 关键字
使用synchronzied 关键字或显示同步锁。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。