Java多线程的调度_动力节点Java学院整理

有多个线程,如何控制它们执行的先后次序?

        方法一:设置线程优先级。

java.lang.Thread 提供了 setPriority(int newPriority) 方法来设置线程的优先级,但线程的优先级是无法保障线程的执行次序的,优先级只是提高了优先级高的线程获取 CPU 资源的概率。也就是说,这个方法不靠谱。

      方法二:使用线程合并。

使用 java.lang.Thread 的 join() 方法。比如有线程 a,现在当前线程想等待 a 执行完之后再往下执行,那就可以使用 a.join()。一旦线程使用了 a.join(),那么当前线程会一直等待 a 消亡之后才会继续执行。什么时候 a 消亡?a 的 run() 方法执行结束了 a 就消亡了。

这个方法可以有效地进行线程调度,但却只能局限于等待一个线程的执行调度。如果要等待 N 个线程的话,显然是无能为力了。而且等待线程必须在被等待线程消亡后才得到继续执行的指令,无法做到两个线程真正意义上的并发,灵活性较差。

        方法三:使用线程通信。

java.lang.Object 提供了可以进行线程间通信的 wait 与 notify 、notifyAll 等方法。每个 Java 对象都有一个隐性的线程锁的概念,通过这个线程锁的概念我们让线程间可以进行通信,各线程不再埋头单干。著名的“生产者-消费者”模型就是基于这个原理实现的。

这个方法也可以有效地进行线程调度,而且也不仅仅局限于等待一个线程的执行调度,具有很大程度上的灵活性。但操作复杂,不易控制容易造成混乱,程序维护起来也不太方便。

        方法四:使用闭锁。

闭锁就像一扇门,在先决条件未达成之前这扇门是闭着的,线程无法通过,先决条件达成之后,闭锁打开,线程就可以继续执行了。java.util.concurrent.CountDownLatch 是一个很实用的闭锁实现,它提供了 countDown() 和 await() 方法达成线程执行队列,这个方法最适合 M 个线程等待 N 个线程执行结束再执行的情况。首先初始化一个 CountDownLatch 对象,比如 CountDownLatch doneSignal = new CountDownLatch(N);该对象具有 N 作为计数阀值,每个被等待线程通过对 doneSignal 对象的持有,使用 countDown() 可以将 doneSignal 的计数阀值减一;每个等待线程通过对 doneSignal 对象的持有,使用 await() 阻塞当前线程,直到 doneSignal 计数阀值减为 0,才继续往下执行。

这个方法也可以有效地进行线程调度,而且比方法三更易于管理,开发者只需控制好 CountDownLatch 即可。但线程执行次序管理相对单一,它只是指出当前等待线程的数量,而且 CountDownLatch 的初始阀值一旦设置就只能递减下去,无法重置。如需递减过程中进行阀值的重置可以参考 java.util.concurrent.CyclicBarrier。

不管如何,CountDownLatch 对于一定条件下的线程队列的达成还是很有用的。对于复杂环境下的线程管理还是卓有成效的。所以熟悉和把握对它的使用还是很有必要的。

以下是一个实际项目中 CountDownLatch 的使用的例子:

 private Map<Long,DecryptSignalAndPath> afterDecryptFilePathMap = new HashMap<Long,DecryptSignalAndPath>();//TODO 注意容器垃圾数据的清理工作
 class DecryptRunnable implements Runnable {
   private ServerFileBean serverFile;
   private Long fid;//指向解密文件
   private CountDownLatch decryptSignal;
   protected DecryptRunnable(Long fid, ServerFileBean serverFile, CountDownLatch decryptSignal) {
     this.fid = fid;
     this.serverFile = serverFile;
     this.decryptSignal = decryptSignal;
   }
   @Override
   public void run() {
     //开始解密
     String afterDecryptFilePath = null;
     DecryptSignalAndPath decryptSignalAndPath = new DecryptSignalAndPath();
     decryptSignalAndPath.setDecryptSignal(decryptSignal);
     afterDecryptFilePathMap.put(fid, decryptSignalAndPath);
     afterDecryptFilePath = decryptFile(serverFile);
     decryptSignalAndPath.setAfterDecryptFilePath(afterDecryptFilePath);
     decryptSignal.countDown();//通知所有阻塞的线程
   } 

 }
 class DecryptSignalAndPath {
   private String afterDecryptFilePath;
   private CountDownLatch decryptSignal;
   public String getAfterDecryptFilePath() {
     return afterDecryptFilePath;
   }
   public void setAfterDecryptFilePath(String afterDecryptFilePath) {
     this.afterDecryptFilePath = afterDecryptFilePath;
   }
   public CountDownLatch getDecryptSignal() {
     return decryptSignal;
   }
   public void setDecryptSignal(CountDownLatch decryptSignal) {
     this.decryptSignal = decryptSignal;
   }
 } 

