高并发Java(7):并发设计模式

1. 什么是设计模式

在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题 ,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领 域引入到计算机科学的。

著名的4人帮: Erich Gamma,Richard Helm, Ralph Johnson ,John Vlissides (Gof)

《设计模式:可复用面向对象软件的基础》收录23种模式

2. 单例模式

单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为

比如:全局信息配置

单例模式最简单的实现:

1
2
3
4
5
6
7
8
9
public class Singleton {
     private Singleton() {
         System.out.println( "Singleton is create" );
     }
     private static Singleton instance = new Singleton();
     public static Singleton getInstance() {
         return instance;
     }
}

由私有构造方法和static来确定唯一性。

缺点:何时产生实例 不好控制

虽然我们知道,在类Singleton第一次被加载的时候,就产生了一个实例。

但是如果这个类中有其他属性

1
2
3
4
5
6
7
8
9
10
public class Singleton {
     public static int STATUS= 1 ;
     private Singleton() {
         System.out.println( "Singleton is create" );
     }
     private static Singleton instance = new Singleton();
     public static Singleton getInstance() {
         return instance;
     }
}

当使用

1
System.out.println(Singleton.STATUS);

这个实例就被产生了。也许此时你并不希望产生这个实例。

如果系统特别在意这个问题,这种单例的实现方法就不太好。

第二种单例模式的解决方式:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
     private Singleton() {
         System.out.println( "Singleton is create" );
     }
     private static Singleton instance = null ;
     public static synchronized Singleton getInstance() {
         if (instance == null )
             instance = new Singleton();
         return instance;
     }
}

让instance只有在调用getInstance()方式时被创建,并且通过synchronized来确保线程安全。

这样就控制了何时创建实例。

这种方法是延迟加载的典型。

但是有一个问题就是,在高并发的场景下性能会有影响,虽然只有一个判断就return了,但是在并发量很高的情况下,或多或少都会有点影响,因为都要去拿synchronized的锁。

为了高效,有了第三种方式:

1
2
3
4
5
6
7
8
9
10
11
public class StaticSingleton {
     private StaticSingleton(){ 
         System.out.println( "StaticSingleton is create" );
     }
     private static class SingletonHolder {
         private static StaticSingleton instance = new StaticSingleton();
     }
     public static StaticSingleton getInstance() {
         return SingletonHolder.instance;
     }
}

由于加载一个类时,其内部类不会被加载。这样保证了只有调用getInstance()时才会产生实例,控制了生成实例的时间,实现了延迟加载。

并且去掉了synchronized,让性能更优,用static来确保唯一性。

3. 不变模式

一个类的内部状态创建后,在整个生命期间都不会发生变化时,就是不变类

不变模式不需要同步

创建一个不变的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public final class Product {
     // 确保无子类
     private final String no;
     // 私有属性,不会被其他对象获取
     private final String name;
     // final保证属性不会被2次赋值
     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;
     }
 
}

Java中不变的模式的案例有:

  • java.lang.String
  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Character
  • java.lang.Double
  • java.lang.Float
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Short

4. Future模式

核心思想是异步调用

非异步:

异步:

第一次的call_return由于任务还没完成,所以返回的是一个空的。

但是这个返回类似于购物中的订单,将来可以根据这个订单来得到一个结果。

所以这个Future模式意思就是,“未来”可以得到,就是指这个订单或者说是契约,“承诺”未来就会给结果。

Future模式简单的实现:

调用者得到的是一个Data,一开始可能是一个FutureData,因为RealData构建很慢。在未来的某个时间,可以通过FutureData来得到RealData。

代码实现:

