java高并发-并行模式(上).md

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态。 
并行:当系统有一个以上的CPU时,则操作的过程有可能非并发,当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行。

单例设计模式:保证一个类在内存中只有一个对象 
模式:模式就是解决一类问题的固定步骤; 
主要要掌握的设计模式是: 
单例设计模式,模板设计模式,装饰者设计模式,观察者设计模式,工厂设计模式

单例设计模式

饿汉单例设计模式:

1.私有化构造函数 
2.声明本类的引用类型变量,并且使用该变量指向本类对象 
3.提供一个公共静态的方法获取本类的对象

懒汉设计模式:

1.私有化构造函数; 
2.声明本类的引用类型变量,但是不要创建对象; 
3.提供公共静态的方法获取本类的对象,获取之前先判断是否已经创建了本类对象,如果已经创建了,那么直接返回对象即可,如果还没有创建,那么先创建本类的对象; 
推荐使用:饿汉单例设计模式。因为懒汉单例设计模式会存在线程安全问题,目前还不能保证一个类在内存中只有一个对象存在; 

懒汉模式之所以存在线程安全的问题原因在于:线程1在判断进入getInstance()方法时,在程序继续向下执行时,此时线程2获得cpu的执行权,开始进行判断s2是否为空,若为空继续向下执行,等到成功创建了对象时,线程1又继续向下执行,因此这样会出现线程安全的问题,cpu进行抢占。

 
 
//饿汉模式
class Singleton1{
private static Singleton1 s1 = new Singleton1();//private的作用是将成员变量将其封装,static的作用是保证在一个类中只有一个对象存在;
 
//私有化构造函数
private Singleton1(){//private的权限是私有的,只能在本类中使用,私有的构造函数不可能形成实例;
}
//提供一个公共静态的方法获取本类的对象
public static Singleton1 getInstance(){//如果不将方法设置成静态,那么在调用的时候就需要使用对象.getInstance();
return s1;
}
}
//懒汉模式
class Singleton2{
//私有化构造函数
private Singleton2(){
}
//声明本类的引用类型变量,但是不要创建对象;
private static Singleton2 s2 = null;
//提供公共静态的方法获取本类的对象,获取之前先判断是否已经创建了本类对象,如果已经创建了,那么直接返回对象即可,如果还没有创建,那么先创建本类的对象;
public static Singleton2 getInstance(){
if(s2==null){
s2 = new Singleton2();
}
return s2;
}
}
public class Demo1 {
public static void main(String[] args){
/*new Singleton();//因为其构造函数是私有化,所以在类的外部是无法访问;
*/
//假如getInstance()方法为非静态的,那么此时需要使用对象.getInstance();
//对象.getInstance();需要注意的是此时的对象是私有的,只能在本类中使用,因此无法调用对象.getInstance()方法;
Singleton2 s1 = Singleton2.getInstance();
Singleton2 s2 = Singleton2.getInstance();
System.out.println("是同一个对象吗"+(s1==s2));
}
}

以上介绍的两种模式各有千秋,两种模式均存在缺陷。 
饿汉模式的不足之处在于:Singleton()构造函数。或者说Singleton()实例在什么时候创建是不受控制的。对于静态成员s1和s2,它会在类第一次初始化的时候被创建,这个时刻并不一定是getInstance()方法第一次被调用的时候。 
懒汉模式的不足之处在于:相比饿汉模式,懒汉模式并不需要实例化instance,当getInstance()第一次在被调用时,创建单例对象,但是坏处也很明显,并发环境下加锁,竞争激烈的场合可能对性能产生了一定的影响。 
为了弥补两种模式的缺陷,将两种模式的优势合二为一。

 
 
public class StaticSingleton{
private StaticSingleton(){
System.out.println("StaticSingleton is create");
}
private static class SingletonHolder{//内部类SingleonHolder被声明为private,这使得不能在外部访问并初始化它;
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance(){//在getInstance()方法第一次调用时,实例StaticSingleton才会被创建
return SingletonHolder.instance;//在getInstance的内部将SingletonHolder进行实例化
}
}

不变模式

在并行软件开发的过程中,同步操作是不可避免的,当多线程对同一个对象进行读写操作时,为了保证数据的一致性和正确性,有必要对对象进行同步操作,而同步操作对系统的性能有一定的损耗,为了尽可能的去除这些同步操作,为了提高并行程序的性能,可以使用一种不可改变的对象,依靠对象的不变性,可以确保其在没有同步操作的多线程的环境中可以保持数据的完整性和一致性,即不变模式。

不变模式的核心思想是:一个对象被创建以后,其内部状态将永远不会发生改变,没有一个线程对其内部状态和数据进行修改。 
不变模式的应用场景需要满足以下2个条件:

  • 当对象创建后,其内部状态和数据不再发生任何变化;
  • 对象需要被共享,被多线程频繁访问; 
    在java语言中,不变模式的实现很简单,为了确保对象被创建后,不发生任何变化,并保证不变模式正常工作,需要注意以下四点:
  • 去除setter方法以及所有修改自身属性的方法;
  • 将所有属性设置为私有,并用final标记,确保其不可修改;
  • 确保没有子类可以重载修改它的行为;
  • 有一个可以完整创建对象的构造函数;
 
 
public final class Product {
private final String no; //将成员变量设置为私有的,不会被其他对象所获取;
private final String name; //final保证属性不会被2次赋值
private final double price; //private final 所修饰的成员变量相当于常量;
 
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;
}
}

