您的位置:首页 > 编程语言

《多线程编程》学习之十:定时器Timer的使用,线程安全的单例模式

2016-11-08 16:21 507 查看
一、定时器Timer的使用

         定时器 Timer类主要的作用是设置计划任务,它在内部使用多线程的方式进行处理;而抽象的TimerTask类负责封装任务,它实现了Runnable接口。

1)schedule(TimerTask task,Date time)

          此方法表示,如果设定的时间早于当前时间,则立即执行;否则等待time指定的时间再执行task任务。

<span style="font-size:18px;">import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
* Timer类主要用于设置计划任务,TimerTask类用于封装任务
* 例子一:在指定的时间执行任务。如果指定时间比现在早,则立即执行;如果指定的时间比现在晚,则将来执行。
* Created by xiuzhen on 2016/11/8.
*/
public class Run1 {
//创建一个Timer,默认为非守护线程(添加参数true,则创建守护线程,TimerTask中的任务不再被运行)
static private Timer timer = new Timer();
//日期格式
static private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

//创建抽象TimerTask类的子类
static class MyTask extends TimerTask {
@Override
public void run() {
try {
//打印执行的当前时间
System.out.println("开始执行任务,时间为:" + sdf.format(new Date()));
Thread.sleep(3000);     //休眠3s
System.out.println("结束执行任务,时间为:" + sdf.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

//测试:在指定的时间执行任务
public static void main(String[] args) {
try {
//创建任务
MyTask task = new MyTask();
//任务执行的时间
String dateString = "2016-11-08 16:26:00";
Date date = sdf.parse(dateString);
System.out.println("指定时间:" + sdf.format(date) + "  当前时间为:" + sdf.format(new Date()));
//定时器设置任务,在指定时间执行任务
timer.schedule(task, date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}</span>
运行结果:

指定时间:2016-11-08 16:26:00 当前时间为:2016-11-08 16:25:25

开始执行任务,时间为:2016-11-08 16:26:00

结束执行任务,时间为:2016-11-08 16:26:03

2)schedule(TimerTask task, Date firstDate, long period)

        此方法作用是在指定日期之后,按指定的时间间隔无限循环执行任务。如果任务的执行时间大于period,则等此次任务执行完毕后立即循环执行下次任务。在上面例子的基础上,测试代码如下:

<span style="font-size:18px;">    public static void main(String[] args) {
try {
//创建任务
MyTask task = new MyTask();
//任务执行的时间
String dateString = "2016-11-08 16:32:00";
Date date = sdf.parse(dateString);
System.out.println("指定时间:" + sdf.format(date) + "  当前时间为:" + sdf.format(new Date()));
//在指定日期之后,按指定的时间间隔无限循环地执行某一任务,如果任务执行的时间过长,则等任务执行完后立即循环执行。
timer.schedule(task, date, 2000);
} catch (ParseException e) {
e.printStackTrace();
}
}</span>
测试结果:



3)多个TimeTask任务及延时

          Timer中允许多个TimerTask任务,它们是以队列的方式一个一个地被顺序执行的,如果前面的任务执行时间过长,则下一个任务执行的时间可能与预期的时间不一致。

<span style="font-size:14px;">import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
* 多个TimerTask任务及延时的测试。
* TimerTask是以队列的方式一个一个被顺序执行的,如果前面的任务执行的时间太长,可能导致后面的任务延时执行。
* Created by xiuzhen on 2016/11/8.
*/
public class Run2 {
static private Timer timer = new Timer();
static private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

//第一个任务类
static private class MyTask1 extends TimerTask {
@Override
public void run() {
try {
System.out.println("MyTask1开始运行,时间为:" + sdf.format(new Date()));
Thread.sleep(5000);     //休眠5s
System.out.println("MyTask1结束运行,时间为:" + sdf.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//第二个任务类
static private class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("MyTask2运行了,时间为:" + sdf.format(new Date()));
}
}

//测试:多个TimerTask任务运行,可能会延时
public static void main(String[] args) {
try {
//任务一:
MyTask1 task1 = new MyTask1();
String dateString1 = "2016-11-8 16:40:00";
Date date1 = sdf.parse(dateString1);
System.out.println("任务一指定的时间为:" + sdf.format(date1) + "  当前时间为:" + sdf.format(new Date()));
//任务二:
MyTask2 task2 = new MyTask2();
String dateString2 = "2016-11-8 16:40:01";
Date date2 = sdf.parse(dateString2);
System.out.println("任务二指定的时间为:" + sdf.format(date2) + "  当前时间为:" + sdf.format(new Date()));

timer.schedule(task1, date1);
timer.schedule(task2, date2);
} catch (ParseException e) {
e.printStackTrace();
}
}

}</span>
运行结果:



二、线程安全的单例模式

1)立即加载(“饿汉模式”)

          所谓立即加载,就是使用类的时候已经将对象创建完毕了,常见的是直接new实例化对象。

<span style="font-size:18px;"><pre style="font-family: Consolas; font-size: 13.5pt; background-color: rgb(255, 255, 255);"><pre name="code" class="java">/**
* 饿汉模式(静态实例变量)
* Created by xiuzhen on 2016/11/8.
*/
public class HungerSingleton {
//立即加载(饿汉模式):静态实例变量
private static  HungerSingleton singleton = new HungerSingleton();
//私有的构造函数
private HungerSingleton () {

}

//对外提供访问单例的接口
public static HungerSingleton getInstance() {
return singleton;
}

//测试:创建三个线程,去访问单例
public static void main(String[] args) {
HungerThread thread1 = new HungerThread();
HungerThread thread2 = new HungerThread();
HungerThread thread3 = new HungerThread();

thread1.start();
thread2.start();
thread3.start();
}
}</span>





HungerThread.java的代码如下:

/**
* 访问单例(饿汉模式下)的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class HungerThread extends Thread {
@Override
public void run() {
System.out.println(HungerSingleton.getInstance().hashCode());
}
}
运行结果:



2)延迟加载(“懒汉模式”)
        所谓延迟加载,就是在调用getInstance()方法时实例才被创建。

<span style="font-size:18px;">/**
* 延迟加载(饿汉模式)
* Created by xiuzhen on 2016/11/8.
*/
public class LazySingleton {
private static LazySingleton singleton;
//私有构造函数
private LazySingleton() {

}

//延迟加载,调用方法是才去实例化对象
public static LazySingleton getInstance() {
try {
if (singleton == null) {
//模拟在创建对象前需要做一些准备工作
Thread.sleep(3000);
singleton = new LazySingleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}

return singleton;
}

//测试,这种延迟加载,可能会出现线程安全问题
public static void main(String[] args) {
LazyThread thread1 = new LazyThread();
LazyThread thread2 = new LazyThread();
LazyThread thread3 = new LazyThread();

thread1.start();
thread2.start();
thread3.start();
}
}</span>


LazyThread.java的代码如下:

/**
* 访问单例(懒汉模式下)的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class LazyThread extends Thread {
@Override
public void run() {
System.out.println(LazySingleton.getInstance().hashCode());
}
}


运行结果:


        

          可见程序创建了三个对象,并不是单例模式。下面介绍线程安全的延迟加载的单例模式。

3)使用双重检锁机制

          可以直接对getInstance()方法声明synchronized关键字来达到线程安全,但这种方法运行效率非常低下,下一个线程需要得到对象,则必须等待上一次线程释放锁。可以使用双重检锁机制来解决这一问题。

<span style="font-size:18px;">/**
* 双重检锁机制,实现线程安全的延迟加载单例模式
* Created by xiuzhen on 2016/11/8.
*/
public class DoubleLockSingleton {
private static DoubleLockSingleton singleton;

private DoubleLockSingleton() {}

public static DoubleLockSingleton getInstance() {
try {
if (singleton == null) {
//模拟在创建对象前需要做一些准备工作
Thread.sleep(3000);
synchronized (DoubleLockSingleton.class) {
if(singleton == null) {    //这里进行第二次判断,不能省略,否则会线程不安全。
singleton = new DoubleLockSingleton();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}

return singleton;
}

public static void main(String[] args) {
DoubleLockThread thread1 = new DoubleLockThread();
DoubleLockThread thread2 = new DoubleLockThread();
DoubleLockThread thread3 = new DoubleLockThread();

thread1.start();
thread2.start();
thread3.start();
}
}</span>

DoubleLockThread.java的代码如下:

/**
* 访问单例(双重检锁机制下)的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class DoubleLockThread extends Thread {
@Override
public void run() {
System.out.println(DoubleLockSingleton.getInstance().hashCode());
}
}
运行结果:



4)使用静态内部类实现线程安全的单例模式

/**
* 使用静态内部类实现线程安全的单例模式
* Created by xiuzhen on 2016/11/8.
*/
public class InnerSingleton {
//私有的构造函数
private InnerSingleton() {

}
//静态内部类创建单例
private static class SingletonHandler {
private static InnerSingleton singleton = new InnerSingleton();
}
//外部接口(外部类可以访问内部类的私有成员)
public static InnerSingleton getInstance() {
return SingletonHandler.singleton;
}

//测试
public static void main(String[] args) {
staticInnerThread thread1 = new staticInnerThread();
staticInnerThread thread2 = new staticInnerThread();
staticInnerThread thread3 = new staticInnerThread();

thread1.start();
thread2.start();
thread3.start();
}

}staticInnerThread.java的代码如下:
/**
* 访问静态内部类实现的单例的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class staticInnerThread extends Thread {
@Override
public void run() {
System.out.println(InnerSingleton.getInstance().hashCode());
}
}运行结果:



5)使用static代码块实现单例模式

        静态代码块中的代码在使用类时就已经执行了,可以利用此特性来实现单例。

/**
* 利用静态代码块实现单例模式(静态代码块在使用类之前已经存在了)
* Created by xiuzhen on 2016/11/8.
*/
public class StaticBlockSingleton {
//私有构造函数
private StaticBlockSingleton() {}
//单例变量
private static StaticBlockSingleton singleton;

//静态代码块
static {
singleton = new StaticBlockSingleton();
}

//对外提供访问单例的接口
public static StaticBlockSingleton getInstance() {
return singleton;
}

//测试
public static void main(String[] args) {
StaticBlockThread thread1 = new StaticBlockThread();
StaticBlockThread thread2 = new StaticBlockThread();
StaticBlockThread thread3 = new StaticBlockThread();

thread1.start();
thread2.start();
thread3.start();
}
}StaicBlockThread.java的代码如下:
/**
* 访问静态代码块实现的单例的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class StaticBlockThread extends Thread {
@Override
public void run() {
System.out.println(StaticBlockSingleton.getInstance().hashCode());
}
}运行结果:



6)使用enum枚举实现单例模式

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
* enum像特殊的类,有构造器,方法和数据域。枚举类的构造器只能是私有的,
* 在构造枚举值时被调用。枚举值都是public static final的。
* Created by xiuzhen on 2016/11/8.
*/
public class EnumSingleton {
//利用枚举类型得到唯一的Connection实例
private enum MyEunmSingleton {
//枚举值
CONNECTION_FACTORY;
//私有成员变量
private Connection connection;
//私有构造器
private MyEunmSingleton() {
try {
System.out.println("创建Connection对象");
String url = "jdbc:mysql://localhost:3306/ali_blog";
String username = "root";
String password = "";
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接(实例化Connection)
connection = DriverManager.getConnection(url, username, password);

} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(SQLException e) {
e.printStackTrace();
}
}
//获取Connection实例
private Connection getConnection() {
return connection;
}
}

//对外提供访问Connection的单例(单一职责原则,不将枚举类对外暴露)
public static Connection getConnection() {
//枚举类.枚举值.方法
return MyEunmSingleton.CONNECTION_FACTORY.getConnection();
}

//测试
public static void main(String[] args) {
EnumThread thread1 = new EnumThread();
thread1.start();
}
}EnumThread.java的代码:
/**
* 访问枚举类实现的单例的线程类
* Created by xiuzhen on 2016/11/8.
*/
public class EnumThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(EnumSingleton.getConnection().hashCode());
}
}
}运行结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: