热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发编程实战(第五章1)

5.1同步容器类5.1.1同步容器类的问题同步容器类(Vector、Hashtable)都是线程安全的,但在某些情况下可能需要额外的客服

5.1 同步容器类

5.1.1 同步容器类的问题
同步容器类(Vector、Hashtable)都是线程安全的,但在某些情况下可能需要额外的客服端加锁来保护复合操作
复合操作:
1)迭代(反复访问元素,直到遍历完容器中所有元素);
2)跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算。
同步容器中,这些复合操作在没有客服端加锁的情况下任然是线程安全的,但但其他线程并发地修改容器时,它们可能会表现出意料之外的行为。

package chapter5;
import java.util.Vector;/*** 在调用size与调用getLast这两个操作之间,Vector变小了,* 因此在调用size时得到的索引值将不再有效---将抛出ArrayIndexOutOfBoundsException异常*/
public class DemoVector {public static Object getLast(Vector vector){// 同步容器类通过其自身的锁来保护它的每个方法。通过获得容器类的锁&#xff0c;我们可以使getLast// 和deleteLast成为原子操作&#xff0c;并确保Vector大小在调用size和get之间不会发生变化。synchronized (vector) {int lastIndex &#61; vector.size() - 1;return vector.get(lastIndex);}}public static Object deleteLast(Vector vector){synchronized (vector) {int lastIndex &#61; vector.size() - 1;Object object &#61; vector.remove(lastIndex);return object ;}}// 在调用size和相应的get之间&#xff0c;vector的长度可能发生变化&#xff0c;这种风险在对Vector中元素// 进行迭代时任然会出现&#xff0c;与getLast一样&#xff0c;如果在对Vector进行迭代时&#xff0c;另一个线程删除了一个// 一个元素&#xff0c;并且这两个操作交替执行&#xff0c;那么这种迭代方法将抛出ArrayIndexOutOfBoundsExceptionpublic static void doSomething(Vector vector){// 通过在迭代期间持有Vector的锁&#xff0c;可以防止其他线程在迭代期间修改vector。然而&#xff0c;这同样会导致// 其他线程在迭代期间无法访问它&#xff0c;因此降低了并发性。synchronized (vector) {System.out.println(Thread.currentThread().getName() &#43; ":" &#43; vector.toString());}}public static void main(String[] args) throws InterruptedException {final Vector vector &#61; new Vector();for (int i &#61; 0; i < 10; i &#43;&#43;){vector.add(i) ;}for (int j &#61; 0; j < 5; j &#43;&#43;) {Thread.sleep(1000);new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()&#43; ":" &#43;getLast(vector));}}).start();}for (int j &#61; 0; j < 5 ; j &#43;&#43;) {new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() &#43; " delete:" &#43; deleteLast(vector));}}).start();}for (int j &#61; 0 ; j < 5; j &#43;&#43;) {Thread.sleep(100);new Thread(new Runnable() {public void run() {doSomething(vector);}}).start();}}
}
/**
output:~Thread-0:9Thread-1:9Thread-2:9Thread-3:9Thread-4:9Thread-5 delete:9Thread-8 delete:8Thread-7 delete:6Thread-9 delete:5Thread-6 delete:7Thread-10:[0, 1, 2, 3, 4]Thread-11:[0, 1, 2, 3, 4]Thread-12:[0, 1, 2, 3, 4]Thread-13:[0, 1, 2, 3, 4]Thread-14:[0, 1, 2, 3, 4]*/

5.1.2 迭代器与ConcurrentModificationException
有时候开发人员并不希望在迭代期间对容器加锁。如果容器的规模很大&#xff0c;或者在某个元素上执行操作的时间很长&#xff0c;那么这些线程将长时间等待。即使不存在饥饿或者死锁等风险&#xff0c;长时间地对容器加锁也会降低程序的可伸缩性。持有锁的时间越长&#xff0c;那么在锁上的竞争就可能越激烈&#xff0c;如果许多线程都在等待死锁被释放&#xff0c;那么将极大地降低吞吐量和CPU的利用率
如果不希望在迭代期间对容器加锁&#xff0c;那么一种可替代方法是**“克隆”容器并在容器上进行迭代。副本被封锁在线程内&#xff0c;其它线程不会在迭代期间对其进行修改–避免了抛出ConcurrentModificationException。&#xff08;在克隆的过程中任然要对容器加锁**&#xff0c;并且克隆容器时的开销由其大小、在每个元素上执行的操作&#xff09;

5.1.3 隐藏迭代器
虽然加锁可以防止迭代器抛出ConcurrentModificationException&#xff0c;但你必须要记住在所有对共享容器进行迭代的地方都需要加锁。 然而实际情况要更加复杂&#xff0c;因为在某些情况下&#xff0c;迭代器会隐藏起来。

package chapter5;import java.util.HashSet;
import java.util.Random;
import java.util.Set;/*** 虽然加锁可以防止迭代器抛出ConcurrentModificationException&#xff0c;* 但你必须要记住在所有对共享容器进行迭代的地方都需要加锁。* * warning:* 容器的hashCode和equals等方法也会间接地执行迭代操作&#xff1b;* 当容器作为另一个容器的元素或键时&#xff0c;也会出现这种情况&#xff1b;* 同样&#xff0c;containsAll、removeAll和retainsAll等&#xff1b;* 以及把容器作为参数的构造函数&#xff0c;都会对容器进行迭代。*/
public class HiddenIterator {private final Set<Integer> set &#61; new HashSet<Integer>();public synchronized void add(Integer i) {set.add(i);}public synchronized void remove(Integer i) {set.remove(i);}public void addTenThings() {Random r &#61; new Random();for (int i &#61; 0; i < 1000; i&#43;&#43;) {add(r.nextInt()); // 进行了同步}// 在使用println中的set之前必须首先获取HiddenIterator的锁&#xff0c;但在// 但在调试代码和日志代码中通常会忽略这个要求System.out.println("DEBUG: added ten elements to " &#43; set);}
}

正如封装对象的状态有助于维持不变性条件一样&#xff0c;封装对象的同步机制同样有助于确保实施同步策略。


5.2 并发容器

通过并发容器来代替同步容器&#xff0c;可以极大地提高伸缩性并降低风险。
5.2.1 ConcurrentHashMap
同步容器类在执行每个操作期间都持有一个锁。
与HashMap一样&#xff0c;ConcurrentHashMap也是一个基于散列的Map&#xff0c;但是它使用了一种完全不同的加锁机制。
ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器&#xff0c;而是使用一种粒度更细的加锁机制来实现更大程度的共享&#xff0c;这种机制称为分段锁
ConcurrentHashMap与其他并发容器一起增强了同步容器类&#xff1a;它们提供的迭代器不会抛出ConcurrentModificationException&#xff0c;因此不需要在迭代过程中对容器加锁。只有当应用程序需要加锁Map以进行独占访问时&#xff0c;才应该放弃使用ConcurrentHashMap.

5.2.2 额外的原子Map操作
由于ConcurrentHashMap不能被加锁来执行独占访问&#xff0c;因此我们无法使用客户端加锁来创建新的原子操作。

5.2.3 CopyOnWriteArrayList
CoppyOnWriteArrayList用于替代同步List&#xff0c;在某些情况下它提供了更好的并发性能&#xff0c;并且在迭代期间不需要对容器进行加锁或复制。&#xff08;类似地&#xff0c;CopyOnWriteArraySet的作用是替代同步Set。&#xff09;
“写入时复制”容器的线程安全性在于&#xff0c;只要正确地发布一个事实不可变的对象&#xff0c;那么在访问该对象时&#xff0c;就不再需要进一步的同步。在每次修改时&#xff0c;都会创建并重新发布一个新的容器副本&#xff0c;从而实现可变性。


5.3 阻塞队列和生产者-消费者模式

阻塞队列提供了可阻塞的put和take方法&#xff0c;以及支持定时的offer和poll方法。队列已满–阻塞put直到有空间可用。队列为空–阻塞take直到有元素可用。
在构建高可靠的应用程序时&#xff0c;有界队列是一种强大的资源管理工具&#xff1a;它们能抑止并防止产生过多的工作项&#xff0c;使应用程序在负载过高的情况下变得跟家健壮。

5.3.2串行线程封闭
在java.util.concurrent中实现的各种阻塞队列都包含了足够的内部同步机制&#xff0c;从而安全地将对象从生产者线程发布到消费者线程。


5.4 阻塞方法与中断方法

线程可能会阻塞或者暂停执行&#xff0c;原因有多种&#xff1a;等待I/O操作结束&#xff0c;等待获得一个锁&#xff0c;等待从Thread.sleep方法中醒来&#xff0c;或是等待另一个线程的计算结果。当线程阻塞时&#xff0c;它通常被挂起&#xff0c;并处于某种阻塞状态(BLOCKED、WAITING或TIME_WAITING&#xff09;。
阻塞操作与执行时间很长的普通操作的差别在于&#xff0c;被阻塞的线程必须等待某个不受它控制的事件发生后才能继续执行&#xff0c;例如等待I/O操作完成&#xff0c;等待某个锁变成可用&#xff0c;或者等待外部计算的结束。
一个线程不能强制其它线程停止正在执行的操作而去执行其他的操作。方法对中断请求的响应度越高&#xff0c;就越容易及时取消那些执行时间长的操作。

public class TaskRunnable implements Runnable{BlockingQueue<Tast> queuqu;...public void run(){try{proccessTask(queue.take()) ;} catch(InterruptedException e) {// 恢复中断的状态Thread.currentThread().interrupt() ;}}
}

5.5 同步工具类

阻塞队列&#xff1a;不仅能作为保存对象的容器&#xff0c;还能协调生产者和消费者之间的控制流&#xff0c;因为take和put等方法将阻塞&#xff0c;直到队列达到期望的状态(队列既非空&#xff0c;也非满)。


