《java并发编程实践》1-3章要点总结

名词解析

1. 上下文切换

调度程序临时挂起当前运行的线程时,另一个线程开始运行。也就是线程切换,在应用程序中很常见,带来的系统开销巨大。

2. 原子性

程序可以作为单独的、不可分割的一次操作执行,称为原子操作,具有原子性,例如赋值操作:a = 3,而看似紧凑的操作有的并不是原子操作:例如 a++,可看成“读-改-写”三步,非原子操作可能遗失更新。java.util.concurrent.atomic包下的AtomicLong等类可以保证变量操作的原子性。

3. 可见性

当前线程对变量的修改操作对其他线程可见,具有可见性的操作读取时不会产生并发问题,但是写入时要处理并发。

可见性问题涉及到变量在内存中的存取情况,长话短说。内存分为主内存以及各自变量占有的内存(副本),以a++为例,首先变量会从主内存中读取a的初值到副本中,然后对副本中的值加1操作,再将结果写入主内存中。这个过程如果有两个线程介入的话,很显然容易产生并发问题。

但是如果变量可见,在线程访问该变量时,操作系统会增加storeload指令,在读取的时候store指令强制从主存中重新读取,写入时load会强制写入主存,保证了任何时候线程看到的变量值都是一致的。注意,在读写这段时间内,如果有其它线程执行写操作,依然会有并发问题,可见性只是保证变量的一致性。

4. 竞争条件

当计算的正确性依赖于运行时的相关时序或者多线程的交替时(“幸运”时序), 会产生竞争条件。最常见的竞争条件是:“先检查再运行”,也即if等判断操作。检查到运行的这段时间,变量的值如果被另一个线程修改,就会导致非期望的运行结果。例如:“惰性初始化”

private Test mTest;

public static Test getInstance(){
    if(mTest == null)
        return new Test();
}
5. 重进入

避免死锁的一种手段。对锁的请求是基于“每线程”而不是“每调用”,意味着同一线程可以多次请求同一个锁。每个锁关联了一个请求计数和一个占有它的线程,该线程每重进入该代码块,计数加1,退出则减1,为0时释放锁。如果没有重进入,以下例子会造成死锁:

public class Widget {
    public synchronized void doSomething(){
        ......
    }
}

public class LoggingWidget extends Widget {
    public synchronized void doSomething(){
        ...
        super.doSomthing();
    }
}
6. 重排序

在单个线程中,为了使java虚拟机能够充分利用CPU等资源,只要结果与原来相比没有产生别的影响,即使对其他线程有影响,虚拟机也会对编写的代码进行重排序,

7. Volatile关键字

用来修饰变量,变量被Volitale关键字修饰时,编译器与运行时会监视这个变量,它具有以下几个特点:

  • 对它的操作不会与其他的内存操作一起被重新排序,这意味着使用Volatile会屏蔽虚拟机的一些优化操作,降低些许性能。
  • Volatile修饰的变量不会缓存在寄存器或者其他对处理器隐蔽的地方,所以可以保证变量的可见性
  • 不能保证变量的原子性,这是与锁机制的不同点
  • 多用于当做标识完成、中断、状态的标记使用
8. 逸出

首先“发布”的意思是一个对象能够被当前范围之外的代码所引用。一个对象在还没准备好的时候就被发布出去,称为逸出

public class ThisEscape{
    public ThisEscape(EventSource source){
        source.registerListener(new EventListener(){
            public void onEvent(Event e){
                doSomething(e);
            }
        });
    }

    private void doSomething(Event e){
    }
}

上面的程序在构造方法中隐式持有了外部对象的引用,然而对象还没创建完全,对象的状态不一定是可预知的、稳定的,造成了逸出。书中建议:不要让this引用在构造期间逸出。

此外,比较常见的一种错误是在构造方法中去创建并开启一个线程,无论是显式还是隐式的,this引用总会被线程共享,这样在新线程开启的时候就可以看见当前实例,但是当前类的构造方法不一定执行完了。总而言之,不要在构造方法中去开启一个线程,可以创建,然后通过工厂方法发布出去。

9. ThreadLocal

允许使用者将每个线程与持有数值的对象关联在一起,为每个使用它的线程维护了一份单独的拷贝,Thread (T) 在概念上与Map (Thread, T)相同,其提供了getset访问器,用来设置和获取单独的拷贝。

线程安全

  • 不要妄图只使用原子类型的数据去解决所有的并发问题,原子类型只保证当前变量的原子性,对整个程序的执行时序等等没有保障。

  • 获得锁的唯一途径是进入锁保护的代码块中,而无论是正常退出还是抛出异常,线程都会在离开代码块后释放锁。

  • 无论是类锁还是对象锁实际上都是对象锁,类锁持有的是ClassLoader加载的当前类对象。

  • 获得和释放锁都存在开销,并且使用锁会导致代码块串行运行,性能大大降低,尽量锁代码块而不是整个方法

共享对象

  • 为了维护程序的封装性,只在必要的时候提供变量的set方法,避免过多的私有变量能够被外界获取。

  • 不可变的对象永远是线程安全的,但是不可变并不意味着将对象的所有域都声明为final类型,因为final修饰的引用虽然不可变,但是引用指向的对象的属性是可以变化的。

一些线程安全的类

在源码中会见到一些线程安全相关的类,比如Hashtable,但是由于在不考虑线程安全下HashMap比它操作、效率高,在多线程下Hashtable线程安全的机制效率很低,所以已经被sychronizedMapConcurrentMap替代。

以数组为结构的CopyOnWriteArrayList,以及其衍生出来的CopyOnWriteSet,没有用到锁,属于效率高的线程安全集合。

队列有BlockingQueueConcurrentLinkedQueue

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值