Java之美[从菜鸟到高手演变]之线程同步的引入(二)

转自:http://www.51weixue.com/thread-104-1-1.html


读写锁
有的时候,数据是需要被频繁读取的,但不排除偶尔的写入,我们只要保证:在读取线程读取数据的时候,能够读到最新的数据就不会问题。此时符合读-写锁的特点:一个资源能够被多个线程读取,或者一个线程写入,二者不同时进行。这种特点,在特定的情况下有很好的性能!
Volatile变量
这是一种轻量级的同步机制,和前面说的可见性有很大关系,可以说,volatile变量,可以保证变量数据的可见性。在Java中设置变量值的操作,对于变量值的简单读写操作没有必要进行同步,都是原子操作。只有long和double类型的变量是非原子操作的。JVM将二者(long和double都是64位的)的读写划分为两个32位的操作,这样就有可能就会不安全,只有声明为volatile,才会使得64位的long和double成为现场安全的。当一个变量声明为volatile类型后,编译器会对其进行监控,保证其不会与其它内存操作一起被重排序(重排序:举个例子,num=num+1;flag=true;JVM在执行这两条语句时,不一定先执行num=num+1,也许在num+1赋值给num之前,flag就已经为true了,这就是一种重排序),同时,volatile变量不会被进行缓存,所以,每当读取volatile变量时,总能得到最新的值!为什么会这样?我们来看下面这段话:在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。要解决这个问题,只有把该变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
此处注意:volatile关键字只能保证线程的可见性,但不能保证原子性,试图用volatile保证原子性会很复杂!
一般情况,volatile关键字用于修饰一些变量,如:被当做完成标识、中断、状态等。满足一下三个条件的情况,比较符合volatile的使用情景:
1、写入变量时并不依赖变量的当前值(否则就和value++类似了),或者能够确保只有单一线程修改变量的值。
2、变量不需要与其他的状态变量共同参与不变约束。
3、访问变量时,没有其它原因需要加锁。(毕竟加锁是个耗性能的操作)
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
Semaphore(信号量)
信号量的意思就是设置一个最大值,来控制有限个对象同时对资源进行访问。因为有的时候有些资源并不是只能由一个线程同时访问的,举个例子,我这儿有5个碗,只能满足5个人同时用餐,那么我可以设置一个最大值5,线程访问时,用acquire() 获取一个许可,如果没有就等待,用完时用release() 释放一个许可。这样就保证了最多5个人同时用餐,不会造成安全问题,这是一种很简单的同步机制。
临界区
如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。
同步容器
Java为我们提供非常完整的线程同步机制,这包括jdk1.5后新增的java.util.concurrent包,里面包含各种各样出色的线程安全的容器(即集合类)。如ConcurrentHashMap,CopyOnWriteArrayList、LinkedBlockingDeque等,这些容器有的在性能非常出色,也是值得我们程序员庆幸的事儿!
Collections位集合类提供线程安全的支持

对于有些非线程安全的集合类,如HashMap,我们可以通过Collections的一些方法,使得HashMap变为线程安全的类,如:Collections.synchronizedMap(new HashMap());

excutor框架

Java中excutor只是一个接口,但它为一个强大的同步框架做好了基础,其实现可以用于异步任务执行,支持很多不同类型的任务执行策略。excutor框架适用于生产者-消费者模式,是一个非常成熟的框架,此处不多讲,在后续的文章中,我会细细分析它!
事件驱动
事件驱动的意思就是一件事情办完后,唤醒其它线程去干另一件。这样就保证:1、数据可见性。在A线程执行的时候,B线程处于睡眠状态,不可能对共享变量进行修改。2、互斥性。相当于上锁,不会有其它线程干扰。常用的方法有:sleep()、wait()、notify()等等。
以上这些就是一些线程同步的方法,此处我没有详细的介绍,是希望将详细的分析留到后面,作专题。上一章和本章都是以基本概念为主,先带领大家从理论的层面了解多线程开发,慢慢地我们会进入到实践环节,从下一章开始,将较为详细的分析各种多线程同步的方法,以及在进行多线程编程时需要注意的问题。笔者真心期望各位读者能提出建议,积极补充!我们一起讨论,共同进步!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值