线程的挂起和唤醒

1. 线程的挂起和唤醒
      挂起实际上是让线程进入“非可执行”状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行;在线程挂起后,可以通过重新唤醒线程来使之恢复运行。

挂起的原因可能是如下几种情况:
     (1)通过调用sleep()方法使线程进入休眠状态,线程在指定时间内不会运行。
     (2)通过调用join()方法使线程挂起,使自己等待另一个线程的结果,直到另一个线程执行完毕为止。
     (3)通过调用wait()方法使线程挂起,直到线程得到了notify()和notifyAll()消息,线程才会进入“可执行”状态。
     (4)使用suspend挂起线程后,可以通过resume方法唤醒线程。
      虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成死锁,因此,这两个方法被标识为  deprecated(抗议)标记 ,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。

      调用sleep()、yield()、suspend()的时候并没有被释放锁

        yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程

      调用wait()的时候释放当前对象的锁

      wait()方法表示,放弃当前对资源的占有权,一直等到有线程通知,才会运行后面的代码。

      notify()方法表示,当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,然后继续运行wait()后面的语句。

      notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。

       当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。 


2.
等待和锁实现资源竞争

      等待机制与锁机制是密切关联的,对于需要竞争的资源,首先用synchronized确保这段代码只能一个线程执行,可以再设置一个标志位condition判断该资源是否准备好,如果没有,则该线程释放锁,自己进入等待状态,直到接收到notify,程序从wait处继续向下执行。
  1. synchronized(obj) {
  2.   while(!condition) {
  3.    obj.wait();
  4.   }
  5.   obj.doSomething();
  6. }
以上程序表示只有一个线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A释放该锁,进入wait()。

      在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:
  1. synchronized(obj) {
  2.  condition = true;
  3.  obj.notify();
  4. }
需要注意的是:
  # 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。
  # 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。
  # 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
  #  如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)
  # obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
  # 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。


例1:
单个线程对多个线程的唤醒

      假设只有一个Game对象,但有3个人要玩,由于只有一个游戏资源,必须必然依次玩。
  1. /**
  2.  * 玩游戏的人.
  3.  * @version V1.0 ,2011-4-8
  4.  * @author xiahui
  5.  */
  6. public class Player implements Runnable {
  7.     private final int id;
  8.     private Game game;

  9.     public Player(int id, Game game) {
  10.         this.id = id;
  11.         this.game = game;
  12.     }


  13.     public String toString() {
  14.         return "Athlete<" + id + ">";
  15.     }

  16.     public int hashCode() {
  17.         return new Integer(id).hashCode();
  18.     }
  19.     
  20.     public void playGame() throws InterruptedException{
  21.         System.out.println(this.toString() + " ready!");
  22.         game.play(this);
  23.     }

  24.     public void run() {
  25.         try {
  26.             playGame();
  27.         } catch (InterruptedException e) {
  28.             System.out.println(this + " quit the game");
  29.         }
  30.     }
  31. }
游戏类,只实例化一个
  1. import java.util.HashSet;
  2. import java.util.Iterator;
  3. import java.util.Set;

  4. /**
  5.  * 游戏类.
  6.  * @version V1.0 ,2011-4-8
  7.  * @author xiahui
  8.  */
  9. public class Game implements Runnable {
  10.     private boolean start = false;

  11.     public void play(Player player) throws InterruptedException {
  12.         synchronized (this) {
  13.             while (!start)
  14.                 wait();
  15.             if (start)
  16.                 System.out.println(player + " have played!");
  17.         }
  18.     }

  19.     //通知所有玩家
  20.     public synchronized void beginStart() {
  21.         start = true;
  22.         notifyAll();
  23.     }

  24.     public void run() {
  25.         start = false;
  26.         System.out.println("Ready......");
  27.         System.out.println("Ready......");
  28.         System.out.println("game start");
  29.         beginStart();//通知所有玩家游戏准备好了
  30.     }

  31.     public static void main(String[] args) {
  32.         Set<Player> players = new HashSet<Player>();
  33.         //实例化一个游戏
  34.         Game game = new Game();
  35.         
  36.         //实例化3个玩家
  37.         for (int i = 0; i < 3; i++)
  38.             players.add(new Player(i, game));
  39.         
  40.         //启动3个玩家
  41.         Iterator<Player> iter = players.iterator();
  42.         while (iter.hasNext())
  43.             new Thread(iter.next()).start();
  44.         Thread.sleep(100);
  45.         
  46.         //游戏启动
  47.         new Thread(game).start();
  48.     }
  49. }
程序先启动玩家,三个玩家竞争玩游戏,但只能有一个进入play,其他二个等待,进入的玩家发现游戏未准备好,所以wait,等游戏准备好后,依次玩。
运行结果
  1. Athlete<0> ready!
  2. Athlete<1> ready!
  3. Athlete<2> ready!
  4. Ready......
  5. Ready......
  6. game start
  7. Athlete<2> have played!
  8. Athlete<1> have played!
  9. Athlete<0> have played!

3.一次唤醒一个线程
      一次唤醒所有玩家,但只有一个玩家能玩,不如一个一个唤醒