需要先执行的,被等待线程在这里加入:

 CountDownLatch decryptSignal = new CountDownLatch(1);
 new Thread(new DecryptRunnable(fid, serverFile, decryptSignal)).start();//无需拿到新线程句柄,由 CountDownLatch 自行跟踪
 try {
   decryptSignal.await();
 } catch (InterruptedException e) {
   // TODO Auto-generated catch block
 } 

需要后执行,等待的线程可以这样加入:

CountDownLatch decryptSignal = afterDecryptFilePathMap.get(fid).getDecryptSignal();
 try {
   decryptSignal.await();
 } catch (InterruptedException e) {
   // TODO Auto-generated catch block
 } 

当然,这也仅仅只是一个简单的 CountDownLatch 的使用展示,对于 CountDownLatch 来说有点大材小用了,因为它可以胜任更复杂的多线程环境。示例中的案例完全可以使用线程通信进行搞定。因为 CountDownLatch 的阀值初始为 1,所以这里甚至完全可以使用方法二所说的线程的合并进行取代。

如果读者觉得以上示例不够清晰,也可以参考 JDK API 提供的 demo,这个清晰明了:

 class Driver2 { // ...
  void main() throws InterruptedException {
   CountDownLatch doneSignal = new CountDownLatch(N);
   Executor e = ... 

   for (int i = 0; i < N; ++i) // create and start threads
    e.execute(new WorkerRunnable(doneSignal, i)); 

   doneSignal.await();      // wait for all to finish
  }
 } 

 class WorkerRunnable implements Runnable {
  private final CountDownLatch doneSignal;
  private final int i;
  WorkerRunnable(CountDownLatch doneSignal, int i) {
   this.doneSignal = doneSignal;
   this.i = i;
  }
  public void run() {
   try {
    doWork(i);
    doneSignal.countDown();
   } catch (InterruptedException ex) {} // return;
  } 

  void doWork() { ... }
 } 

