线程共享内存及可能存在的问题

线程共享内存及可能存在的问题

每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈,但线程之间可以共享内存,他们可以访问和操作相同的对象。

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)当多条执行流执行相同的程序代码时,每条执行流都有单独的栈,方法中的参数和局部变量都有自己的一份。

当多条执行流可以操作相同的变量时,可能会出现一些意料之外的结果,包括竞态条件和内存可见性问题。

1.竞态条件

所谓竞态条件是指多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,可能正确也可能不正确。

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关键字;
使用显示锁;
使用原子变量。

2.内存可见性

多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就可以看到,甚至永远也看不到。

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 关键字或显示同步锁。

参考文章

java编程的逻辑基础(马俊昌)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛涛之海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值