5.1单例模式
单例模式:是一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例。
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;售票时,一共有100张票,可有有多个窗口同时售票,但需要保证不要超售(这里的票数余量就是单例,售票涉及到多线程)。如果不是用机制对窗口对象进行唯一化将弹出多个窗口,如果这些窗口显示的都是相同的内容,重复创建就会浪费资源。
1.双重检查
public class Singleton {
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstacne(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
2.静态内部类
public class Singleton {
private Singleton(){}
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
5.2不变模式
不变模式的核心思想是一个对象一旦被创建,则它的内部状态将永远不会发生改变。
比如一个对象的存活时间,它会随时间的变化而变化,因此它是只读属性,而不是不变属性。
不变模式的主要应用场景:
- 当对象创建后,其内部状态和数据不再发生变化
- 对象需要被共享,被多线程频繁访问
public final class Product {
private final String no;
private final String name;
private final double price;
public Product(String no,String name,double price){
super();
this.no = no;
this.name = name;
this.price = price;
}
public String getNo() {
return no;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
注意:不变模式通过回避问题而不是解决问题的态度来处理 多线程并发访问控制。不变对象是不需要进行同步控制的。由于并发同步会对性能产生不良的影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量。
5.3生产者消费者模式
在生产者与消费者之间建立一个缓冲区,对生产者线程和消费者线程进行解耦。
5.4Future模式
先想一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。
实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。
public class test{
public static void main(String[] args) throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis();
// 第一步 网购厨具
Callable<Cooker> onlineShopping = new Callable<Cooker>() {
@Override
public Cooker call() throws Exception {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
// 模拟送货时间
Thread.sleep(5000);
System.out.println("第一步:快递送到");
return new Cooker();
}
};
FutureTask<Cooker> task = new FutureTask<Cooker>(onlineShopping);
new Thread(task).start();
// 第二步 去超市购买食材
// 模拟购买食材时间
Thread.sleep(2000);
Food food = new Food();
System.out.println("第二步:食材到位");
// 第三步 用厨具烹饪食材
// 联系快递员,询问是否到货
if (!task.isDone()) {
System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
}
Cooker cooker = task.get();
System.out.println("第三步:厨具到位,开始展现厨艺");
cook(cooker, food);
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
/**
* 厨具
*/
static class Cooker{}
/**
* 食物
*/
static class Food{}
/**
* 烹饪
*/
static void cook(Cooker cooker,Food food){}
}
5.5并行搜索
public class test {
/**
* 定义我们需要查询的无序数组
*/
static int[] arr = {1, 22, 2, 3, 4, 5, 344, 6, 7, 8, 10, 9};
/**
* 定义线程池数据,已经存放结果的Result
*/
static ExecutorService pool = Executors.newCachedThreadPool();
static final int thread_num = 2;
/**
* 初始值定位-1
*/
static AtomicInteger result = new AtomicInteger(-1);
public static int search(int searchValue, int beginPos, int endPos) {
for (int j = beginPos; j < endPos; j++) {
if (result.get() >= 0) {
return result.get();
}
if (arr[j] == searchValue) {
//如果设置失败,表示其他现场已经先找到了
if (!result.compareAndSet(-1, j)) {
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
//-1当前值为-1就返回
return result.get();
}
return j;
}
}
return -1;
}
public static class SearchTask implements Callable<Integer> {
int begin, end, searchValue;
public SearchTask(int searchValue, int begin, int end) {
this.begin = begin;
this.end = end;
this.searchValue = searchValue;
}
@Override
public Integer call() {
int re = search(searchValue, begin, end);
return re;
}
}
public static int psearch(int searchValue) throws InterruptedException, ExecutionException {
int subArrSize = arr.length / thread_num + 1;
List<Future<Integer>> re = new ArrayList<Future<Integer>>();
for (int i = 0; i < arr.length; i += subArrSize) {
int end = i + subArrSize;
if (end >= arr.length) {
end = arr.length;
}
re.add(pool.submit(new SearchTask(searchValue, i, end)));
}
for (Future<Integer> fu :
re) {
if (fu.get() >= 0) {
return (fu.get());
}
}
return -1;
}
public static void main(String[] args) {
try {
System.out.println(psearch(10));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
5.6NIO
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
传统io与nio区别:
public class test {
public static void method1() {
RandomAccessFile aFile = null;
try {
aFile = new RandomAccessFile("src/test.txt", "rw");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(buf);
System.out.println(bytesRead);
while (bytesRead != -1) {
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
buf.compact();
bytesRead = fileChannel.read(buf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (aFile != null) {
aFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void method2() {
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream("src/test.txt"));
byte[] buf = new byte[1024];
int bytesRead = in.read(buf);
while (bytesRead != -1) {
for (int i = 0; i < bytesRead; i++) {
System.out.print((char) buf[i]);
}
bytesRead = in.read(buf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// method2();
method1();
}
}
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。比如:文件的上传下载
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。