「java队列使用」队列方法 java

博主:adminadmin 2023-01-14 23:15:08 68

本篇文章给大家谈谈java队列使用,以及队列方法 java对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

到底什么是消息队列?Java中如何实现消息队列

消息队列,顾名思义 首先是个队列。 队列的操作有入队和出队

也就是你有一个程序在产生内容然后入队(生产者) 另一个程序读取内容,内容出队(消费者)

这是最最基本的概念。

我想你应该是缺乏一个使用场景。

当你不需要立即获得结果,但是并发量又不能无限大的时候,差不多就是你需要使用消息队列的时候。

比如你写日志,因为可能一个客户端有多个操作去写,又有很多个客户端,显然并发不能无穷大,于是你就需要把写日志的请求放入到消息队列里,在消费者那边依次把队列中产生的日志写到数据库里。

至于怎么实现消息队列,其实你本身一个普通的队列就行呀~看你需要什么附加功能而已。

java 线程池 工作队列是如何工作的

使用线程池的好处

1、降低资源消耗

可以重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度

当任务到达时,任务可以不需要等到线程创建就能立即执行。

3、提高线程的可管理性

线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

线程池的工作原理

首先我们看下当一个新的任务提交到线程池之后,线程池是如何处理的

1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。

2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步

3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务

线程池饱和策略

这里提到了线程池的饱和策略,那我们就简单介绍下有哪些饱和策略:

AbortPolicy

为Java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。

DiscardPolicy

直接抛弃,任务不执行,空方法

DiscardOldestPolicy

从队列里面抛弃head的一个任务,并再次execute 此task。

CallerRunsPolicy

在调用execute的线程里面执行此command,会阻塞入口

用户自定义拒绝策略(最常用)

实现RejectedExecutionHandler,并自己定义策略模式

下我们以ThreadPoolExecutor为例展示下线程池的工作流程图

1.jpg

2.jpg

1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2、如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3、如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。

4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

线程池只是并发编程中的一小部分,下图是史上最全面的Java的并发编程学习技术总汇

3.jpg

关键方法源码分析

我们看看核心方法添加到线程池方法execute的源码如下:

//     //Executes the given task sometime in the future.  The task     //may execute in a new thread or in an existing pooled thread.     //     // If the task cannot be submitted for execution, either because this     // executor has been shutdown or because its capacity has been reached,     // the task is handled by the current {@code RejectedExecutionHandler}.     //     // @param command the task to execute     // @throws RejectedExecutionException at discretion of     //         {@code RejectedExecutionHandler}, if the task     //         cannot be accepted for execution     // @throws NullPointerException if {@code command} is null     //    public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        //         // Proceed in 3 steps:         //         // 1. If fewer than corePoolSize threads are running, try to         // start a new thread with the given command as its first         // task.  The call to addWorker atomically checks runState and         // workerCount, and so prevents false alarms that would add         // threads when it shouldn't, by returning false.         // 翻译如下:         // 判断当前的线程数是否小于corePoolSize如果是,使用入参任务通过addWord方法创建一个新的线程,         // 如果能完成新线程创建exexute方法结束,成功提交任务         // 2. If a task can be successfully queued, then we still need         // to double-check whether we should have added a thread         // (because existing ones died since last checking) or that         // the pool shut down since entry into this method. So we         // recheck state and if necessary roll back the enqueuing if         // stopped, or start a new thread if there are none.         // 翻译如下:         // 在第一步没有完成任务提交;状态为运行并且能否成功加入任务到工作队列后,再进行一次check,如果状态         // 在任务加入队列后变为了非运行(有可能是在执行到这里线程池shutdown了),非运行状态下当然是需要         // reject;然后再判断当前线程数是否为0(有可能这个时候线程数变为了0),如是,新增一个线程;         // 3. If we cannot queue task, then we try to add a new         // thread.  If it fails, we know we are shut down or saturated         // and so reject the task.         // 翻译如下:         // 如果不能加入任务到工作队列,将尝试使用任务新增一个线程,如果失败,则是线程池已经shutdown或者线程池         // 已经达到饱和状态,所以reject这个他任务         //        int c = ctl.get();        // 工作线程数小于核心线程数        if (workerCountOf(c) corePoolSize) {            // 直接启动新线程,true表示会再次检查workerCount是否小于corePoolSize            if (addWorker(command, true))                return;            c = ctl.get();        }        // 如果工作线程数大于等于核心线程数        // 线程的的状态未RUNNING并且队列notfull        if (isRunning(c) workQueue.offer(command)) {            // 再次检查线程的运行状态,如果不是RUNNING直接从队列中移除            int recheck = ctl.get();            if (! isRunning(recheck) remove(command))                // 移除成功,拒绝该非运行的任务                reject(command);            else if (workerCountOf(recheck) == 0)                // 防止了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。                // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务                addWorker(null, false);        }        // 如果队列满了或者是非运行的任务都拒绝执行        else if (!addWorker(command, false))            reject(command);    }

