聊聊Java中是什么方法导致的线程阻塞

一、为什么引入线程阻塞机制?

为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,Java 引入了对阻塞机制的支持

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。

二、Java中实现线程阻塞的方法:

(1)线程睡眠:Thread.sleep (long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

(2)线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait() 一样。wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.

(3)线程礼让,Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程.

(4)线程自闭,join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

(5)suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

这里,笔者放入一张线程生命周期的经典图片,来帮助读者理解,里面展示了一个线程从创建->运行->阻塞->运行->死亡的全过程:

三、常用线程名词解释

主线程:JVM调用程序main()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

可见进程:可见进程是指一些不在前台,但用户依然可见的进程,举例来说:各种widget、输入法等,都属于visibe。这部分进程虽然不在前台,但与我们的使用也是密切相关,我们并不希望它被系统终止。

“前台可见进程服务于后台空进程”——这是记录线程重要性的口诀,

重要性一次递减即,前台进程>可见进程>服务进程>后台进程>空进程。

线程类的一些常用方法:

sleep(): 强迫一个线程睡眠N毫秒。

isAlive(): 判断一个线程是否存活。

join(): 等待线程终止。

activeCount(): 程序中活跃的线程数。

enumerate(): 枚举程序中的线程。

currentThread(): 得到当前线程。

isDaemon(): 一个线程是否为守护线程。

setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)

setName(): 为线程设置一个名称。

wait(): 强迫一个线程等待。

notify(): 通知一个线程继续运行。

setPriority(): 设置一个线程的优先级。

补充:java处理线程阻塞的小技巧

在java中我们使用多线程去处理一些业务,如果业务比较复杂且当并发量有挺大的时候,很有可能出现线程阻塞的问题。

案例:

有一个触发接口,根据触发的信息内部开启多个线程去执行业务,每个线程都会去执行两种业务:私有业务(比如调用不同的接口)、公共业务(比如执行存储、mq发送等等),当私有业务处理时间很快而公共业务处理时间比较长,这样的情景下就可以把私有业务和公共业务分到不同线程执行。

例如:

当触发了这个接口,根据接口触发的信息,需要开启10个线程,那么就可以创建10个线程去执行它的私有业务,然后再额外创建一个线程去拿到前面那10个线程的执行返回结果并进行公共业务的处理。

这样有个好处,就是能让线程池很快的回收线程,能有效防止线程的阻塞

量化:

单个私有业务1秒钟能执行完成,单个公共业务需要5秒钟才能执行完成,如果接口被触发,发现需要创建100个线程执行,那么线程池回收这些线程池至少需要等待6秒,如果按照前面说的分成两个线程,那么就需要创建101个线程,而1秒后就能回收掉执行完成的100个线程

但是这里需要做权衡,如果接口被触发的时候发现需要开启的线程比较多且公共业务很耗时,这种情况下执行公共业务只有单个线程同步执行,那么这个线程就会执行比较长的时间,所以执行公共业务的时候也可根据实际情况开启多个线程。

下面写了个小demo:

1.私有业务的类:

@Component
public class Calculation {
  public Result cal(String req, int a, int b) {
    System.out.println("请求id:" + req + "  结果:" + (a + b));
    return new Result(req, a + b);
  }
}

2.公共业务的类:

@Component
public class SomethingElse {
  public void doElse(Result result) {
    try {
      System.out.println(Thread.currentThread().getName() + " : 开始做其他事情,请求号:" + result.getReq() + " ,请求结果:" + result.getSum());
      Thread.sleep(2000);
      System.out.println(Thread.currentThread().getName() + " : 完成做其他事情,请求号:" + result.getReq() + " ,请求结果:" + result.getSum());
    } catch (InterruptedException e) {
    }
  }
}

3.私有业务的线程类:

public class CallTask implements Callable<Result> {
  private String req;
  private int a;
  private int b;
  @Override
  public Result call() throws Exception {
    Calculation calculation = Main.applicationContext.getBean(Calculation.class);
    return calculation.cal(req, a, b);
  }
  public CallTask(String req, int a, int b) {
    this.req = req;
    this.a = a;
    this.b = b;
  }
  // getter and setter 等等
}