将上面的代码修改如下
  1.     public void play(Player player) throws InterruptedException {
  2.         synchronized (this) {
  3.             while (!start)
  4.                 wait();
  5.             if (start){
  6.                 System.out.println(player + " have played!");
  7.                 notify();//玩完后,通知下一个玩家来玩
  8.             }
  9.         }
  10.     }
  11.     //通知一个玩家
  12.     public synchronized void beginStart() {
  13.         start = true;
  14.         notify();
  15.     }

4.suspend挂起
      该方法已不建议使用,例子如下
例2:suspend方法进行挂起和唤醒
  1. /**
  2.  * suspend方法进行挂起和唤醒.
  3.  * @version V1.0 ,2011-3-27 
  4.  * @author xiahui
  5.  */
  6. public class SuspendThread implements Runnable{
  7.     public void run() {
  8.         try {
  9.             Thread.sleep(10);
  10.         } catch (Exception e) {
  11.             System.out.println(e);
  12.         }
  13.         for (int i = 0; i <= 1; i ) {
  14.             System.out.println(Thread.currentThread().getName() ":" i);
  15.         }
  16.     }

  17.     public static void main(String args[]) throws Exception {
  18.         Thread th1 = new Thread(new SuspendThread(),"thread1");
  19.         Thread th2 = new Thread(new SuspendThread(),"thread2");
  20.         System.out.println("Starting " th1.getName() "...");
  21.         th1.start();
  22.         System.out.println("Suspending " th1.getName() "...");
  23.         //Suspend the thread.
  24.         th1.suspend();
  25.         th2.start();
  26.         th2.join();
  27.         // Resume the thread.
  28.         th1.resume();
  29.     }
  30. }
运行结果
  1. Starting thread1...
  2. Suspending thread1...
  3. thread2:0
  4. thread2:1
  5. thread1:0
  6. thread1:1
注意:
      如果注释掉//th2.join();则thread2运行后,主线程会直接执行thread1的resume,运行结果可能会是
  1. Starting thread1...
  2. Suspending thread1...
  3. thread1:0
  4. thread1:1
  5. thread2:0
  6. thread2:1

多线程争取一个对象,某些情况下, 可以替代static静态变量


import java.util.*;

class Widget...{}
class WidgetMaker extends Thread...{
    List<Widget> finishedWidgets=new ArrayList<Widget>();
    public void run()...{
        try...{
            while(true)...{
                Thread.sleep(5000);//act busy
                Widget w=new Widget();
                //也就是说需要5秒钟才能新产生一个Widget,这决定了一定要用notify而不是notifyAll
                //因为上面两行代码不是同步的,如果用notifyAll则所有线程都企图冲出wait状态
                //第一个线程得到了锁,并取走了Widget(这个过程的时间小于5秒,新的Widget还没有生成)
                //并且解开了锁,然后第二个线程获得锁(因为用了notifyAll其他线程不再等待notify语句
                //,而是等待finishedWidgets上的锁,一旦锁放开了,他们就会竞争运行),运行
                //finishedWidgets.remove(0),但是由于finishedWidgets现在还是空的,
                //于是产生异常
                //***********这就是为什么下面的那一句不能用notifyAll而是要用notify
                                
                synchronized(finishedWidgets)...{
                    finishedWidgets.add(w);
                    finishedWidgets.notify(); //这里只能是notify而不能是notifyAll
                }
            }
        }
        catch(InterruptedException e)...{}
    }
    
    public Widget waitForWidget()...{
        synchronized(finishedWidgets)...{
            if(finishedWidgets.size()==0)...{
                try...{
                    finishedWidgets.wait();
                }
                catch(InterruptedException e)
                ...{}
            }
            return finishedWidgets.remove(0);
        }
    }
}
public class WidgetUser extends Thread...{
    private WidgetMaker maker;
    public WidgetUser(String name,WidgetMaker maker)...{
        super(name);
        this.maker=maker;
    }
    public void run()...{
        Widget w=maker.waitForWidget();
        System.out.println(getName()+"got a widget");
    }
   