java 什么情况下使用 并发队列

并发队列是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法来实现,该算法在Michael

Scott算法上进行了一些修改。

入队列

入队列就是将入队节点添加到队列的尾部。为了方便理解入队时队列的变化,以及head节点和tair节点的变化,每添加一个节点我就做了一个队列的快照图。

第一步添加元素1。队列更新head节点的next节点为元素1节点。又因为tail节点默认情况下等于head节点,所以它们的next节点都指向元素1节点。

第二步添加元素2。队列首先设置元素1节点的next节点为元素2节点,然后更新tail节点指向元素2节点。

第三步添加元素3,设置tail节点的next节点为元素3节点。

第四步添加元素4,设置元素3的next节点为元素4节点,然后将tail节点指向元素4节点。

通过debug入队过程并观察head节点和tail节点的变化,发现入队主要做两件事情,

第一是将入队节点设置成当前队列尾节点的下一个节点。

第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点不总是尾节点,理解这一点对于我们研究源码会非常有帮助。

上面的分析让我们从单线程入队的角度来理解入队过程,但是多个线程同时进行入队情况就变得更加复杂,因为可能会出现其他线程插队的情况。如果有一个线程正在入队,那么它必须先获取尾节点,然后设置尾节点的下一个节点为入队节点,但这时可能有另外一个线程插队了,那么队列的尾节点就会发生变化,这时当前线程要暂停入队操作,然后重新获取尾节点。让我们再通过源码来详细分析下它是如何使用CAS算法来入队的。

public boolean offer(E e) {    

          

   if (e == null) throw new NullPointerException();    

         

    //入队前,创建一个入队节点    

         

    Node/ee n = new Node/ee(e);    

  

   retry:    

          

    //死循环,入队不成功反复入队。    

          

    for (;;) {    

           

        //创建一个指向tail节点的引用    

           

       Node/ee t = tail;    

           

     //p用来表示队列的尾节点,默认情况下等于tail节点。    

           

      Node/ee p = t;    

           

      for (int hops = 0; ; hops++) {    

           

     //获得p节点的下一个节点。    

           

        Node/ee next = succ(p);    

           

     //next节点不为空,说明p不是尾节点,需要更新p后在将它指向next节点    

           

        if (next != null) {    

           

     //循环了两次及其以上,并且当前节点还是不等于尾节点    

           

        if (hops  HOPS  t != tail)    

           

            continue retry;    

           

            p = next;    

           

        }    

           

     //如果p是尾节点,则设置p节点的next节点为入队节点。    

           

         else if (p.casNext(null, n)) {    

           

        //如果tail节点有大于等于1个next节点,则将入队节点设置成tair节点,更新失败了也没关系,因为失败了表示有其他线程成功更新了tair节点。    

           

    if (hops = HOPS)    

           

           casTail(t, n); // 更新tail节点,允许失败    

           

        return true;    

           

         }    

           

        // p有next节点,表示p的next节点是尾节点,则重新设置p节点    

           

        else {    

           

          p = succ(p);    

           

        }    

           

     }    

         

  }    

          

}