4.公共业务的线程类:

public class ElseTask implements Runnable {
  private CompletionService<Result> cs;
  private int threadCount;
  public ElseTask(CompletionService<Result> cs, int threadCount) {
    this.cs = cs;
    this.threadCount = threadCount;
  }
  @Override
  public void run() {
    SomethingElse somethingElse = Main.applicationContext.getBean(SomethingElse.class);
    doElse(somethingElse);
  }
  private void doElse(SomethingElse somethingElse) {
    try {
      for (int i = 0; i < threadCount; i++) {
        Future<Result> take = cs.take();
        Result result = take.get();
        somethingElse.doElse(result);
      }
    } catch (Exception e) {
    }
  }
  // getter and setter 等等
}

6.测试主方法:

@Service
public class Main implements ApplicationContextAware {
  public static ApplicationContext applicationContext = null;
  public static void main(String[] args) throws InterruptedException {
    AbstractApplicationContext appContext = new ClassPathXmlApplicationContext("application01.xml");
    ExecutorService executorService = Executors.newFixedThreadPool(100);
    CompletionService<Result> cs = new ExecutorCompletionService(executorService);
    //这里启动执行计算的线程
    cs.submit(new CallTask("req001", 0, 1));
    cs.submit(new CallTask("req002", 0, 2));
    cs.submit(new CallTask("req003", 0, 3));
    cs.submit(new CallTask("req004", 0, 4));
    cs.submit(new CallTask("req005", 0, 5));
    //专门的监控线程,并执行其他耗时的线程
    executorService.execute(new ElseTask(cs, 5));
    executorService.shutdown();
    appContext.registerShutdownHook();
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}

执行结果如下:

核心思想: 将多线程的公有的业务抽出来(前提是公有业务比较耗时,不然就没必要了)在其他线程里面执行。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Java使用阻塞队列控制线程通信的方法实例详解

    本文实例讲述了Java使用阻塞队列控制线程通信的方法.分享给大家供大家参考,具体如下: 一 点睛 阻塞队列主要用在生产者/消费者的场景,下面这幅图展示了一个线程生产.一个线程消费的场景: 负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值.队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费.同理,负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入. BlockingQueue的核心方法:

  • 详解Java多线程编程中CountDownLatch阻塞线程的方法

    直译过来就是倒计数(CountDown)门闩(Latch).倒计数不用说,门闩的意思顾名思义就是阻止前进.在这里就是指 CountDownLatch.await() 方法在倒计数为0之前会阻塞当前线程. CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. CountDownLatch 的作用和 Thread.join() 方法类似,可用于一组线程和另外一组线程的协作.例如,主线程在做一项工作之前需要一系列的准备工作,只有这些准备工

  • 详解Java多线程编程中LockSupport类的线程阻塞用法

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语. LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到"Thread.suspend 和 Thread.resume所可能引发的死锁"问题. 因为park() 和 unpark()有许可的存在:调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性. 基本用法 LockSupport 很类似于二元信号

  • Java多线程阻塞与唤醒代码示例

