zoukankan      html  css  js  c++  java
  • 多线程控制线程的等待和唤醒

    最近做注册的时候,发现同步发送注册邮件多了一个耗时,就想到异步处理邮件发送,直接返回成功给用户。

    设计了一个线程,用来发送邮件,需要发送的时候再来唤醒就好了,但是对于没有系统了解过多线程的我来说,想的太简单了。

    public class MailSendThread  extends Thread{
    
        private static Logger    log    = Logger.getLogger(MailSendThread.class);
        public final static long mail_user_time = 48 * 1800000L;//一天运行一次
        public void run(){
            log.error("MailSendThread is running!");
            try {
                MailUtil.sendMailInfo();
                sleep(mail_user_time);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                log.error("MailSendThread run error", e);
            }
        }
    }
    private static MailSendThread mailSender = new MailSendThread();
    public static void notifyMailSender(){
            mailSender.notify();
        }

    多傻的代码!!!!

    仔细研究后发现,首先sleep只能用作线程内部等待使用,指定时间段内休眠,不能外部唤醒;

    其次,nofity方法必须依托与一个线程正在等待的对象,就是锁住的对象,不能直接对线程操作,因为wait函数需要一个锁;

    研究了一下生产者和消费者,这里用到的锁是对象实例

    package com.thread;
    /**
     * 生产者,制造馒头
     * @author huangjc
     *
     */
    public class A extends Thread{
        private C c;
        public A(C c){
            this.c = c;
        }
        public void run(){
            int i=0;
            while(i < 10){
                try {
                    c.add();
                    i++;
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    package com.thread;
    
    /**
     * 消费者,消耗馒头
     * @author huangjc
     *
     */
    public class B extends Thread{
    
        private C c;
        public B(C c){
            this.c = c;
        }
        public void run(){
            int i=0;
            while(i<10){
                i++;
                c.minus();
            }
        }
        public static void main(String[] args) {
            C c = new C();
            new Thread(new B(c)).start();
            new Thread(new A(c)).start();
        }
    }

    具体的锁就在下面

    package com.thread;
    
    public class C {
    
        private static int i=0;
        public synchronized  void add() throws InterruptedException{
            System.out.println("增加馒头:现在有"+(i)+"个馒头");
            if(i == 5){
                System.out.println("馒头够多了,赶快吃吧!");
                wait();
            }else{
                i++;
                System.out.println("放了一个馒头");
                notify();//唤醒消耗线程
            }
        }
        public synchronized  void minus(){
            System.out.println("取出馒头:现在有"+(i)+"个馒头");
            if (i == 0){
                System.out.println("馒头没有了,等会儿吧!");
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }else{
                i--;
                System.out.println("取出一个馒头");
                notify();
            }
        }
    }

    这里的notify和wait方法锁住的都是在B的main方法中创建的实例对象

    所以问题又来了,我需要在主线程中启动分线程,所以不能给两个线程分配同一个实例对象,如果每次都重新分配一个实例对象,再创建一个分线程,是不是太愚蠢了;

    对于静态方法和非静态方法的同步问题,静态方法锁住的是Class,例如A.class,而非静态方法锁住的是当前实例。

    所以静态方法和非静态方法并不适用同一个锁。一个类中的所有静态方法使用同一个锁。

    所以,是否可以考虑使用静态方法来发送邮件,锁住类本身,然后再主线程中依托类本身进行唤醒;

    太坑爹了,完全不是这回事,查了API后发现,wait方法是Object对象的,那不就是说必须依托于一个对象实例吗?

    再来看api中关于thread类下面的方法,其中有个

    interrupt()
    interrupt
    
    public void interrupt()
    中断线程。
    如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。
    
    如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
    
    如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
    
    如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。
    
    如果以前的条件都没有保存,则该线程的中断状态将被设置。
    
    中断一个不处于活动状态的线程不需要任何作用。
    
    抛出:
    SecurityException - 如果当前线程无法修改该线程

    线程A正在使用sleep()暂停着: Thread.sleep(100000);
    如果要取消他的等待状态,可以在正在执行的线程里(比如这里是B)调用
    a.interrupt();
    令线程A放弃睡眠操作,这里a是线程A对应到的Thread实例
    执行interrupt()时,并不需要获取Thread实例的锁定.任何线程在任何时刻,都可以调用其他线程interrupt().当sleep中的线程被调用interrupt()时,就会放弃暂停的状态.并抛出InterruptedException.丢出异常的,是A线程

    package com.thread;
    
    /**
     * 消费者,消耗馒头
     * @author huangjc
     *
     */
    public class B extends Thread{
    
        public void run(){
            while(true){
                System.out.println("线程执行");
                try {
                    System.out.println("睡一会儿");
                    sleep(180000);
                } catch (InterruptedException e) {
                    System.out.println("cao ,谁在打扰我睡觉呢!");
                }
            }
        }
        public static void main(String[] args) {
            B b = new B()
    ;        b.start();
            int i=0;
            while(i < 100000000){
                i++;
            }
            System.out.println("别睡了");
            b.interrupt();
        }
    }

    执行main函数

    线程执行
    睡一会儿
    别睡了
    cao ,谁在打扰我睡觉呢!
    线程执行
    睡一会儿
  • 相关阅读:
    HTML5的data-*自己定义属性
    Cocos2d-X直接使用OpenGL接口
    经典递推问题错排公式分析
    一步一步跟我学习lucene(18)---lucene索引时join和查询时join使用演示样例
    Linux下安装JRE和Eclipse IDE for C/C++ Developers
    Android 官方推荐 : DialogFragment 创建对话框
    hibernate 继承映射
    [Swift通天遁地]七、数据与安全-(7)创建文件浏览器:以可视化的方式浏览沙箱文件
    [Swift通天遁地]七、数据与安全-(6)管理文件夹和创建并操作文件
    [Swift]LeetCode398. 随机数索引 | Random Pick Index
  • 原文地址:https://www.cnblogs.com/yangchengInfo/p/3640429.html
Copyright © 2011-2022 走看看