1
2
3
public interface Data {    
     public String getResult ();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FutureData implements Data {    
     protected RealData realdata = null ;   //FutureData是RealData的包装    
     protected boolean isReady = false ;    
     public synchronized void setRealData(RealData realdata) {        
         if (isReady) {             
             return ;        
         }        
         this .realdata = realdata;        
         isReady = true ;        
         notifyAll();    //RealData已经被注入,通知getResult()    
     }    
     public synchronized String getResult() //会等待RealData构造完成        
    
         while (!isReady) {            
             try {                
                 wait();    //一直等待,知道RealData被注入           
             } catch (InterruptedException e) {            
                 }        
         }        
         return realdata.result;  //由RealData实现      
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RealData implements Data {
     protected final String result;
     public RealData(String para) {
         // RealData的构造可能很慢,需要用户等待很久,这里使用sleep模拟
         StringBuffer sb = new StringBuffer();
         for ( int i = 0 ; i < 10 ; i++) {
             sb.append(para);
             try {
                 // 这里使用sleep,代替一个很慢的操作过程
                 Thread.sleep( 100 );
             } catch (InterruptedException e) {
             }
         }
         result = sb.toString();
     }
     public String getResult() {
         return result;
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {    
     public Data request( final String queryStr) {        
         final FutureData future = new FutureData();        
         new Thread() {
             public void run()
             {
                 // RealData的构建很慢,          
                 //所以在单独的线程中进行               
                 RealData realdata = new RealData(queryStr);                
                 future.setRealData(realdata);            
             }                                                       
         }.start();        
         return future; // FutureData会被立即返回    
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
         Client client = new Client();
         // 这里会立即返回,因为得到的是FutureData而不是RealData
         Data data = client.request( "name" );
         System.out.println( "请求完毕" );
         try {
             // 这里可以用一个sleep代替了对其他业务逻辑的处理
             // 在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间
             Thread.sleep( 2000 );
         } catch (InterruptedException e) {
         }
         // 使用真实的数据
         System.out.println( "数据 = " + data.getResult());
     }

JDK中也有多Future模式的支持:

接下来使用JDK提供的类和方法来实现刚刚的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.Callable;
 
public class RealData implements Callable<String> {
     private String para;
 
     public RealData(String para) {
         this .para = para;
     }
 
     @Override
     public String call() throws Exception {
         StringBuffer sb = new StringBuffer();
         for ( int i = 0 ; i < 10 ; i++) {
             sb.append(para);
             try {
                 Thread.sleep( 100 );
             } catch (InterruptedException e) {
 
             }
         }
         return sb.toString();
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
 
public class FutureMain {
     public static void main(String[] args) throws InterruptedException,
             ExecutionException {
         // 构造FutureTask
         FutureTask<String> future = new FutureTask<String>( new RealData( "a" ));
         ExecutorService executor = Executors.newFixedThreadPool( 1 );
         // 执行FutureTask,相当于上例中的 client.request("a") 发送请求
         // 在这里开启线程进行RealData的call()执行
         executor.submit(future);
         System.out.println( "请求完毕" );
         try {
             // 这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
             Thread.sleep( 2000 );
         } catch (InterruptedException e) {
         }
         // 相当于data.getResult (),取得call()方法的返回值
         // 如果此时call()方法没有执行完成,则依然会等待
         System.out.println( "数据 = " + future.get());
     }
}

这里要注意的是FutureTask是即具有 Future功能又具有Runnable功能的类。所以又可以运行,最后还能get。

当然如果在调用到future.get()时,真实数据还没准备好,仍然会产生阻塞状况,直到数据准备完成。

当然还有更加简便的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
public class FutureMain2 {
     public static void main(String[] args) throws InterruptedException,
             ExecutionException {
         ExecutorService executor = Executors.newFixedThreadPool( 1 );
         // 执行FutureTask,相当于上例中的 client.request("a") 发送请求
         // 在这里开启线程进行RealData的call()执行
         Future<String> future = executor.submit( new RealData( "a" ));
         System.out.println( "请求完毕" );
         try {
             // 这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
             Thread.sleep( 2000 );
         } catch (InterruptedException e) {
         }
         // 相当于data.getResult (),取得call()方法的返回值
         // 如果此时call()方法没有执行完成,则依然会等待
         System.out.println( "数据 = " + future.get());
     }
}

由于Callable是有返回值的,可以直接返回future对象。

5. 生产者消费者

生产者-消费者模式是一个经典的多线程设计模式。它为多线程间的协作提供了良好的解决方案。 在生产者-消费者模式中,通常由两类线程,即若干个生产者线程和若干个消费者线程。生产者线 程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务。生产者和消费者之间则通 过共享内存缓冲区进行通信。

以前写过一篇用Java来实现生产者消费者的多种方法,这里就不多阐述了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值