Java多线程_定时器和单例模式
admin
2024-03-03 01:11:38
0

文章目录

  • 一、定时器Timer
    • 1.Timer类的方法使用
      • 1.1 schedule(TimerTask task,Date time)
      • 1.2 public void cancel(),针对TimerThread
      • 1.3 schedule(TimerTask task,Date firstTime,long period)
      • 1.4 public boolean cancel(),针对TimerTask
      • 1.5 scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
  • 二、单例模式与多线程
    • 1.立即加载/饿汉模式
    • 2.延迟加载/懒汉模式
    • 3.序列化和反序列化实现单例模式
    • 4.使用static代码块实现单例模式
    • 5.使用enum枚举数据类型实现单例模式
  • 总结

一、定时器Timer

1.Timer类的方法使用

1.1 schedule(TimerTask task,Date time)

作用是在指定的日期执行一次某一任务。

  1. 创建timer.task包,在包下创建MyTask 类

    package timer.task;import java.util.TimerTask;public class MyTask extends TimerTask {@Overridepublic void run() {System.out.println("任务执行了,时间为:"+System.currentTimeMillis());}
    }
  2. 创建timer.test包,在包下创建Test1类

    package timer.test;import timer.task.MyTask;
    import java.util.Date;
    import java.util.Timer;public class Test1 {public static void main(String[] args) throws InterruptedException {long nowTime = System.currentTimeMillis();System.out.println("当前时间为:"+nowTime);long scheduleTime = nowTime + 10000;System.out.println("计划时间为:"+scheduleTime);MyTask task = new MyTask();Timer timer = new Timer();Thread.sleep(1000);/**1.调用Timer实例的schedule方法,将MyTask实例和将转换成Date对象的scheduleTime一起传入*/timer.schedule(task,new Date(scheduleTime));}
    }

    即先预先将计划的时间打印出来,然后当执行到schedule方法后,时间达到指定时间时执行task任务,task此时打印出来的时间会跟前者打印出来的时间相同。
    任务虽然执行完了,但是进程还未被销毁,说明内部有非守护线程正在执行,即Timer的线程还在运行。

1.2 public void cancel(),针对TimerThread

作用是终止此计时器,丢弃当前所有已安排的任务。这不会干扰当前正在执行的任务。一旦终止计时器,那么它的执行线程也会终止,并且无法根据它安排更多的任务。
即安排任务的mainLoop方法跳出死循环,对执行任务的线程不会有影响。

  1. 创建timer.test包,在包下创建Test2类

    package timer.test;import timer.task.MyTask;import java.util.Date;
    import java.util.Timer;public class Test2 {public static void main(String[] args) throws InterruptedException {long nowTime = System.currentTimeMillis();System.out.println("当前时间为:"+nowTime);long scheduleTime = (nowTime + 15000);System.out.println("计划时间为:"+scheduleTime);MyTask myTask = new MyTask();Timer timer = new Timer();timer.schedule(myTask,new Date(scheduleTime));Thread.sleep(18000);timer.cancel();}
    }
    

    MyTask引用使用schedule方法时创建MyTask即可,调用timer.cancel()后实现TimerThread线程销毁。即18秒后,TimerThread线程安排任务的mainLoop方法跳出死循环,最后TimerThread线程被销毁。但是对执行任务的线程不会有影响。此时如果MyTask任务正在运行,则会等到MyTask任务结束,程序才会退出。

1.3 schedule(TimerTask task,Date firstTime,long period)

  1. 作用是在指定日期之后按指定的间隔周期无限循环的执行某一任务。
    跟1.1几乎类似,就是将Timer中的schdule方法的参数修改即可。

1.4 public boolean cancel(),针对TimerTask

  1. 作用是将自身从任务队列中清除。即将当前TimerTask任务的state改为CANCELLED。
    即MyTask类继承TimerTask类后,调用MyTask类中的cancel()即可。

1.5 scheduleAtFixedRate(TimerTask task,Date firstTime,long period)

  1. 该方法与schedule方法的主要区别是在于有没有追赶特性。
    追赶特性就是说scheduleAtFixedRate在调用时,如果使用指定日期并且现在时间比指定时间晚,此时该方法就会将晚了的时间追赶回来,即先不等待period的时间,而是直接执行方法,直到将晚了的时间追赶回来为止,然后就可以再继续等待period这个周期后才执行任务。
    它在第一次任务开始后计时,到间隔时间后执行第二次任务(执行时间小于间隔时间)。如果大于,则等第一次任务执行完,立即执行第二次任务。

二、单例模式与多线程

1.立即加载/饿汉模式