final()关键字:final顾名思义是最后的,最终的,不可更改的意思。

  • 被final修饰的类不可以再被继承,相当于最后一个类,不能再往下继承了;
  • 被final修饰的方法被子类继承后,不可以被子类重写;
  • 被final修饰的变量会变成常量,只能进行一次赋值;
  • final修饰的成员变量必须在声明时或在类的构造方法中进行赋值;

里氏替换原则通俗的来说:子类可以扩展父类的功能,但不能改变父类原有的功能。包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  • 子类可以增加自己特有的方法;
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松;
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格;

注意:重写和重载的区别: 
override是重写(覆盖)了一个方法,以实现不同的功能。一般是用于子类在继承父类时,重写(重新实现)父类中的方法

重写(覆盖)的规则:

1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载. 
2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。 
3、重写的方法的返回值必须和被重写的方法的返回一致; 
4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类; 
5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。 
6、静态方法不能被重写为非静态的方法(会编译出错)。 
overload是重载,一般是用于在一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同。

重载的规则:

1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样); 
2、不能通过访问权限、返回类型、抛出的异常进行重载; 
3、方法的异常类型和数目不会对重载造成影响;

生产者-消费者模式

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

从上图生产者-消费者模式架构图中可知,其核心组件是共享内存缓冲区,它作为生产者和消费者间的通信桥梁,避免了生产者和消费者的直接通信。与此同时,由于内存缓冲区的存在,允许生产者和消费者在执行速度上存在时间差,无论是生产者在某一局部时间内速度高于消费者,还是消费者在局部时间内高于生产者,都可以通过共享内存缓冲区得到缓解,确保系统正常运行。

 
 
//生产者线程的实现
import java.text.MessageFormat;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class Producer implements Runnable{
private volatile boolean isRunning = true; //volatile修饰一个变量时,相当于告诉虚拟机,这个变量极有可能被某些程序或者线程修改,其可以保证数据的可见性和有序性;
private BlockingQueue<PCData> queue; //内存缓冲区;
private static AtomicInteger count = new AtomicInteger(); //总数,原子操作;
private static final int SLEEPTIME = 1000; //设置线程的睡眠时间;
 
public Producer(BlockingQueue<PCData> queue){//创建生产者的构造函数,将构建的PCData对象放在BlockingQueue队列中;
this.queue = queue;
}
public void run(){
PCData data = null;
Random r = new Random();
System.out.println("start producer id="+Thread.currentThread().getId());
try{
while(isRunning){
Thread.sleep(r.nextInt(SLEEPTIME)); //nextInt()读入的仅是数字,而换行符没有读入,nextLine(),它是读到行分隔符就结束;
data = new PCData(count.incrementAndGet()); //构造任务数据
System.out.println(data + "is put into queue");
if(!queue.offer(data,2, TimeUnit.SECONDS)){
System.out.println("failed to put data:"+data);
 
}
}
}catch(InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
public void stop(){
isRunning = false;
}
}
//消费者的实现
class Consumer implements Runnable{
private BlockingQueue<PCData> queue;
private static final int SLEEPTIME = 1000; //设置线程的睡眠时间;
public Consumer(BlockingQueue<PCData> queue){//创建消费者构造函数,构建PCData对象,并将其放入到BlockingQueue队列中;
this.queue = queue;
}
public void run(){
System.out.println("start Consumer id="+Thread.currentThread().getId());
Random r = new Random();
 
try{
while(true){
PCData data = queue.take();//从BlockingQueue队列中取出PCData对象
if(null!= data){
int re = data.getData() * data.getData(); //计算平方
System.out.println(MessageFormat.format("{0}*{1}={2}",data.getData(),data.getData(),re));
Thread.sleep(r.nextInt(SLEEPTIME));
}
}
}catch(InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
//PCD作为生产者和消费者之间的共享数据模型,定义如下:
final class PCData{
private final int intData;
public PCData(int d){
intData = d;
}
public PCData(String d){
intData = Integer.valueOf(d);//integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,缓存了[-128~127]之间的数字。此数字范围内传参则直接返回缓存中的对象
}
public int getData(){
return intData;
}
public String toString(){
return "data:"+intData;
}
}
//在主函数中,创建三个生产者和三个消费者,并让它们协作运行。在主函数的实现中,定义LinkedBlockingQueue作为BlockingQueue的实现类
public class Main{
public static void main(String[] args) throws InterruptedException{
//建立缓冲区
BlockingQueue<PCData> queue = new LinkedBlockingQueue<PCData>(10);//定义LinkedBlockingQueue作为BlockingQueue的实现类
Producer producer1 = new Producer(queue); //建立生产者
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Consumer consumer1 = new Consumer(queue); //建立消费者
Consumer consumer2 = new Consumer(queue);
Consumer consumer3 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool(); //建立线程池,newCachedThreadPool()方法返回一个可根据实际情况调整线程数量的线程池
service.execute(producer1);
service.execute(producer2);
service.execute(producer3);
service.execute(consumer1);
service.execute(consumer2);
service.execute(consumer3);
Thread.sleep(10*1000);
producer1.stop();
producer2.stop();
producer3.stop();
Thread.sleep(3000);
service.shutdown();
}
}

 
上图是生产者-消费者的实现类图,其中,BlockingQueue充当了共享内存缓冲区。用于维护任务或者数据队列(PCData对象)PCData对象表示一个生产任务,或者相关任务的数据,生产者对象和消费者对象均引用同一个BlockingQueue实例。生产者负责创建PCData对象,并将它加入到BlockingQueue中,消费者则从BlockingQueue队列中获取PCData;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值