从源代码角度来看整个入队过程主要做二件事情。

第一步定位尾节点。tail节点并不总是尾节点,所以每次入队都必须先通过tail节点来找到尾节点,尾节点可能就是tail节点,也可能是tail节点的next节点。代码中循环体中的第一个if就是判断tail是否有next节点,有则表示next节点可能是尾节点。获取tail节点的next节点需要注意的是p节点等于p的next节点的情况,只有一种可能就是p节点和p的next节点都等于空,表示这个队列刚初始化,正准备添加第一次节点,所以需要返回head节点。获取p节点的next节点代码如下

final Node/ee succ(Node/ee p) {    

        

     Node/ee next = p.getNext();    

 

       return (p == next) ? head : next;    

        

      }

第二步设置入队节点为尾节点。p.casNext(null, n)方法用于将入队节点设置为当前队列尾节点的next节点,p如果是null表示p是当前队列的尾节点,如果不为null表示有其他线程更新了尾节点,则需要重新获取当前队列的尾节点。

hops的设计意图。上面分析过对于先进先出的队列入队所要做的事情就是将入队节点设置成尾节点,doug lea写的代码和逻辑还是稍微有点复杂。那么我用以下方式来实现行不行?

public boolean offer(E e) {    

      

    if (e == null)    

           

       throw new NullPointerException();    

          

      Node/ee n = new Node/ee(e);    

          

          for (;;) {    

          

      Node/ee t = tail;    

         

       if (t.casNext(null, n)  casTail(t, n)) {    

          

             return true;    

          

     }    

         

     }    

         

}

让tail节点永远作为队列的尾节点,这样实现代码量非常少,而且逻辑非常清楚和易懂。但是这么做有个缺点就是每次都需要使用循环CAS更新tail节点。如果能减少CAS更新tail节点的次数,就能提高入队的效率,所以doug

lea使用hops变量来控制并减少tail节点的更新频率,并不是每次节点入队后都将 tail节点更新成尾节点,而是当

tail节点和尾节点的距离大于等于常量HOPS的值(默认等于1)时才更新tail节点,tail和尾节点的距离越长使用CAS更新tail节点的次数就会越少,但是距离越长带来的负面效果就是每次入队时定位尾节点的时间就越长,因为循环体需要多循环一次来定位出尾节点,但是这样仍然能提高入队的效率,因为从本质上来看它通过增加对volatile变量的读操作来减少了对volatile变量的写操作,而对volatile变量的写操作开销要远远大于读操作,所以入队效率会有所提升。

 private static final int HOPS = 1;

还有一点需要注意的是入队方法永远返回true,所以不要通过返回值判断入队是否成功。

4. 出队列

出队列的就是从队列里返回一个节点元素,并清空该节点对元素的引用。让我们通过每个节点出队的快照来观察下head节点的变化。

从上图可知,并不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head节点里的元素,而不会更新head节点。只有当head节点里没有元素时,出队操作才会更新head节点。这种做法也是通过hops变量来减少使用CAS更新head节点的消耗,从而提高出队效率。让我们再通过源码来深入分析下出队过程。

 public E poll() {    

        

    Node/ee h = head;    

       

    // p表示头节点,需要出队的节点    

        

      Node/ee p = h;    

        

      for (int hops = 0;; hops++) {    

        

         // 获取p节点的元素    

        

         E item = p.getItem();    

        

         // 如果p节点的元素不为空,使用CAS设置p节点引用的元素为null,如果成功则返回p节点的元素。    

        

         if (item != null  p.casItem(item, null)) {    

         

         if (hops = HOPS) {    

        

              //将p节点下一个节点设置成head节点    

        

              Node/ee q = p.getNext();    

        

              updateHead(h, (q != null) ? q : p);    

       

              }    

        

                  return item;    

 

              }    

        

        // 如果头节点的元素为空或头节点发生了变化,这说明头节点已经被另外一个线程修改了。那么获取p节点的下一个节点    

        

        Node/ee next = succ(p);    

        

        // 如果p的下一个节点也为空,说明这个队列已经空了    

       

        if (next == null) {    

        

       // 更新头节点。    

         

             updateHead(h, p);    

        

             break;    

         

             }    

        

      // 如果下一个元素不为空,则将头节点的下一个节点设置成头节点    

         

       p = next;    

         

      }    

        

          return null;    

        

      }