  1. 闭锁
    在闭锁到达结束状态之前&#xff0c;这扇门一直是关闭的&#xff0c;并且没有任何线程能通过&#xff0c;当到达结束状态时&#xff0c;这扇门会打开并释放所有的线程通过。

import java.util.concurrent.CountDownLatch;
public class TestHarness{public long timeTasks(int nThreads, final Runnable task) throws InterruptedException{final CountDownLatch startGate &#61; new CountDownLatch(1); // 初始化闭锁final CountDownLatch endGate &#61; new CountDownLatch(nThreads);for (int i &#61; 0; i < nThreads; i &#43;&#43;){Thread t &#61; new Thread(){public void run(){try{startGate.await(); // 在启动门上等待try{task.run();}finally{endGate.countDown(); // 调用结束门的countDown方法减一}}catch(InterruptedException ignored){}}};t.start();}long start &#61; System.nanoTime();startGate.countDown();endGate.await();long end &#61; System.nanoTime();return end-start;}
}

推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 数组的排序:数组本身有Arrays类中的sort()方法,这里写几种常见的排序方法。(1)冒泡排序法publicstaticvoidmain(String[]args ... [详细]
  • 面向对象之3:封装的总结及实现方法
    本文总结了面向对象中封装的概念和好处,以及在Java中如何实现封装。封装是将过程和数据用一个外壳隐藏起来,只能通过提供的接口进行访问。适当的封装可以提高程序的理解性和维护性,增强程序的安全性。在Java中,封装可以通过将属性私有化并使用权限修饰符来实现,同时可以通过方法来访问属性并加入限制条件。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
author-avatar
月曳柳覀梢
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有