立即加载就是使用类的时候已经将对象创建完毕。常见的实现办法就是new实例化。也称饿汉模式。
实现方式为在类内部新建一个对象实例,外面直接通过一个方法获取这个对象实例。他的缺点是不能有其他实例变量,如果方法没有同步,有可能出现非线程安全问题。

  1. 创建包singleton.test,在包下创建类MyObject

    package singleton.test;public class MyObject {//立即加载方法 == 饿汉模式private static MyObject myObject = new MyObject();private MyObject(){}public static MyObject getInstance(){return myObject;}
    }
    

    即使用静态内置类实现单例模式。

  2. 创建包singleton.thread,在包下创建类MyThread

    package singleton.thread;import singleton.test.MyObject;public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
    }
    
  3. 创建包singleton.test.run,在包下创建类Run

    package singleton.test.run;import singleton.thread.MyThread;public class Run {public static void main(String[] args) {MyThread myThread1 = new MyThread();MyThread myThread2 = new MyThread();MyThread myThread3 = new MyThread();myThread1.start();myThread2.start();myThread3.start();}
    }

    输出的结果是相同的hashCode,证明是同一个实例(即单例),也就是该实例只能通过该对象的getInstance()方法来获取,并且不能new,以此来确保单例。

2.延迟加载/懒汉模式

延迟加载就是调用get()方法时,实例才被工厂创建。常见的方法就是在get()方法中进行new实例化。也称懒汉模式。

  1. 将上面代码的MyObject类修改如下即可。
    package singleton.test;public class MyObject {//延迟加载private static MyObject myObject;private MyObject(){}synchronized public static MyObject getInstance(){if(myObject==null)myObject = new MyObject();return myObject;}
    }
    即在需要的时候才创建。但在多线程的环境中会出现取出多个实例的情况,所以直接在方法加上synchronized关键字,但是会导致方法效率低下。
  2. 延迟加载/懒汉模式的解决方案
    使用DCL双检查锁机制
    package singleton.test;public class MyObject {//延迟加载private volatile static MyObject myObject;private MyObject(){}public static MyObject getInstance(){if(myObject==null)synchronized (MyObject.class) {if (myObject == null)myObject = new MyObject();}return myObject;}
    }
    volatile修饰MyObject ,作用是使该变量在多个线程间可见,从而确保在MyObject实例被new出来后能检测到myObject!=null。并且防止myObject = new MyObject()代码重排序。
    myObject = new MyObject()包含三个步骤,
    1)memory=allocate(); //分配对象的内存空间
    2) ctorInstance(memory); //初始化对象
    3) myObject = memory; //设置instance指向刚分配的内存地址
    如果没有禁止重排序,则2跟3可能调换顺序,即先执行3,然后此时myObject已经有对象,即值不是null了,则可能出现下一个调用getInstance方法的线程直接return myObject了,但是此时的myObject并没有初始化,因此出错。
    双检查锁的作用是提高MyObject对象实例被new后的效率,即后面整个方法都不需要再同步执行了。如果不使用双检查锁,则每次进入方法判断时都需要去同步执行,降低效率。