首先获取头节点的元素,然后判断头节点元素是否为空,如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走,如果不为空,则使用CAS的方式将头节点的引用设置成null,如果CAS成功,则直接返回头节点的元素,如果不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取头节点。

java中的队列都有哪些,有什么区别?

阻塞队列、普通队列,非阻塞队列。

阻塞队列与普通队列的而区别在于,当队列是空时,从队列中获取元素的操作会被阻塞,或则当队列是满的时,往队列中增加元素会被阻塞,试图从空的队列中取元素的线程或从满的队列中添加元素的线程同样会被阻塞。

队列的两个基本操作是inserting(插入)一个数据项,即把一个数据项放入队尾,另一个是removing(移除)一个数据项,即移除队头的数据项。这类似于电影爱好者排队买票时先排到队尾,然后到达队头买票后离开队列。

栈中的插入和移除数据项方法的命名是很标准,称为push和pop。队列的方法至今没有标准化的命名。“插入”可以称为put、add或enque,而“删除”可以叫delete、get或deque。插入数据项的队尾,也可以叫作back、tail或end。而移除数据项的队头,也可以叫head。下面将使用insert、remove、front和rear。

插入将值插入队尾,同时队尾箭头增加一,指向新的数据项。

java中怎么实现队列

public class QueueE {

private Object[] data=null;

private int maxSize; //队列容量

private int front; //队列头,允许删除

private int rear; //队列尾,允许插入

//构造函数

public Queue(){

this(10);

}

public Queue(int initialSize){

if(initialSize =0){

this.maxSize = initialSize;

data = new Object[initialSize];

front = rear =0;

}else{

throw new RuntimeException("初始化大小不能小于0:" + initialSize);

}

}

//判空

public boolean empty(){

return rear==front?true:false;

}

//插入

public boolean add(E e){

if(rear== maxSize){

throw new RuntimeException("队列已满,无法插入新的元素!");

}else{

data[rear++]=e;

return true;

}

}

//返回队首元素,但不删除

public E peek(){

if(empty()){

throw new RuntimeException("空队列异常!");

}else{

return (E) data[front];

}

}

//出队

public E poll(){

if(empty()){

throw new RuntimeException("空队列异常!");

}else{

E value = (E) data[front]; //保留队列的front端的元素的值

data[front++] = null; //释放队列的front端的元素

return value;

}

}

//队列长度

public int length(){

return rear-front;

}

}

在JAVA中怎么实现消息队列

java中的消息队列

消息队列是线程间通讯的手段:

import java.util.*

public class MsgQueue{

   private Vector queue = null;

   public MsgQueue(){

              queue = new   Vector();

   }

   public synchronized void send(Object o)

   {

      queue.addElement(o);

   }

   public synchronized Object recv()

{

     if(queue.size()==0)

        return null;

     Object o = queue.firstElement();

     queue.removeElementAt(0);//or queue[0] = null can also work

     return o;

}

}

因为java中是locked by object的所以添加synchronized 就可以用于线程同步锁定对象

可以作为多线程处理多任务的存放task的队列。他的client包括封装好的task类以及thread类

Java的多线程-线程间的通信2009-08-25 21:58

1. 线程的几种状态

线程有四种状态,任何一个线程肯定处于这四种状态中的一种:

1) 产生(New):线程对象已经产生,但尚未被启动,所以无法执行。如通过new产生了一个线程对象后没对它调用start()函数之前。

2) 可执行(Runnable):每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程并启动它。当一个线程处于可执行状态时,表示它可能正处于线程池中等待排排程器启动它;也可能它已正在执行。如执行了一个线程对象的start()方法后,线程就处于可执行状态,但显而易见的是此时线程不一定正在执行中。

3) 死亡(Dead):当一个线程正常结束,它便处于死亡状态。如一个线程的run()函数执行完毕后线程就进入死亡状态。

4) 停滞(Blocked):当一个线程处于停滞状态时,系统排程器就会忽略它,不对它进行排程。当处于停滞状态的线程重新回到可执行状态时,它有可能重新执行。如通过对一个线程调用wait()函数后,线程就进入停滞状态,只有当两次对该线程调用notify或notifyAll后它才能两次回到可执行状态。

2. class Thread下的常用函数函数

2.1 suspend()、resume()

1) 通过suspend()函数,可使线程进入停滞状态。通过suspend()使线程进入停滞状态后,除非收到resume()消息,否则该线程不会变回可执行状态。

2) 当调用suspend()函数后,线程不会释放它的“锁标志”。

例11:

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public synchronized void run(){

if(shareVar==0){

for(int i=0; i5; i++){

shareVar++;

if(shareVar==5){

this.suspend(); //(1)

}}}

else{

System.out.print(Thread.currentThread().getName());

System.out.println(" shareVar = " + shareVar);

this.resume(); //(2)

}}

}

public class TestThread{

public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.start(); //(5)

//t1.start(); //(3)

t2.start(); //(4)

}}

运行结果为:

t2 shareVar = 5

i. 当代码(5)的t1所产生的线程运行到代码(1)处时,该线程进入停滞状态。然后排程器从线程池中唤起代码(4)的t2所产生的线程,此时shareVar值不为0,所以执行else中的语句。

ii. 也许你会问,那执行代码(2)后为什么不会使t1进入可执行状态呢?正如前面所说,t1和t2是两个不同对象的线程,而代码(1)和(2)都只对当前对象进行操作,所以t1所产生的线程执行代码(1)的结果是对象t1的当前线程进入停滞状态;而t2所产生的线程执行代码(2)的结果是把对象t2中的所有处于停滞状态的线程调回到可执行状态。

iii. 那现在把代码(4)注释掉,并去掉代码(3)的注释,是不是就能使t1重新回到可执行状态呢?运行结果是什么也不输出。为什么会这样呢?也许你会认为,当代码(5)所产生的线程执行到代码(1)时,它进入停滞状态;而代码(3)所产生的线程和代码(5)所产生的线程是属于同一个对象的,那么就当代码(3)所产生的线程执行到代码(2)时,就可使代码(5)所产生的线程执行回到可执行状态。但是要清楚,suspend()函数只是让当前线程进入停滞状态,但并不释放当前线程所获得的“锁标志”。所以当代码(5)所产生的线程进入停滞状态时,代码(3)所产生的线程仍不能启动,因为当前对象的“锁标志”仍被代码(5)所产生的线程占有。

#p#2.2 sleep()

1) sleep ()函数有一个参数,通过参数可使线程在指定的时间内进入停滞状态,当指定的时间过后,线程则自动进入可执行状态。

2) 当调用sleep ()函数后,线程不会释放它的“锁标志”。

例12:

class TestThreadMethod extends Thread{

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public synchronized void run(){

for(int i=0; i3; i++){

System.out.print(Thread.currentThread().getName());

System.out.println(" : " + i);

try{

Thread.sleep(100); //(4)

}

catch(InterruptedException e){

System.out.println("Interrupted");

}}}

}

public class TestThread{public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.start(); (1)

t1.start(); (2)

//t2.start(); (3)

}}

运行结果为:

t1 : 0

t1 : 1

t1 : 2

t1 : 0

t1 : 1

t1 : 2

由结果可证明,虽然在run()中执行了sleep(),但是它不会释放对象的“锁标志”,所以除非代码(1)的线程执行完run()函数并释放对象的“锁标志”,否则代码(2)的线程永远不会执行。

如果把代码(2)注释掉,并去掉代码(3)的注释,结果将变为:

t1 : 0