    java线程的阻塞及唤醒 1. sleep() 方法: sleep(-毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态.(暂停线程,不会释放锁) //测试sleep()方法 class Thread7 implements Runnable{ @Override public void run() { for(int i=0;i<50;i++){ System.out.println(Thread.currentT

  • java 打造阻塞式线程池的实例详解

    java 打造阻塞式线程池的实例详解 原来以为tiger已经自带了这种线程池,就是在任务数量超出时能够阻塞住投放任务的线程,主要想用在JMS消息监听. 开始做法: 在ThreadPoolExcecutor中代入new ArrayBlockingQueue(MAX_TASK). 在任务超出时报错:RejectedExecutionException. 后来不用execute方法加入任务,直接getQueue().add(task), 利用其阻塞特性.但是发现阻塞好用了,但是任务没有被处理.一看Qu

  • Java countDownLatch如何实现多线程任务阻塞等待

    我这里需要通过多线程去处理数据,然后在所有数据都处理完成后再往下执行.这里就用到了CountDownLatch.把countdownlatch作为参数传入到每个线程类里,在线程中处理完数据后执行countdown方法.在所有countdownlatch归零后,其await方法结束阻塞状态而往下执行. 具体代码如下: 将多线程任务提交线程池 @Bean(name = "ggnews_executor") public Executor postExecutor() { ThreadPoo

  • 聊聊Java中是什么方法导致的线程阻塞

    一.为什么引入线程阻塞机制? 为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个.为了解决这种情况下的访问控制问题,Java 引入了对阻塞机制的支持. 阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了.Java 提供了大量方法来支持阻塞,下面让我们逐一分析. 二.Java中实

  • 一起聊聊Java中13种锁的实现方式

    目录 1.悲观锁 2.乐观锁 3.分布式锁 加锁 4.可重入锁 5.自旋锁 6.独享锁 7.共享锁 8.读锁/写锁 9.公平锁/非公平锁 10.可中断锁/不可中断锁 11.分段锁 12.锁升级(无锁|偏向锁|轻量级锁|重量级锁) 无锁 偏向锁 轻量级锁 重量级锁 13.锁优化技术(锁粗化.锁消除) 最近有很多小伙伴给我留言,分布式系统时代,线程并发,资源抢占,"锁" 慢慢变得很重要.那么常见的锁都有哪些? 今天Tom哥就和大家简单聊聊这个话题. 1.悲观锁 正如其名,它是指对数据修改时

  • java中重写equals()方法的同时要重写hashcode()方法(详解)

    object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true: 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下: (1) 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2) 当obj

  • 浅谈Java中的hashcode方法(推荐)

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

  • Java中Equals使用方法汇总

    这篇总结的形式是提出个问题,然后给出问题的答案.这是目前学习知识的一种尝试,可以让学习更有目的. Q1.什么时候应当重写对象的equals方法? 答:一般在我们需要进行值比较的时候,是需要重写对象的equals方法的.而例外情况在<effective java>的第7条"在改写equals的时候请遵守通用约定"中清楚描述了. 我们知道,在Java中,每个对象都继承于Object.如果不重写,则默认的equals代码如下所示: public boolean euqals(Ob

  • Java中的hashcode方法介绍

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

  • Java中由substring方法引发的内存泄漏详解

    内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出. 内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样. 由substring方法引发的内存泄漏 substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全

  • 聊聊Java 中的线程中断

    Java如何实现线程中断? 通过调用Thread类的实例方法interrupt.如下: Thread thread = new Thread(){ @Override public void run() { if(isInterrupted()){ System.out.println("interrupt"); } } }; thread.start(); thread.interrupt(); 线程中断后线程会立即停止执行吗? NO. 而如果线程未阻塞,或未关心中断状态,则线程会正

  • 浅谈Java中的桥接方法与泛型的逆变和协变

    目录 1. 泛型的协变 1.1 泛型协变的使用 1.2 泛型协变存在的问题 1.2.1 Java当中桥接方法的来由 1.2.2 为什么泛型协变时,不允许添加元素呢 1.2.3 从Java字节码的角度去看桥接方法 2. 泛型逆变 2.1 泛型逆变的使用 2.2 泛型逆变会有什么问题 3.协变与逆变-PECS原则 泛型的协变和逆变是什么?对应于Java当中,协变对应的就是<? extends XXX>,而逆变对应的就是<? super XXX>. 1. 泛型的协变 1.1 泛型协变的使

  • java中String.intern()方法功能介绍

    下文笔者讲述java中String.intern()方法的功能简介说明,如下所示: String.intern原理 String.intern():    此方法是一个Native方法      底层调用C++的 StringTable::intern方法实现 当通过语句str.intern()调用intern()方法后    JVM 就会在当前类的常量池中查找是否存在与str等值的String     若存在则直接返回常量池中相应Strnig的引用     若不存在,则会在常量池中创建一个等值

随机推荐