3.序列化和反序列化实现单例模式

  1. 创建singleton.entity包,在包下创建UserInfo 类

    package singleton.entity;public class UserInfo {}
  2. 创建singleton.test1包,在包下创建MyObject 类

    package singleton.test1;import singleton.entity.UserInfo;import java.io.Serializable;public class MyObject implements Serializable {private static final long serialVersionUID = 1L;public static UserInfo userInfo = new UserInfo();private static MyObject myObject = new MyObject();private MyObject(){}public static MyObject getInstance(){return myObject;}protected Object readResolve(){System.out.println("调用了readResolve方法!");return MyObject.myObject;}
    }

    如果将单例对象进行序列化,使用默认的反序列化行为取出的对象是多例的。
    上面的readResolve()方法作用是在反序列化时不创建新的MyObject对象,而是复用原有的MyOject对象。即没有使用该方法,反序列化时则会出现多个MyOject对象。

  3. 创建singleton.test1包,在包下创建SaveAndRead 类

    package singleton.test1;import java.io.*;public class SaveAndRead {public static void main(String[] args) {try {MyObject myObject = MyObject.getInstance();System.out.println("序列化-myObject="+myObject.hashCode()+" userInfo="+myObject.userInfo.hashCode());FileOutputStream fos = new FileOutputStream(new File("myObject-File.txt"));ObjectOutputStream oos = new ObjectOutputStream(fos);//将myObject存入myObject-File.txt文件中oos.writeObject(myObject);} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}try {FileInputStream fis = new FileInputStream(new File("myObject-File.txt"));ObjectInputStream ois = new ObjectInputStream(fis);//将myObject从myObject-File.txt文件中取出MyObject myObject = (MyObject)ois.readObject();System.out.println("序列化-myObject="+myObject.hashCode()+" userInfo="+myObject.userInfo.hashCode());} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
    }

    即通过MyOject.getInstance()方法获取MyOject对象后,将其读入myObject-File.txt文件中,然后再从该文件读取,使用readResolve()方法后,序列化和反序列化时,就不会创建新的MyOject对象,而是选择复用原有的MyOject对象,从而保持单例。不使用的话则会出现多个MyOject对象,即创建多个新的MyOject对象。
    即在静态内置类实现单例的基础上进行序列化和反序列化。
    但是如果将序列化和反序列化分别放入两个class,反序列化时会产生新的MyOject对象。放在两个class类中分别执行其实相当于创建了2个JVM虚拟机,每个虚拟机里有一个MyObject对象。原本想要实现的是在一个JVM虚拟机中进行序列化和反序列化时保持MyObject单例性。

4.使用static代码块实现单例模式

因为静态代码块中的代码在使用类的时候就已经执行,所以可以应用静态代码块的这个特性实现单例模式。

  1. 代码如下
    package singleton.test;public class MyObject {private static MyObject myObject = null;private MyObject(){}static{myObject = new MyObject();}public static MyObject getInstance(){return myObject;}
    }
    即将new一个对象的代码改为放在static代码块中即可。

5.使用enum枚举数据类型实现单例模式

枚举enum和静态代码块的特性相似。在使用枚举类时,构造方法会被自动调用,可以应用这个特性实现单例模式。

  1. 创建singleton.test2包,在包下创建枚举类MyObject

    package singleton.test2;import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;public class MyObject {public enum MyEnumSingleton{//枚举类的实例 不需要newconnectionFactory;private Connection connection;private MyEnumSingleton(){try{System.out.println("调用了MyObject的构造");String url = "jdbc:mysql:///test";String username="root";String password="123456";String driverName = "com.mysql.jdbc.Driver";//com.microsoft.sqlserver.jdbc.SQLServerDriverClass.forName(driverName);connection = DriverManager.getConnection(url,username,password);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);}}}public static Connection getConnection(){return MyEnumSingleton.connectionFactory.connection;}
    }

    此时需要引用connector(mysql-connector-java-5.0.8-bin),即连接mysql所需要的connector的jar包。
    即在构造方法中实例化一个connection对象。getConnection()方法返回这个对象。

  2. 创建singleton.thread包,在包下创建类MyThread2

    package singleton.thread;import singleton.test2.MyObject;public class MyThread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(MyObject.getConnection().hashCode());}}
    }
  3. 创建singleton.test2.run包,在包下创建类Run

    package singleton.test2.run;import singleton.thread.MyThread2;public class Run {public static void main(String[] args) {MyThread2 t1 = new MyThread2();MyThread2 t2 = new MyThread2();MyThread2 t3 = new MyThread2();t1.start();t2.start();t3.start();}
    }

    即启动线程,执行run方法,然后通过引用MyObject类的内置枚举类MyEnumSingleton的实例connectionFactory,再调用这个MyObject类的getConnection()方法来获取connection实例对象的hashCode。相同的hashCode,完成枚举类实现单例模式。

总结

  1. Timer类的主要作用就是设置计划任务,即在指定的时间开始执行某一个任务。
    封装任务的类却是TimerTask(抽象类)的子类。
  2. schedule(TimerTask task,Date time),作用是在指定的日期执行一次某一任务。
    schedule(TimerTask task,Date firstTime,long period),作用是在指定日期之后按指定的间隔周期无限循环的执行某一任务。
    间隔执行任务的算法,若有ABC三个任务,执行顺序为ABC→CAB→BCA→…,即每次执行一遍后,后面就会将最后一个任务放入队列头,再执行队列头中任务的run()方法。
    schedule执行任务的时间早于当前时间,则立即执行任务。
    schedule执行任务的时间晚于当前时间,则在指定的未来时间执行任务。
    执行多个TimerTask任务时,即多个Task实例和调用多次Timer实例的schedule方法。
    schedule(TimerTask task,long delay),作用是以执行方法的当前时间为参考,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。
    而schedule(TimerTask task,Date time),是指定日期时间执行一次任务。
    schedule(TimerTask task,long delay,long period),作用是当前时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
    而schedule(TimerTask task,Date firstTime,long period),是指定日期firstTime,而非延迟毫秒数。
    schedule开始执行任务的时间,有可能会被前面一个任务执行时长所影响,因为TimerThread线程管理一个队列,任务会按在队列的顺序执行任务。
    即任务1执行结束后,任务2才能开始执行。
  3. public void cancel()方法的作用是终止此计时器,丢弃当前所有已安排的任务。销毁创建Timer对象实例时启动的TimerThread线程。但是不影响正在执行的任务。
    Timer类中的cancel()方法有时并没有争抢到队列锁,会让TimerTask类中的任务正常执行。即存在小概率的失误。
    public boolean cancel(),作用是将自身(TimerTask)从任务队列中清除。
    前者移除所有任务,并且不影响正在执行的任务;后者只移除自身,不影响其他所有任务。
  4. 立即加载就是使用类的时候已经将对象创建完毕。常见的实现办法就是new实例化。也称饿汉模式。
    延迟加载就是调用get()方法时,实例才被工厂创建。
    DCL双检查锁是大多数多线程结合单例模式使用的解决方案。DCL双检查锁需要使用volatile,此时主要作用是使该实例变量在多个线程间可见以及禁止代码重排序。