以上所述是小编给大家介绍的Java多线程的调度,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • java 多线程Thread与runnable的区别

    java 多线程Thread与runnable的区别 java中实现多线程的方法有两种:继承Thread类和实现runnable接口 1,继承Thread类,重写父类run()方法 public class thread1 extends Thread { public void run() { for (int i = 0; i < 10000; i++) { System.out.println("我是线程"+this.getId()); } } public static

  • java多线程消息队列的实现代码

    本文介绍了java多线程消息队列的实现代码,分享给大家,希望对大家有帮助,顺便也自己留个笔记 1.定义一个队列缓存池: //static修饰的成员变量和成员方法独立于该类的任何对象.也就是说,它不依赖类特定的实例,被类的所有实例共享. private static List<Queue> queueCache = new LinkedList<Queue>(); 2.定义队列缓冲池最大消息数,如果达到该值,那么队列检入将等待检出低于该值时继续进行. private Integer

  • java多线程学习之死锁的模拟和避免(实例讲解)

    1.死锁 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不可能正常终止. Java 死锁产生的四个必要条件: 1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放. 3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有. 4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的

  • Java基于Socket实现简单的多线程回显服务器功能示例

    本文实例讲述了Java基于Socket实现简单的多线程回显服务器功能.分享给大家供大家参考,具体如下: 需要两个类,一个是EchoServer,代表服务器.另外一个是EchoServerClient,代表客户端.代码如下: package interview; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter

  • java多线程学习笔记之自定义线程池

    当我们使用 线程池的时候,可以使用 newCachedThreadPool()或者 newFixedThreadPool(int)等方法,其实我们深入到这些方法里面,就可以看到它们的是实现方式是这样的. public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueu

  • Java多线程的用法详细介绍

    Java多线程的用法详细介绍 最全面的Java多线程用法解析,如果你对Java的多线程机制并没有深入的研究,那么本文可以帮助你更透彻地理解Java多线程的原理以及使用方法. 1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( ); publi

  • 浅谈Java的两种多线程实现方式

    本文介绍了浅谈Java的两种多线程实现方式,分享给大家.具有如下: 一.创建多线程的两种方式 Java中,有两种方式可以创建多线程: 1 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2 通过实现Runnable接口,实例化Thread类 在实际应用中,我们经常用到多线程,如车站的售票系统,车站的各个售票口相当于各个线程.当我们做这个系统的时候可能会想到两种方式来实现,继承Thread类或实现Runnable接口,现在看一下这两种方式实现的两种结果. 程序1

  • Java多线程 线程同步与死锁

     Java多线程 线程同步与死锁 1.线程同步 多线程引发的安全问题 一个非常经典的案例,银行取钱的问题.假如你有一张银行卡,里面有5000块钱,然后你去银行取款2000块钱.正在你取钱的时候,取款机正要从你的5000余额中减去2000的时候,你的老婆正巧也在用银行卡对应的存折取钱,由于取款机还没有把你的2000块钱扣除,银行查到存折里的余额还剩5000块钱,准备减去2000.这时,有趣的事情发生了,你和你的老婆从同一个账户共取走了4000元,但是账户最后还剩下3000元. 使用代码模拟下取款过

  • java多线程之火车售票系统模拟实例

    1.前言 为了学习多线程共享与通信,我们模拟一个火车售票系统,假设有10张火车票,三个窗口(也就是三个线程)同时进行售票. 2.非同步代码 package com.tl.skyLine.thread; /** * Created by tl on 17/3/6. */ public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); Thread t1

  • Java多线程的调度_动力节点Java学院整理

    有多个线程,如何控制它们执行的先后次序?         方法一:设置线程优先级. java.lang.Thread 提供了 setPriority(int newPriority) 方法来设置线程的优先级,但线程的优先级是无法保障线程的执行次序的,优先级只是提高了优先级高的线程获取 CPU 资源的概率.也就是说,这个方法不靠谱.       方法二:使用线程合并. 使用 java.lang.Thread 的 join() 方法.比如有线程 a,现在当前线程想等待 a 执行完之后再往下执行,那就

  • Java 多线程并发编程_动力节点Java学院整理

    一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

  • Java BigDecimal详解_动力节点Java学院整理

    1.引言 借用<Effactive Java>这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的.然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合.但是,商业计算往往要求结果精确,例如银行存款数额,这时候BigDecimal就派上大用场啦. 2.BigDecimal简介 BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组

  • Java接口的作用_动力节点Java学院整理

    1. 接口是一种规范 很好,你已经知道接口是一种规范了! 下面这张图是我们生活中遇到的接口:电源插座接口. 2. 为什么需要规范呢? 因为有了接口规范: • 任何电器只有有符合规范的插头,就可以获得电力 • 任何厂家(西门子插座,TCL插座,公牛插座...)按照规范进行制作,就能进行供电 每个厂家插座的生产技术.工艺都不一样,因为接口的implementation可以不一样,但是并不影响电器的正常工作.插座的内部实现对于电器来说是完全屏蔽的. 对于软件开发同样也是类似的: • 按照接口规范进行方

  • Java数组的特性_动力节点Java学院整理

    Java中的数组是对象吗? Java和C++都是面向对象的语言.在使用这些语言的时候,我们可以直接使用标准的类库,也可以使用组合和继承等面向对象的特性构建自己的类,并且根据自己构建的类创建对象.那么,我们是不是应该考虑这样一个问题:在面向对象的语言中,数组是对象吗? 要判断数组是不是对象,那么首先明确什么是对象,也就是对象的定义.在较高的层面上,对象是根据某个类创建出来的一个实例,表示某类事物中一个具体的个体.对象具有各种属性,并且具有一些特定的行为.而在较低的层面上,站在计算机的角度,对象就是

  • Java 中HashCode作用_动力节点Java学院整理

    第1 部分 hashCode的作用 Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的.对于List好处理,但是对于Set而言我们要如何来保证元素不重复呢?通过迭代来equals()是否相等.数据量小还可以接受,当我们的数据量大的时候效率可想而知(当然我们可以利用算法进行优化).比如我们向HashSet插入1000数据,难道我们真的要迭代1000次,调用1000次equals()方法吗?hashCod

  • Java字符编码简介_动力节点Java学院整理

    1. 概述 本文主要包括以下几个方面:编码基本知识,Java,系统软件,url,工具软件等. 在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687".注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示". 2. 编码基本知识 最早的编码是iso88

  • Java线程安全问题小结_动力节点Java学院整理

    浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的.其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改.总结java的内存模型,要解决两个主要的问题:可见性和有序性.我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的.JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要清楚在jvm内存模型的基础上,如果解决多线程的可见性和有序性. 那么,何谓

  • Java Socket编程笔记_动力节点Java学院整理

    对于即时类应用或者即时类的游戏,HTTP协议很多时候无法满足于我们的需求.这会,Socket对于我们来说就非常实用了.下面是本次学习的笔记.主要分异常类型.交互原理.Socket.ServerSocket.多线程这几个方面阐述. 异常类型 在了解Socket的内容之前,先要了解一下涉及到的一些异常类型.以下四种类型都是继承于IOException,所以很多之后直接弹出IOException即可. UnkownHostException:    主机名字或IP错误 ConnectException

  • Java日志相关技术_动力节点Java学院整理

    Java日志相关技术 作为一名Java程序员,我们开发了很多Java应用程序,包括桌面应用.WEB应用以及移动应用.然而日志系统是一个成熟Java应用所必不可少的,在开发和调试阶段,日志可以帮助我们更好更快地定位bug:在运行维护阶段,日志系统又可以帮我们记录大部分的异常信息,从而帮助我们更好的完善系统.本文要来分享一些Java程序员最常用的Java日志框架组件. 1.log4j – 最受欢迎的Java日志组件 Log4j是一款基于Java的开源日志组件,Log4j功能非常强大,我们可以将日志信

随机推荐