    public static void main(String[] args) ...{
        WidgetMaker maker=new WidgetMaker();
        maker.start();
        new WidgetUser("Lenny",maker).start();
        new WidgetUser("Moe",maker).start();
        new WidgetUser("Curly",maker).start();

    }

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Python 中,可以使用 `threading` 模块来创建和管理线程。 下面是一个线程挂起唤醒的例子: ``` import threading import time # 创建线程 thread = threading.Thread(target=print, args=('线程已启动',)) # 启动线程 thread.start() # 挂起线程 thread.suspend() # 等待 5 秒钟 time.sleep(5) # 唤醒线程 thread.resume() # 等待线程结束 thread.join() ``` 在这个例子中,我们创建了一个新的线程,并使用 `thread.start()` 方法启动它。然后,我们使用 `thread.suspend()` 方法挂起线程,并使用 `thread.resume()` 方法唤醒它。最后,我们使用 `thread.join()` 方法等待线程结束。 注意:在 Python 3.8 及更高版本中,`thread.suspend()` 和 `thread.resume()` 方法已被弃用,建议使用其他线程同步机制来替代。 ### 回答2: 线程挂起唤醒是多线程编程中常用的技术,用于控制线程的执行状态,实现线程之间的同步。 一个典型的线程挂起唤醒的例子是生产者和消费者模型。在这个模型中,生产者线程负责生产产品,消费者线程负责消费产品。为了实现线程之间的同步,我们需要在产品缓冲区为空时,挂起消费者线程,直到有新的产品被生产出来;当产品缓冲区已满时,挂起生产者线程,直到有产品被消费掉。 下面是一个简化的生产者和消费者模型的代码示例: ```python import threading buffer = [] # 产品缓冲区 buffer_size = 5 # 缓冲区大小 lock = threading.Lock() # 用于保护缓冲区的互斥锁 can_produce = threading.Event() # 用于通知生产者线程可以开始生产 can_consume = threading.Event() # 用于通知消费者线程可以开始消费 class Producer(threading.Thread): def run(self): global buffer while True: lock.acquire() # 获取互斥锁 if len(buffer) < buffer_size: # 缓冲区未满,可以生产产品 buffer.append(1) if len(buffer) == 1: # 缓冲区从空变为非空,通知消费者线程可以开始消费 can_consume.set() print("生产者生产了一个产品,当前缓冲区中有%d个产品" % len(buffer)) if len(buffer) == buffer_size: # 缓冲区已满,挂起生产者线程 can_produce.clear() lock.release() # 释放互斥锁 class Consumer(threading.Thread): def run(self): global buffer while True: lock.acquire() # 获取互斥锁 if len(buffer) > 0: # 缓冲区非空,可以消费产品 buffer.pop() if len(buffer) == buffer_size - 1: # 缓冲区从满变为非满,通知生产者线程可以开始生产 can_produce.set() print("消费者消费了一个产品,当前缓冲区中有%d个产品" % len(buffer)) if len(buffer) == 0: # 缓冲区为空,挂起消费者线程 can_consume.clear() lock.release() # 释放互斥锁 # 创建生产者和消费者线程 producer = Producer() consumer = Consumer() # 启动线程 producer.start() consumer.start() ``` 在这个例子中,使用了互斥锁来保护缓冲区的访问,确保同一时间只有一个线程可以对缓冲区进行操作。通过`threading.Event`来实现线程挂起唤醒的功能。当缓冲区由空变为非空时,生产者线程通过`can_consume.set()`通知消费者线程可以开始消费;当缓冲区由满变为非满时,消费者线程通过`can_produce.set()`通知生产者线程可以开始生产。同时,生产者线程通过`can_produce.clear()`挂起自己,消费者线程通过`can_consume.clear()`挂起自己,直到有合适的条件触发唤醒操作。 这样,生产者和消费者线程就可以在合适的时间挂起唤醒,实现线程之间的同步,保证生产者和消费者的顺序执行。 ### 回答3: 线程是操作系统中最小的执行单位,在同一个进程中可以同时执行多个线程线程挂起唤醒是控制线程执行顺序的一种方式。 假设有一个需求:有两个线程,一个线程用于打印奇数,一个线程用于打印偶数。我们可以通过线程挂起唤醒来实现这个需求。 具体步骤如下: 1. 创建两个线程对象:oddThread和evenThread。 2. 在oddThread中,使用一个循环来打印奇数。如果打印完一个奇数后,就将evenThread唤醒,并将自己挂起。 3. 在evenThread中,使用一个循环来打印偶数。如果打印完一个偶数后,就将oddThread唤醒,并将自己挂起。 4. 控制两个线程的执行顺序,在主线程中,先挂起oddThread,然后启动evenThread。 具体代码如下: ```python import threading class PrintNumberThread(threading.Thread): def __init__(self, num, lock, thread_to_resume, thread_to_suspend): super(PrintNumberThread, self).__init__() self.num = num self.lock = lock self.thread_to_resume = thread_to_resume self.thread_to_suspend = thread_to_suspend def run(self): while self.num <= 10: self.lock.acquire() print(self.num) self.num += 2 self.thread_to_resume.resume() self.thread_to_suspend.suspend() self.lock.release() if __name__ == "__main__": lock = threading.Lock() suspend_event = threading.Event() suspend_event.set() # 初始状态为挂起线程 thread1 = PrintNumberThread(1, lock, suspend_event, suspend_event) thread2 = PrintNumberThread(2, lock, suspend_event, suspend_event) thread1.start() suspend_event.clear() thread2.start() ``` 以上代码中,我们创建了两个PrintNumberThread线程,分别用于打印奇数和偶数。在run方法中,使用lock保证了每次只有一个线程打印,并且通过resume和suspend方法来实现挂起唤醒操作。 在主线程中,我们先将oddThread挂起,然后启动evenThread,两个线程将交替执行,并打印出1到10之间的奇数和偶数。 以上就是一个线程挂起唤醒的例子。当然,在实际应用中还可以使用更为复杂的方式来控制线程挂起唤醒,具体方式根据实际需求而定。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值