相关内容

热门资讯

跨境电商巨无霸,赴港IPO获证... 来源:上海证券报 7月10日,中国证监会国际合作司发布关于SHEIN Global Holdings...
陪爸妈走过的第四个城市 陪爸妈走过的第四个城市 开篇 今年春天,趁着天气刚刚好,我又带着爸妈出了趟门。算上这次,是陪他们走...
百度沈抖:未来 90% 工作都... “未来90%的工作,都可能有智能体深度参与、协助完成。”在百度AIDAY百度搭子专场上,百度集团执行...
港股风向标|恒指短线放量滞涨 ... 财联社7月10日讯(编辑 冯轶)今日港股再度冲高回落,三大指数午后集体走弱。截至收盘,恒生指数、国企...
MLCC周期全面爆发!风华高科... 7月10日,国内被动元件龙头风华高科(000636.SZ)披露2026年半年度业绩预告,上半年净利润...
关停杠杆炒金!银行个人贵金属代... 今年6月以来,交通银行、招商银行、工商银行、建设银行等多家银行相继发布公告,宣布将于7月下旬起停办代...
原创 许... 许家印当年为何跟黄有龙搞在一起?背后到底有何猫腻?许家印跟黄有龙堪称是中国资本市场的两大奇人。一个凭...
世界杯第29日前瞻:西班牙vs... 北京时间7月11日凌晨3点,2026年美加墨世界杯进入第29个比赛日的争夺,将展开第二场1/4决赛争...
食品饮料周报:白酒去库存加速,... 证券之星食品饮料行业周报:2026年7月6日-2026年7月10日,沪深300指数下跌1.27%,申...
沈阳闲置黄金变现爆发,宏昇贵金... 近期,国际黄金价格持续震荡上行,沈阳地区贵金属回收市场迎来交易热潮。随着居民家庭闲置黄金存量的释放,...
Claude Code “高危... 出品 | 妙投APP 作者 | 张贝贝 编辑 | 丁萍 头图 | AI生图 7月8日,一则关于Cla...
新豪轩门窗X新华网《极致中国造... 从“中国制造”的规模优势迈向“中国创造”的价值跃升,中国品牌正以持续创新重塑全球竞争力。在这一进程中...
上市械企董事长,被女儿、女婿联... 来源:医疗器械经销商联盟 7月8日,科创板上市公司奥精医疗突发重磅公告,瞬间引爆资本市场与舆论圈。公...
汕头市优佳适科技有限公司成立 ... 天眼查App显示,近日,汕头市优佳适科技有限公司成立,注册资本50万人民币,经营范围为一般项目:技术...
砍掉两百个BU与近千家代持外壳... 在智能清洁硬件赛道凭借激进出海与疯狂营销一路狂飙的追觅(Dreame),突然在资本市场的前夜迎来了最...
原创 昨... 美东时间 7 月 9 日夜,纽约债市收盘钟声敲响时,交易大厅里紧绷了一周的情绪终于松了下来。本周总额...
段永平再次增持泡泡玛特 持股比... 观点网讯:7月10日,据港交所披露,知名投资人段永平再度增持泡泡玛特股票。 此次增持后,段永平持股数...
包揽全球80%的产能,又一个行... 作者: 快刀财经 郑栾 近日,新消费行业又迎来一笔久违的大额融资——咖爷科技宣布完成B轮融资,由美...
外储增配香港资产,可能会买啥? 界面新闻记者 | 杨志锦 界面新闻编辑 | 王姝 本周,央行行长潘功胜在香港的讲话引起市场广泛关...
最贵一套1.5亿,上海徐汇两大... 界面新闻记者 | 王婷婷 上海顶豪市场年度大戏来了。 7月11日,上海西岸美高梅酒店将迎来两大豪宅...