java多线程初探(二)构建线程安全类

上一篇记录了多线程的简单使用,这里接着记录。

一、可见性

可见性:读操作和写操作在两个线程时,不能保证读操作可以实时的看到其他线程写入的值,如果能保证则说拥有可见性

保证可见性方法:

1,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值(而不是高速缓存中)。

2,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。

对可见性,笔者测试代码如下:

package gcc.thread.test;

/**
 * Created by gcc on 2018/3/29.
 */
public class NoVisibility {

    private static boolean readFlag;//默认false
    private static int count=0;

    public static void main(String[] args) {
        ReadThread read1 = new ReadThread();
        Thread thread = new Thread(read1);
        Thread thread2 = new Thread(read1);
        Thread thread3 = new Thread(read1);
        thread.start();
        thread2.start();
        thread3.start();
        count=50;
        readFlag=true;
        System.out.println("main--count:"+count);
    }

    private static class ReadThread implements Runnable{
        public void run(){
            System.out.println("ReadThread1--count:"+Thread.currentThread().getName()+"--"+count);
            while (!readFlag){
                Thread.yield();//使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。此线程会把自己CPU执行的时间让掉,让自己或者其它的线程运行(重新竞争)
            }
            System.out.println("ReadThread2--count:"+Thread.currentThread().getName()+"--"+count);
        }
    }
}

结果如下:

ReadThread1--count:Thread-0--0
ReadThread1--count:Thread-2--50
ReadThread2--count:Thread-2--50
main--count:50
ReadThread1--count:Thread-1--0
ReadThread2--count:Thread-1--50
ReadThread2--count:Thread-0--50

这里有两类线程,主线程和读线程,主线程开启了三个读线程,读线程获取共享数据count,可以看到主线程应该先写入的count=50,有可能在读线程里没有被读到,结果就为0。这就引发了可见和不可见的问题。

二、有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。举个简单的例子,看下面这段代码:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  

flag = true;          //语句2

从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。

指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令2必须用到指令1的结果,那么处理器会保证指令1会在指令2之前执行。

多线程时重排序:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}

doSomethingwithconfig(context);

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此时线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被线程1初始化,就会导致程序出错。

注:要想并发程序正确地执行,必须保证原子性、可见性以及有序性。只要有一个没有被保证,就可能导致程序运行不正确

三、线程封闭

这里记录下线程封闭的常用方法ThreadLocal。

线程封闭就是将共享的可变数据,封闭在一个线程内,即在单线程内访问数据,这样不用同步也是安全的。

ThreadLocal,线程本地变量,是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本,通过ThreadLocal可以将对象的可见范围限制在同一个线程内。

ThreadLocal可以理解为将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。

例如:

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值