zoukankan      html  css  js  c++  java
  • Java多线程编程核心技术---单例模式与多线程

    立即加载/饿汉模式

    立即加载就是使用类的时候已经将对象创建完毕。

    public class MyObject {
    	//立即加载方式==饿汉模式
    	private static MyObject myObject = new MyObject();
    	private MyObject(){
    	}
    	public static MyObject getInstance(){
    		//立即加载
    		//缺点是不能有其他实例变量
    		//getInstance()方法没有同步,有可能出现非线程安全问题
    		return myObject;
    	}
    }
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    }
    
    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    运行程序,控制台打印结果如下:

    1795478472
    1795478472
    1795478472
    

    控制台打印的hashCode是同一个值,说明对象是同一个,也就实现了立即加载型单例模式。


    延迟加载/懒汉模式

    延迟加载就是在调用get()方法时实例才被创建

    public class MyObject {
    	private static MyObject myObject;
    	private MyObject(){
    	}
    	public static MyObject getInstance() {
    		//延迟加载
    		if (myObject != null) {
    			return myObject;
    		}
    		myObject = new MyObject();
    		return myObject;
    	}
    }
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    }
    
    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		t1.start();
    	}
    }
    

    程序运行结果如下:

    1396452035
    

    此实验虽然取得一个对象的实例,但是如果是在多线程环境中,就会出现取出多个实例的情况。对以上代码中的main函数做如下修改:

    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    重新运行程序,控制台打印结果如下:

    166471260
    1795478472
    1858758426
    

    控制台打印了三个不同的hashCode,说明并没有实现单例模式。


    延迟加载/懒汉模式 解决方案
    public class MyObject {
    	private static MyObject myObject;
    	private MyObject(){
    	}
    	//整个方法上锁,效率较低
    	synchronized public static MyObject getInstance() {
    		//延迟加载
    		if (myObject != null) {
    			return myObject;
    		}
    		try {
    		//模拟一些耗时操作
    				Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		myObject = new MyObject();
    		return myObject;
    	}
    }
    

    重新运行程序,控制台将打印出三个一样的hashCode。

    以上方法对整个方法加锁,效率比较低。对以上代码做如下修改:

    public class MyObject {
    	private static MyObject myObject;
    	private MyObject(){
    	}
    	public static MyObject getInstance() {
    		//延迟加载
    		if (myObject != null) {
    			return myObject;
    		}
    		try {
    			//模拟一个耗时操作
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		synchronized (MyObject.class) {
    			myObject = new MyObject();
    		}
    		return myObject;
    	}
    }
    

    重新运行程序,控制台打印结果如下:

    774088025
    641502649
    1367113803
    

    以上代码虽然解决的效率问题,但是仍然没有保证只创建一个实例。继续对以上代码做如下修改:

    public class MyObject {
    	private static MyObject myObject;
    	private MyObject(){
    	}
    	public static MyObject getInstance() {
    		//延迟加载
    		if (myObject != null) {
    			return myObject;
    		}
    		try {
    			//模拟一个耗时操作
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		synchronized (MyObject.class) {
    			if (myObject == null) {
    				myObject = new MyObject();
    			}
    		}
    		return myObject;
    	}
    }
    

    重新运行程序,控制台输入结果如下:

    641502649
    641502649
    641502649
    

    使用双重检查锁(DCL)功能,成功解决了懒汉模式遇到的多线程问题。DCL也是大多数多线程结合单例模式使用的解决方案。


    使用静态内置类实现单例模式
    public class MyObject {
    	private static class MyObjectHandler{
    		private static MyObject myObject = new MyObject();
    	}
    	private MyObject(){
    		
    	}
    	public static MyObject getInstance () {
    		return MyObjectHandler.myObject;
    	}
    }
    

    运行程序,控制台输出结果如下:

    774088025
    774088025
    774088025
    

    序列化与反序列化的单例模式实现
    public class MyObject implements Serializable {
    	private static final long serialVersionUID = 1L;
    	private static class MyObjectHandler{
    		private static MyObject myObject = new MyObject();
    	}
    	private MyObject(){
    		
    	}
    	public static MyObject getInstance () {
    		return MyObjectHandler.myObject;
    	}
    }
    
    public class SaveAndRead {
    	public static void main(String[] args) {
    		try {//序列化对象到磁盘
    			MyObject myObject = MyObject.getInstance();
    			FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/shangyidong/Downloads/myObjectFile.txt"));
    			ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutputStream);
    			objectOutput.writeObject(myObject);
    			objectOutput.close();
    			fileOutputStream.close();
    			System.out.println(myObject.hashCode());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		/****************/
    		try {//从磁盘反序列化到对象
    			FileInputStream fileInputStream = new FileInputStream(new File("/Users/shangyidong/Downloads/myObjectFile.txt"));
    			ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    			MyObject myObject = (MyObject) objectInputStream.readObject();
    			objectInputStream.close();
    			fileInputStream.close();
    			System.out.println(myObject.hashCode());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    运行程序,控制台打印结果如下:

    988487739
    1878277481
    

    控制台打印出的hashCode不同,存储到磁盘上的对象和从磁盘读取出来的对象并不是同一个对象。对以上代码做如下修改:

    public class MyObject implements Serializable {
    	private static final long serialVersionUID = 1L;
    	private static class MyObjectHandler{
    		private static MyObject myObject = new MyObject();
    	}
    	private MyObject(){
    		
    	}
    	public static MyObject getInstance () {
    		return MyObjectHandler.myObject;
    	}
    	protected Object readResolve(){
    		System.out.println("readResolve invoked");
    		return MyObjectHandler.myObject;
    	}
    }
    

    重新运行程序,控制台打印结果如下:

    1066557918
    readResolve invoked
    1066557918
    

    反序列化时使用了ReadResolve()方法,存储到磁盘上的对象和从磁盘上取出来的对象是同一个对象。


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

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

    public class MyObject {
    	private static MyObject myObject = null;
    	private MyObject(){
    		
    	}
    	static{
    		myObject = new MyObject();
    	}
    	public static MyObject getInstance() {
    		return myObject;
    	}
    }
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 3; i++) {
    			System.out.println(MyObject.getInstance().hashCode());
    		}
    	}
    }
    
    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    运行程序,控制台输出结果如下:

    708252873
    708252873
    708252873
    708252873
    708252873
    708252873
    708252873
    708252873
    708252873
    

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

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

    public enum MyObject {
    	connectionFactory;
    	private Connection connection;
    	private MyObject(){
    		try {
    			System.out.println("创建MyObject对象");
    			String url = "jdbc:mysql://127.0.0.1:3306/test";
    			String user = "root"; 
    	        String password = "";
    	        String driver = "com.mysql.jdbc.Driver";
    	        Class.forName(driver);
    	        connection = DriverManager.getConnection(url, user, password);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    	public Connection getConnection() {
    		return connection;
    	}
    }
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 3; i++) {
    			System.out.println(MyObject.connectionFactory.getConnection().hashCode());
    		}
    	}
    }
    
    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    运行程序,控制台打印结果如下:

    创建MyObject对象
    1253195928
    1253195928
    1253195928
    1253195928
    1253195928
    1253195928
    1253195928
    1253195928
    1253195928
    

    完善使用enum枚举实现单例模式

    上面的代码中将枚举类进行了暴露,违反了“职责单一原则”。下面进行改善:

    public class MyObject {
    	public enum MyEnumSingleton{
    		connectionFactory;
    		private Connection connection;
    		private MyEnumSingleton(){
    			try {
    				System.out.println("创建MyObject对象");
    				String url = "jdbc:mysql://127.0.0.1:3306/test";
    				String user = "root"; 
    		        String password = "";
    		        String driver = "com.mysql.jdbc.Driver";
    		        Class.forName(driver);
    		        connection = DriverManager.getConnection(url, user, password);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    		public Connection getConnection() {
    			return connection;
    		}	
    	}
    	public static Connection getConnection() {
    		return MyEnumSingleton.connectionFactory.getConnection();
    	}
    }
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 3; i++) {
    			System.out.println(MyObject.getConnection().hashCode());
    		}
    	}
    }
    
    public class Main {
    	public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    程序运行结果如下:

    创建MyObject对象
    2039988480
    2039988480
    2039988480
    2039988480
    2039988480
    2039988480
    2039988480
    2039988480
    2039988480
    
  • 相关阅读:
    技术分享会之——智能指针
    TeleMCU技术规格
    Golang-interface(一 基本使用)
    关于Mac虚拟机中安装的Windows系统键盘问题
    Android 高速开发系列 打造万能的ListView GridView 适配器
    [Erlang]怎样在Erlang中使用SSL
    Web最新力作有奖试读获奖名单发布
    每天复习Shell—ls
    Mininet系列实验(七):Mininet脚本实现控制交换机行为
    Mininet系列实验(七):Mininet脚本实现控制交换机行为
  • 原文地址:https://www.cnblogs.com/umgsai/p/5600109.html
Copyright © 2011-2022 走看看