单例模式的练习-如何正确构建

1、说明:单例模式是最简单的设计模式之一,单例模式适合于一个类只有一个实例的情况。

2、要求:确保一个类只有一个实例被建立,并提供了一个对对象的全局访问指针 。

3、注意事项:如何正确构建线程安全的单例模式。

不正确的构建,示例代码加测试代码如下:


package org.com.jsoup;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPublicTest {

	public static   ThreadPublicTest test   ;

	private final String threadName;

	private ThreadPublicTest() {
		System.out.println("构建方法执行,当前执行线程名称为:" + Thread.currentThread().getName());
threadName = Thread.currentThread().getName();
	}

	public String getThreadName() {
		return threadName;
	}

	public static   ThreadPublicTest getThreadPublicTest() {
		if (test == null){
			test = new ThreadPublicTest();
		}
		return test;
	}

	/**
	 * @param args测试代码
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		// 开始时间
		long beginTime = System.nanoTime();

		final int theadMax = 10;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(theadMax);
		// 创建一个线程池,限制为最多theadMax个线程
		final ExecutorService exec = Executors.newFixedThreadPool(theadMax);
		// 创建锁存器,设置为theadMax个,表示要等待theadMax个线程执行完
		final CountDownLatch downLatch = new CountDownLatch(theadMax);
		for (int i = 0; i < theadMax; i++) {
			Runnable runable = new Runnable() {
				@Override
				public void run() {
					try {
						// 排队等待
						cyclicBarrier.await();
						ThreadPublicTest test = ThreadPublicTest
								.getThreadPublicTest();
						System.out.println(String.format("对象名称:%s 线程名称:%s",
								test.getThreadName(),Thread.currentThread().getName()));
					} catch (Exception e) {
						// 处理异常
					} finally {
						// 减少计数值
						downLatch.countDown();
					}
				}
			};
			// 将任务放入线程池执行
			exec.execute(runable);
		}
		downLatch.await();// 等待所有的并发访问完
		exec.shutdown();// 关闭线程池
		System.out.println("执行完毕:" + (System.nanoTime() - beginTime));
	}

}


上面的测试代码输出为:


构建方法执行,当前执行线程名称为:pool-1-thread-1

构建方法执行,当前执行线程名称为:pool-1-thread-10

对象名称:pool-1-thread-10 线程名称:pool-1-thread-4

对象名称:pool-1-thread-10 线程名称:pool-1-thread-10

对象名称:pool-1-thread-10 线程名称:pool-1-thread-2

对象名称:pool-1-thread-10 线程名称:pool-1-thread-8

对象名称:pool-1-thread-10 线程名称:pool-1-thread-6

对象名称:pool-1-thread-1 线程名称:pool-1-thread-1

对象名称:pool-1-thread-10 线程名称:pool-1-thread-5

对象名称:pool-1-thread-10 线程名称:pool-1-thread-3

对象名称:pool-1-thread-10 线程名称:pool-1-thread-7

对象名称:pool-1-thread-10 线程名称:pool-1-thread-9

执行完毕:57840462

说明这个对象的构造方法执行了多次,于是这不是线程安全的。

线程安全的构造方式1,示例代码加测试代码如下:
package org.com.jsoup;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPublicTest {

	public static  final ThreadPublicTest test  = new ThreadPublicTest() ;

	private final String threadName;

	private ThreadPublicTest() {
		System.out.println("构建方法执行,当前执行线程名称为:" +  Thread.currentThread().getName());
		threadName = Thread.currentThread().getName();
	}

	public String getThreadName() {
		return threadName;
	}

	public static   ThreadPublicTest getThreadPublicTest() {
		return test;
	}

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		// 开始时间
		long beginTime = System.nanoTime();

		final int theadMax = 10;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(theadMax);
		// 创建一个线程池,限制为最多theadMax个线程
		final ExecutorService exec = Executors.newFixedThreadPool(theadMax);
		// 创建锁存器,设置为theadMax个,表示要等待theadMax个线程执行完
		final CountDownLatch downLatch = new CountDownLatch(theadMax);
		for (int i = 0; i < theadMax; i++) {
			Runnable runable = new Runnable() {
				@Override
				public void run() {
					try {
						// 排队等待
						cyclicBarrier.await();
						ThreadPublicTest test = ThreadPublicTest
								.getThreadPublicTest();
						System.out.println(String.format("对象名称:%s 线程名称:%s",
								test.getThreadName(),Thread.currentThread().getName()));
					} catch (Exception e) {
						// 处理异常
					} finally {
						// 减少计数值
						downLatch.countDown();
					}
				}
			};
			// 将任务放入线程池执行
			exec.execute(runable);
		}
		downLatch.await();// 等待所有的并发访问完
		exec.shutdown();// 关闭线程池
		System.out.println("执行完毕:" + (System.nanoTime() - beginTime));
	}

}


看看执行结果:
构建方法执行,当前执行线程名称为:main
对象名称:main 线程名称:pool-1-thread-1
对象名称:main 线程名称:pool-1-thread-6
对象名称:main 线程名称:pool-1-thread-2
对象名称:main 线程名称:pool-1-thread-4
对象名称:main 线程名称:pool-1-thread-8
对象名称:main 线程名称:pool-1-thread-10
对象名称:main 线程名称:pool-1-thread-3
对象名称:main 线程名称:pool-1-thread-5
对象名称:main 线程名称:pool-1-thread-7
对象名称:main 线程名称:pool-1-thread-9

执行完毕:51450575

可以看到构建方法只执行了一次,这种实现方法的好处是代码简洁,线程获取单例的方法执行效率高,缺点就是代码一加载就会执行对象初始化,所以上面我们看到构造方法是在主线程中执行的。

那么我们采用线程同步的方式来实现延迟初始化,以下是示例加测试代码:


package org.com.jsoup;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPublicTest {

	public static   ThreadPublicTest test  ;

	private final String threadName;

	private ThreadPublicTest() {
		System.out.println("构建方法执行,当前执行线程名称为:" +  Thread.currentThread().getName());
		threadName = Thread.currentThread().getName();
	}

	public String getThreadName() {
		return threadName;
	}

	public static  synchronized ThreadPublicTest getThreadPublicTest() {
		if (test == null){
			test	= new ThreadPublicTest() ;
		}
		return test;
	}

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		// 开始时间
		long beginTime = System.nanoTime();

		final int theadMax = 10;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(theadMax);
		// 创建一个线程池,限制为最多theadMax个线程
		final ExecutorService exec = Executors.newFixedThreadPool(theadMax);
		// 创建锁存器,设置为theadMax个,表示要等待theadMax个线程执行完
		final CountDownLatch downLatch = new CountDownLatch(theadMax);
		for (int i = 0; i < theadMax; i++) {
			Runnable runable = new Runnable() {
				@Override
				public void run() {
					try {
						// 排队等待
						cyclicBarrier.await();
						ThreadPublicTest test = ThreadPublicTest
								.getThreadPublicTest();
						System.out.println(String.format("对象名称:%s 线程名称:%s",
								test.getThreadName(),Thread.currentThread().getName()));
					} catch (Exception e) {
						// 处理异常
					} finally {
						// 减少计数值
						downLatch.countDown();
					}
				}
			};
			// 将任务放入线程池执行
			exec.execute(runable);
		}
		downLatch.await();// 等待所有的并发访问完
		exec.shutdown();// 关闭线程池
		System.out.println("执行完毕:" + (System.nanoTime() - beginTime));
	}

}


看看测试的执行结果:


构建方法执行,当前执行线程名称为:pool-1-thread-10

对象名称:pool-1-thread-10 线程名称:pool-1-thread-5

对象名称:pool-1-thread-10 线程名称:pool-1-thread-1

对象名称:pool-1-thread-10 线程名称:pool-1-thread-10

对象名称:pool-1-thread-10 线程名称:pool-1-thread-4

对象名称:pool-1-thread-10 线程名称:pool-1-thread-7

对象名称:pool-1-thread-10 线程名称:pool-1-thread-6

对象名称:pool-1-thread-10 线程名称:pool-1-thread-3

对象名称:pool-1-thread-10 线程名称:pool-1-thread-8

对象名称:pool-1-thread-10 线程名称:pool-1-thread-9

对象名称:pool-1-thread-10 线程名称:pool-1-thread-2

执行完毕:44952622

上面的执行结果表明单例对象是在子线程中执行的,好处就是,系统要用到单例对象时才构造,避免不使用也进行构造而浪费内存,缺点就是线程获取这个单例对象时每次都要判断并加了synchronized进行线程同步,执行效率稍低。

还有更好的方法吗?或许下面的示例可以(延长初始化点位类模式):
package org.com.jsoup;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPublicTest {
	
	private static class InternalHalder{
		//JVM会根据需要的时候才会初始化ThreadPublicTest
		public static final ThreadPublicTest test = new ThreadPublicTest();
	}
	public static   ThreadPublicTest test  ;

	private final String threadName;

	private ThreadPublicTest() {
		System.out.println("构建方法执行,当前执行线程名称为:" +  Thread.currentThread().getName());
		threadName = Thread.currentThread().getName();
	}

	public String getThreadName() {
		return threadName;
	}

	public static  ThreadPublicTest getThreadPublicTest() {
		return InternalHalder.test;
	}

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		// 开始时间
		long beginTime = System.nanoTime();

		final int theadMax = 10;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(theadMax);
		// 创建一个线程池,限制为最多theadMax个线程
		final ExecutorService exec = Executors.newFixedThreadPool(theadMax);
		// 创建锁存器,设置为theadMax个,表示要等待theadMax个线程执行完
		final CountDownLatch downLatch = new CountDownLatch(theadMax);
		for (int i = 0; i < theadMax; i++) {
			Runnable runable = new Runnable() {
				@Override
				public void run() {
					try {
						// 排队等待
						cyclicBarrier.await();
						ThreadPublicTest test = ThreadPublicTest
								.getThreadPublicTest();
						System.out.println(String.format("对象名称:%s 线程名称:%s",
								test.getThreadName(),Thread.currentThread().getName()));
					} catch (Exception e) {
						// 处理异常
					} finally {
						// 减少计数值
						downLatch.countDown();
					}
				}
			};
			// 将任务放入线程池执行
			exec.execute(runable);
		}
		downLatch.await();// 等待所有的并发访问完
		exec.shutdown();// 关闭线程池
		System.out.println("执行完毕:" + (System.nanoTime() - beginTime));
	}

}


执行结果跟上面一个示例是一样,但是不一样的是减少了同步开销,性能优于上一个。








转载于:https://my.oschina.net/20076678/blog/80585

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值