t2 : 0

t1 : 1

t2 : 1

t1 : 2

t2 : 2

由于t1和t2是两个对象的线程,所以当线程t1通过sleep()进入停滞时,排程器会从线程池中调用其它的可执行线程,从而t2线程被启动。

例13:

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public synchronized void run(){

for(int i=0; i5; i++){

System.out.print(Thread.currentThread().getName());

System.out.println(" : " + i);

try{

if(Thread.currentThread().getName().equals("t1"))

Thread.sleep(200);

else

Thread.sleep(100);

}

catch(InterruptedException e){

System.out.println("Interrupted");

}}

}}

public class TestThread{public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.start();

//t1.start();

t2.start();

}}

运行结果为:

t1 : 0

t2 : 0

t2 : 1

t1 : 1

t2 : 2

t2 : 3

t1 : 2

t2 : 4

t1 : 3

t1 : 4

由于线程t1调用了sleep(200),而线程t2调用了sleep(100),所以线程t2处于停滞状态的时间是线程t1的一半,从从结果反映出来的就是线程t2打印两倍次线程t1才打印一次。

#p#2.3 yield()

1) 通过yield ()函数,可使线程进入可执行状态,排程器从可执行状态的线程中重新进行排程。所以调用了yield()的函数也有可能马上被执行。

2) 当调用yield ()函数后,线程不会释放它的“锁标志”。

例14:

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){super(name);

}

public synchronized void run(){for(int i=0; i4; i++){

System.out.print(Thread.currentThread().getName());

System.out.println(" : " + i);

Thread.yield();

}}

}

public class TestThread{public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.start();

t1.start(); //(1)

//t2.start(); (2)

}

}

运行结果为:

t1 : 0

t1 : 1

t1 : 2

t1 : 3

t1 : 0

t1 : 1

t1 : 2

t1 : 3

从结果可知调用yield()时并不会释放对象的“锁标志”。

如果把代码(1)注释掉,并去掉代码(2)的注释,结果为:

t1 : 0

t1 : 1

t2 : 0

t1 : 2

t2 : 1

t1 : 3

t2 : 2

t2 : 3

从结果可知,虽然t1线程调用了yield(),但它马上又被执行了。

2.4 sleep()和yield()的区别

1) sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

2) sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会。

例15:

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public void run(){

for(int i=0; i4; i++){

System.out.print(Thread.currentThread().getName());

System.out.println(" : " + i);

//Thread.yield(); (1)

/* (2) */

try{

Thread.sleep(3000);

}

catch(InterruptedException e){

System.out.println("Interrupted");

}}}

}

public class TestThread{

public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.setPriority(Thread.MAX_PRIORITY);

t2.setPriority(Thread.MIN_PRIORITY);

t1.start();

t2.start();

}

}

运行结果为:

t1 : 0

t1 : 1

t2 : 0

t1 : 2

t2 : 1

t1 : 3

t2 : 2

t2 : 3

由结果可见,通过sleep()可使优先级较低的线程有执行的机会。注释掉代码(2),并去掉代码(1)的注释,结果为:

t1 : 0

t1 : 1

t1 : 2

t1 : 3

t2 : 0

t2 : 1

t2 : 2

t2 : 3

可见,调用yield(),不同优先级的线程永远不会得到执行机会。

2.5 join()

使调用join()的线程执行完毕后才能执行其它线程,在一定意义上,它可以实现同步的功能。

例16:

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public void run(){

for(int i=0; i4; i++){

System.out.println(" " + i);

try{

Thread.sleep(3000);

}

catch(InterruptedException e){

System.out.println("Interrupted");

}

}

}

}

public class TestThread{

public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

t1.start();

try{

t1.join();

}

catch(InterruptedException e){}

t1.start();

}

}

运行结果为:

1

2

3

1

2

3

#p#3. class Object下常用的线程函数

wait()、notify()和notifyAll()这三个函数由java.lang.Object类提供,用于协调多个线程对共享数据的存取。

