`

Java多线程常用工具小结

 
阅读更多

Java多线程问题常用的几种场景(不是全部)通常需要包括如下几个方面:

 

  1. 共享资源的互斥访问(比如:资源初始化过程)。
  2. 有限资源的访问控制(比如:数据库连接池会限制只有有限个线程可以同时保持链接)。
  3. 多线程之间访问的通讯配合(比如:典型的生产-消费模式场景)
  4. 构建线程池
  5. Callable & Future
  6. 读过写少的并发控制(比如:资源初始化过程)。
针对这些比较典型的多线程使用场景,Java已经在他的工具包中提供了很多有力的工具协助开发人员进行处理。下面会针对这几种典型的场景列举一下比较常用的对应解决方案。当然,许多并发控制工具并非只能解决固定的场景,这里仅列出个人认为比较典型的应用。

1、共享资源的互斥访问
最简单也是最古老的方式是随便创建一个对象(任何类型的实例都可以)作为lock,通过synchronized块进行某个关键代码段的互斥访问需求。比如如下的伪代码:
synchronized (lockObject) {
    // here is your code.
}
这里,如果有第二个线程想要进入这个synchronized块,那没有任何商量的余地,就是必须获得lockObject这把锁。
So,这种方法的特点是——简单而粗暴。当然你可以更简单,比如直接在方法的签名上加上synchronized关键字,那么就相当于使用this作为锁对象的大synchronized块而已。代码使用上貌似更简单。

JDK1.5之后,除了上面这种Java关键字的加锁方式之外,新引入了Lock框架。这样就提供了编程API级别的锁支持,比较常用的写法像下面这样:
public class LockDemoClass{

private final Lock lock = new ReentrantLock();

public void lockOnMethod(){
    //some codes which does not need synchronization
    lock.lock();
    try{
        //some codes which need synchronization
    }finally{
        lock.unlock();
    }
}

}
这个方式明显看上去比之前的synchronized块繁杂了一些,但却在许多方面提供了更大的灵活性。
关于两者的常见对比大概有如下几个方面:
  • 在性能上,在JDK1.5或者之前的时期,确实有人诟病Java原生的synchronized关键字锁的太重,甚至有人认为弃用synchronized而投降Lock是因为性能更好。这种假设在JDK1.6之后,由于JVM内部对synchronized的优化之后,这种考虑的因素几乎可以忽略不计了。因为synchronized有了大幅度的性能提升。
  • 在灵活度上,Lock明显高于前者,尽管有些灵活性未必被开发人员经常使用。比如:
    • Lock可以在不同方法中分别加锁解锁;
    • 如果你需要,Lock可以在保证等待线程进入互斥代码块的排队顺序(当然这要付出一些性能的代价);
    • 你可以通过设置timeout来控制获取锁时尝试等待的时间,而不是想前者那样无限的等待下去(这时加大死锁可能性的一个重要的因素)。
  • 在线程的通讯机制上,前者使用锁对象上wait/notify/notifyAll(继承自Object)来进行线程间的等待唤醒通讯;后者引入了Condition机制。一个Lock上可以创建多个Condition实例,具体condition的语义由开发人员把控,而线程之间的通讯由Condition的await/signal/signalAll来完成,这三个方法的语义基本上和上面的Object三个方法对应。
2、有限资源的访问控制
这个是Semaphore的典型应用场景。
典型的代码结构如下:
public class SemaphoreDemoClass{

//here 5 can be replaced to any int value
private final Semaphore semaphore = new Semaphore(5);

public void accessControlMethod(){
    //some codes which does not need multi-thread access control
    semaphore.acquire();
    try{
        //some codes which need multi-thread access control
    }finally{
        semaphore.release();
    }
}

}
Semaphore本质上很像一个带计数性质的阀门。每次访问这个阀门上的acquire()方法时,Semaphore都会将自身的计数器自减1,当Semaphore本身计数器已经被自减到0的时候,再去访问这个Semaphore上的acquire()方法的线程就会被Block住,于是这种机制就顺利的保证了统一资源的同时访问只能在有限个数目的线程范围内。
而且,从这个机制中可以看出,对于内部计数器最大值为1的Semaphore,就可以是另外一种资源互斥访问的形式了。

3、多线程之间访问的通讯配合 
通常情况下,我们认为较优的多线程使用场景是:多线程访问的资源是可以切分的,每个线程操控的资源和其他线程是不相干的。这种场景最爽,每个线程不需要鸟其他线程,只要自己单干就好。
但现实很残酷,绝大部分的多线程使用场景都是需要“团队合作”的。有团队合作,就需要有沟通。
关于线程间通讯沟通机制,已经在前面的共享资源的互斥访问中做了一些介绍。这里再补充一些细节场景:
  • 如果是生产-消费模式,可以借助JDK1.5之后BlockingQueue机制去做(具体选用的BlockingQueue的实现类根据具体情况选择)
  • 多个线程需要步调一致行动,必须保证同一时间点一起执行,比如模仿高并发时的模拟;多个线程必须保证等待其他线程都完成任务之后才可以进入下一步操作(当然两个线程之间的协调等待也可以通过join()来实现)。这两种典型的场景就可以使用CountDownLatch来完成。CountDownLatch内部和Semaphore实现机制相同,都会维护一个计数器,但不同的是,前者只有计数器为0时才允许线程开始执行。
  • 两个线程之间构建的生产-消费模型,但采用“互不干涉”的模式进行交互。注意:这里和一般的生产-消费模式一个最大的区别是,他不是即时生产即时消费的,而是双方分别进行自己的生产和消费(通常会使用两个资源,比如两个队列分别进行生产和消费),其中任何一方ready之后,就可以利用Exchanger.exchange(resourceObject)来完成生产资源和消费资源互换。
4、构建线程池
通过Executors的相应的静态方法可以获得具体的ExecutorService的实例(通常为ThreadPoolExecutor),通过这个具体的线程池的submit方法,可以提交执行自己业务线程。这里线程池内部按照什么机制安排被提交的线程,主要取决于构建ThreadPoolExecutor时,所使用的构造函数的参数,比如不同的内部BlockingQueue

5、Callable & Future
传统的Thread都是Runnable风格,没有返回值。如果你想得到一个线程执行的结果,只能通过join等方法,Block在那里,等待线程执行结束。
JDK1.5之后的有一个新特性就是引入了CallableFuture接口。这里最长用的使用方式就是结合上面第4点提到的线程池的submit方法获得Futurn实例。这样,就不需要阻塞业务当前主线程的执行,在将来的某个时刻在通过Future的get方法获得执行结果。
Future本身的引入,更大的意义是在多线程的环境中引入异步处理的机制,这在某些场景下实现真正的并发非常有意义。

6、读过写少的并发控制
这种比较典型的场景是资源的初始化过程中,某个资源需要初始化一次。只要初始化这一次之后,后面所有的访问全部是读取。
比如某个内存的cache,他会有初始化一堆内容进去。在真正暴露他对外服务之前,我们是需要完成所有资源的cache的,否则可能会造成cache的内容不全而导致的问题。
这里,根据ReadWriteLock的特点,可以将cache初始化的过程用writeLock包住,将资源的读取用readLock包住。这样,除了在writeLock尚未释放之前所有的其他尝试获取readLock的线程需要被Block住之外,其他大多数读取的场景下,多个线程可以共享readLock,可以获得无阻塞的高性能。
分享到:
评论

相关推荐

    实验5 JAVA常用类.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java优化编程(第2版)

    第12章 java多线程技术与应用性能优化 12.1 java多线程技术 12.1.1 进程与线程 12.1.2 线程的生命周期 12.2 并行任务与性能 12.2.1 并行任务与多线程 12.2.2 并行任务与死锁 12.3 线程池技术与应用性能优化 12.3.1 ...

    Java基础知识点总结.docx

    Java数组与集合小结 305 递归 309 对象的序列化 310 Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java...

    Java语言程序设计实验指导书

    Java语言程序设计实验指导书 前 言  Java语言是计算机专业的一门重要的专业,是在实际开发中的一个非常重要开发工具。Java语言由于其平台无关性和自己就是一个网络编程语言,使得它在...实验6:Java中的多线程 12

    Java典型模块

    1.4 小结 第2章 Java面向对象编程 2.1 面向对象的一些概念 2.1.1 面向对象涉及的概念 2.1.2 类和对象 2.2 面向对象的一些特性 2.2.1 继承特性 2.2.2 多态特性 2.2.3 封装特性 2.3 Java中实现的面向对象特性 2.3.1 ...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part2

    11.1 多线程的servlet模型 350 11.2 线程安全的servlet 351 11.2.1 变量的线程安全 351 11.2.2 属性的线程安全 360 11.3 singlethreadmodel接口 362 11.4 小结 363 11.5 思考题 363 第3部分 jsp篇 第12章 ...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part3

    11.1 多线程的servlet模型 350 11.2 线程安全的servlet 351 11.2.1 变量的线程安全 351 11.2.2 属性的线程安全 360 11.3 singlethreadmodel接口 362 11.4 小结 363 11.5 思考题 363 第3部分 jsp篇 第12章 ...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part4

    11.1 多线程的servlet模型 350 11.2 线程安全的servlet 351 11.2.1 变量的线程安全 351 11.2.2 属性的线程安全 360 11.3 singlethreadmodel接口 362 11.4 小结 363 11.5 思考题 363 第3部分 jsp篇 第12章 ...

    疯狂JAVA讲义

    1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种基本结构 25 2.1.3 面向对象程序设计简介 27 2.1.4 面向对象的基本特征 28 2.2 UML...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

    第1章 Java应用分层架构及软件模型  1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型 ...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    7.11 小结:多方位理解Java方法 191 7.12 习题 192 第8章 Java中的包(Package)命名习惯和注释 193 教学视频:43分钟 8.1 Java中的包(Package) 193 8.1.1 Java中的包 193 8.1.2 在Eclipse中使用包 194 ...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part5

    11.1 多线程的servlet模型 350 11.2 线程安全的servlet 351 11.2.1 变量的线程安全 351 11.2.2 属性的线程安全 360 11.3 singlethreadmodel接口 362 11.4 小结 363 11.5 思考题 363 第3部分 jsp篇 第12章 ...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    虚拟机堆转储快照分析工具 / 84 4.2.6 jstack:Java堆栈跟踪工具 / 85 4.3 JDK的可视化工具 / 87 4.3.1 JConsole:Java监视与管理控制台 / 88 4.3.2 VisualVM:多合一故障处理工具 / 96 4.4 本章小结 / 105 第5...

    21天学通Java-由浅入深

    245 12.3.3 在外部类外访问静态内部类 246 12.4 匿名内部类 247 12.4.1 创建匿名内部类 247 12.4.2 匿名内部类的初始化 249 12.5 综合练习 250 12.6 小结 250 12.7 习题 250 第13章 多线程(精彩视频:55分钟) 252 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    7.11 小结:多方位理解Java方法 191 7.12 习题 192 第8章 Java中的包(Package)命名习惯和注释 193 教学视频:43分钟 8.1 Java中的包(Package) 193 8.1.1 Java中的包 193 8.1.2 在Eclipse中使用包 194 ...

    Java虚拟机

    4.3.2 VisualVM:多合一故障处理工具 4.4 本章小结 第5章 调优案例分析与实战 5.1 概述 5.2 案例分析 5.2.1 高性能硬件上的程序部署策略 5.2.2 集群间同步导致的内存溢出 5.2.3 堆外内存导致的溢出错误 ...

Global site tag (gtag.js) - Google Analytics