美文网首页技术总结
一些基础之一

一些基础之一

作者: AlanKim | 来源:发表于2016-09-27 10:34 被阅读111次
    高并发

    面试的时候都喜欢问这个问题,解决高并发的问题根源在于解决高并发下共享资源的控制问题。也就牵扯到多线程下共享资源的同步问题。

    • 同步:单线程场景下,不需要考虑资源的同步,在执行一个方法或者操作后,一直等待结果,当然这时候可以有个超时的处理机制,但是本质上还是一直阻塞的。
    • 异步:多线程场景下,就需要考虑异步操作,也就是做一件事,不影响别的操作。在执行一个操作后,不去理会结果,而是去处理另外的操作,直到接收到已经处理完成的通知或消息,才去处理对应的结果。

    解决并发和同步问题,主要是通过锁机制,不管是数据库级别,还是缓存级别,都是通过锁来完成。

    • 悲观锁,一般是数据库级别,也就是锁定对应的行级数据,优点是可以控制数据不出问题,缺点是性能方面
    • 乐观锁,一般通过采用version字段的方式来控制,不会锁定对应的数据,性能也比较高,但是会导致脏数据,比如发券场景下的超发现象,以及商品库存的超卖现象。
    • 对于单系统而言,在代码级别使用syncrinized来控制代码的访问是可行的,但是在分布式环境下,只能通过数据库锁或者缓存锁来控制。
    • 缓存锁的优势:速度相对DB是比较快,可以有效降低DB的压力。比如redis的watch,对应的jedis就需要使用setnx自己实现lock方法;比如tair在ldb的incr方法及put方法,incr方法指定lowBound和upBound(也就是一个取值范围)可以解决这个问题,而put方法则传入version版本来做,也是乐观锁。(参见:https://yq.aliyun.com/articles/58928
    • 当然数据库可以通过分库分表的方式来缓解压力,对于老数据可以考虑增加历史库,以保证数据量不会影响对应的数据库操作;而缓存也可以通过设置多个Key来分流单key的锁竞争压力。

    设计模式 -- 后续每天补充

    设计模式是代码设计经验的总结,在代码复用,稳定性以及易理解方面都有一定的保证。具体参见:
    http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html

    • 设计模式的六大原则
      1、开闭原则(Open Close Principle)
      开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
      2、里氏代换原则(Liskov Substitution Principle)
      里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
      3、依赖倒转原则(Dependence Inversion Principle)
      这个是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
      4、接口隔离原则(Interface Segregation Principle)
      使用多个隔离的接口,比使用单个接口要好。降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
      5、迪米特法则(最少知道原则)(Demeter Principle)
      为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立
      6、合成复用原则(Composite Reuse Principle)
      原则是尽量使用合成/聚合的方式,而不是使用继承
    工厂模式

    实现同一个接口的对象,如果需要大量创建,则使用工厂模式。工厂模式又可以细分为普通工厂模式和静态工厂模式。以及为了解决上述工厂模式需要修改原有代码的抽象工厂模式

    • 接口及实现
      // interface
      public interface Sender { public void send();}
      //mailSender
      public class MailSender implements Sender { public void send() { System.out.println("Mail Sender"); }}
      // smsSender
      public class SmsSender implements Sender { public void send() { System.out.println("Sms Sender"); }}
    • 普通工厂模式:
      public class NormalSenderFactory {
      public Sender mailSender(){ return new MailSender(); }
      public Sender smsSender(){ return new SmsSender(); }
      }

    • 静态工厂模式
      public class StaticSenderFactory {
      public static Sender mailSender(){ return new MailSender(); }
      public static Sender smsSender(){ return new SmsSender(); }
      }

    • 调用方式
      public class SenderFactoryTest {
      public static void main(String[] args){
      NormalSenderFactory senderFactory = new NormalSenderFactory();
      senderFactory.mailSender().send();
      senderFactory.smsSender().send();
      // 静态工厂模式调用
      StaticSenderFactory.smsSender().send();
      StaticSenderFactory.mailSender().send(); }
      }

    • 抽象工厂模式
      工厂方法模式有一个问题就是,对象的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则。所以也就有了抽象工厂模式。
      抽象工厂模式增加一个新的接口,用于封装原有的工厂,也就是说可以创建不同的工厂来处理不同的业务。

    • 接口及实现
      //provider
      public interface Provider { public Sender produce();}
      // smsFactory
      public class SmsFactoryProvider implements Provider {
      public Sender produce() { return new SmsSender(); }
      }
      // mailFactory
      public class MailFactoryProvider implements Provider {
      public Sender produce() { return new MailSender(); }
      }
    • 调用方式
      public class AbstractFactoryTest {
      public static void main(String[] args){
      Provider provider = new SmsFactoryProvider(); provider.produce().send();
      }
      }
    单例模式

    保证只有一个对象存在,注意在多线程场景下,需要使用synchronized来锁住对应的instance,也就是需要用synchronized来锁定对应的对象
    注意的是,采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?

    • 首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
    • 其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
    • 再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
    • 最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!
    建造者模式

    相当于将多个工厂的调用放到一起,创建批量的不同子类型的复合体复杂对象,如下:

    public class SenderBuilder {    
        private List<Sender> senderList = Lists.newArrayList();    
    
        public List<Sender> buildMail(int count){        
             for(int i=0;i<count;i++){            senderList.add(new MailSender());        }        
             return senderList;    
        }    
    
       public List<Sender> buildSms(int count){        
           for(int i=0;i<count;i++){            senderList.add(new SmsSender());        }        
           return senderList;    }
       }
    
     // test类
     public class BuilderTest {    
           public static void main(String[] args){        
              SenderBuilder senderBuilder = new SenderBuilder();        
              senderBuilder.buildMail(10);    
       }}
    
    原型模式
    适配器模式

    将一种接口转换成另外一种接口,以消除接口不匹配造成的兼容性问题。

    装饰模式

    需要(动态)扩展一个类的功能,实现同一个接口,并且调用原有类,只不过在原有类对应方法的前后加入自己的业务处理逻辑。

    代理模式

    跟装饰模式其实很相似,都是去扩展一些原有类的功能,不过代理模式会隐藏掉原有类的对象及入口,而装饰模式不会。

    外观模式

    讲互相有依赖关系的对象,统一放入一个新的外观类中,已去除彼此间的依赖,降低耦合。类似于OrderTO中的bizOrder,payOrder以及logicOrder。

    桥接模式

    通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用


    Paste_Image.png
    组合模式

    将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树

    Paste_Image.png
    享元模式

    享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

    策略模式

    策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。

    模板模式

    一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用

    观察者模式

    当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系

    责任链模式

    有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整

    状态模式

    根据状态的不同来实现不同的业务处理

    HashMap的实现

    HashMap基于数组+链表实现,其中数组是key值对应的hashcode,而链表则是对应的entry,entry是key-value的组合,根据key的hashcode决定存储在哪个位置对应的链表中。Entry还包含next属性,指向下一个entry对象。
    在遍历hashMap时,如果需要remove某个位置的参数,要用iterator来remove,直接调用hashmap的remove方法会导致modCount变化,modCount不匹配时会报出ConcurrentModifitionException,导致remove失败。
    key值的hashcode对应的链表需要去重排元素。

    ConcurrentHashMap主要引入了segment,把整个hashmap分段加锁,来达到控制并发同时解决单一锁瓶颈的目的。segment继承了ReentrantLock,另一种锁机制。

    Volatile

    每个线程都有自己的线程栈空间,不存在共享资源的问题。
    基础类型的变量存储在栈中,复杂对象的引用保存在线程栈中,而对应的对象则存储在堆中。
    volatile只能修饰变量,而synchronized则可以修饰类、方法、代码块、变量,作用域是不同的

    • 针对基础类型

      • 对于没有使用volatile修饰的基础类型,每个线程在用到这个对象时,都会从主线程的栈空间中拷贝一份副本到本线程栈中,后面的每次操作都是操作这个副本。只有当线程结束时,会同步给主线程空间。
      • 而对于使用volatile修饰的基础类型,则每个线程操作的都是主线程栈中的变量,但是jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的,还是会存在并发问题。
    • 对于引用变量
      不管是否使用volatile,线程栈中保存的都是一个引用指针,保存的是堆中的内存地址,指向的是堆中的对象。所以这种情况下的读写操作,没办法保证是原子性的。

    IO

    阻塞IO
    非阻塞IO
    SELECTOR

    concurrent包下的常用类
    jvm,类加载
    常见的代码,字符串,字符

    相关文章

      网友评论

        本文标题:一些基础之一

        本文链接:https://www.haomeiwen.com/subject/pxfqyttx.html