3.1 wait()、notify()和notifyAll()

1) wait()函数有两种形式:第一种形式接受一个毫秒值,用于在指定时间长度内暂停线程,使线程进入停滞状态。第二种形式为不带参数,代表waite()在notify()或notifyAll()之前会持续停滞。

2) 当对一个对象执行notify()时,会从线程等待池中移走该任意一个线程,并把它放到锁标志等待池中;当对一个对象执行notifyAll()时,会从线程等待池中移走所有该对象的所有线程,并把它们放到锁标志等待池中。

3) 当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。

例17:

下面,我们将对例11中的例子进行修改

class TestThreadMethod extends Thread{

public static int shareVar = 0;

public TestThreadMethod(String name){

super(name);

}

public synchronized void run(){

if(shareVar==0){

for(int i=0; i10; i++){

shareVar++;

if(shareVar==5){

try{

this.wait(); //(4)

}

catch(InterruptedException e){}

}

}

}

if(shareVar!=0){

System.out.print(Thread.currentThread().getName());

System.out.println(" shareVar = " + shareVar);

this.notify(); //(5)

}

}

}

public class TestThread{

public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

TestThreadMethod t2 = new TestThreadMethod("t2");

t1.start(); //(1)

//t1.start(); (2)

t2.start(); //(3)

}}

运行结果为:

t2 shareVar = 5

因为t1和t2是两个不同对象,所以线程t2调用代码(5)不能唤起线程t1。如果去掉代码(2)的注释,并注释掉代码(3),结果为:

t1 shareVar = 5

t1 shareVar = 10

这是因为,当代码(1)的线程执行到代码(4)时,它进入停滞状态,并释放对象的锁状态。接着,代码(2)的线程执行run(),由于此时shareVar值为5,所以执行打印语句并调用代码(5)使代码(1)的线程进入可执行状态,然后代码(2)的线程结束。当代码(1)的线程重新执行后,它接着执行for()循环一直到shareVar=10,然后打印shareVar。

#p#3.2 wait()、notify()和synchronized

waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

例18:

class TestThreadMethod extends Thread{

public int shareVar = 0;

public TestThreadMethod(String name){

super(name);

new Notifier(this);

}

public synchronized void run(){

if(shareVar==0){

for(int i=0; i5; i++){

shareVar++;

System.out.println("i = " + shareVar);

try{

System.out.println("wait......");

this.wait();

}

catch(InterruptedException e){}

}}

}

}

class Notifier extends Thread{

private TestThreadMethod ttm;

Notifier(TestThreadMethod t){

ttm = t;

start();

}

public void run(){

while(true){

try{

sleep(2000);

}

catch(InterruptedException e){}

/*1 要同步的不是当前对象的做法 */

synchronized(ttm){

System.out.println("notify......");

ttm.notify();

}}

}

}

public class TestThread{

public static void main(String[] args){

TestThreadMethod t1 = new TestThreadMethod("t1");

t1.start();

}

}

运行结果为:

i = 1

wait......

notify......

i = 2

wait......

notify......

i = 3

wait......

notify......

i = 4

wait......

notify......

i = 5

wait......

notify......

4. wait()、notify()、notifyAll()和suspend()、resume()、sleep()的讨论

4.1 这两组函数的区别

1) wait()使当前线程进入停滞状态时,还会释放当前线程所占有的“锁标志”,从而使线程对象中的synchronized资源可被对象中别的线程使用;而suspend()和sleep()使当前线程进入停滞状态时不会释放当前线程所占有的“锁标志”。

2) 前一组函数必须在synchronized函数或synchronized block中调用,否则在运行时会产生错误;而后一组函数可以non-synchronized函数和synchronized block中调用。

4.2 这两组函数的取舍

Java2已不建议使用后一组函数。因为在调用suspend()时不会释放当前线程所取得的“锁标志”,这样很容易造成“死锁”。

关于java队列使用和队列方法 java的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

The End

发布于:2023-01-14,除非注明,否则均为首码项目网原创文章,转载请注明出处。