zoukankan      html  css  js  c++  java
  • Java多线程设计模式(2)

    《Java多线程设计模式》读书笔记2

    目录:
    1 Java的内存模型
    2 Single Threaded Execution Pattern
    3 Guarded Suspension Pattern
    4 Balking Pattern
    5 Producer-Consumer Pattern
    6 Read-Write Lock Pattern

    =============Java的内存模型======================

    Java的内存模型分为主存储器与工作存储器两种。
    主存储器(与硬件上的主存无关,仅为抽象概念)就是实例位置所在的区域,所有的实例都存在于主存储器内。尤其是实例所拥有的字段即位于主存储器内的区域。主存储器为所有线程所共有。工作存储器为各个线程所拥有的作业区,所有的线程都有其专用的工作存储器。在工作存储器中存有主存储器中必要的拷贝,称之为工作拷贝。
    线程无法直接对主存储器直接进行操作,因此也无法直接引用字段的值,当线程需要引用字段的值时,需要把它从主存储器上拷贝到自己的工作存储器中。至于要修改字段的值时,也要先修改工作拷贝,然后把修改从工作存储器拷贝回主存储器中。至于何时完成数据在工作存储器和主存储器之间的映像,则有Java执行处理系统决定。
    对于多个线程共享的字段,一般由synchronized或者volatile来保护。
    synchronized用来完成线程的共享互斥。而volatile不负责线程的共享互斥,仅负责内存的同步。
    synchronized一方面制作临界区,使得该区域只能同时允许一个线程进行操作;另一方面,当进入和退出临界区时,执行内存的同步操作,即把当前工作区的修改映像到主存储器上。在进入synchronized时,除了工作拷贝到主存储器的映像之外,还会释放工作存储器的工作拷贝,如果之后再引用主存储器上值,则必须从主存储器拷贝到工作存储器。
    虽然在进入和离开synchronized时,会发生内存的同步,但是如果是“在synchronized内”或者“在synchronized外”时,内存何时同步是不确定的,取决于Java运行处理系统。
    在单线程中,可以不考虑内存的同步问题,当然线程的同步也不需考虑。
    volatile内存同步:当线程欲引用volatile字段的值时,通常都会发生从主存储器到工作存储器的拷贝操作,反之,将值指定给写着volatile的字段后,工作存储器的内容通常会映像到主存储器。另外,对于long和double类型,默认的赋值操作不是atomic的,如要以atomic方式指定这些类型的值,则需使用volatile关键字,此时,volatile实现原子操作,而非内存同步。问:如果要指定long,double类型字段的内存同步,怎么办?难道只能用synchronized来实现?

    =============Single Threaded Execution Pattern=============

    当我们修改由多个线程共享的变量,变量就会失去安全性。所以我们应该仔细找出变量状态不安定的范围,将这个范围设置为临界区,并对临界区施加保护,限制同时只能有一个线程执行它。在java中,可以使用synchronized关键字定义临界区,保护共享字段。
    适用性:数据可被多个线程访问,并且状态可能变化的时候。
    ===================Immutable Pattern===============
    当一个类的实例声明之后,状态就完全不再改变,此时就算用多个线程同时调用这个类的方法也无所谓,所以方法没有synchronized的必要,这样一来,可以在不丧失安全性与生命性的前提下,提高程序的性能。而要实现类的immutable,则在类的实现中必须小心防备,不要提供能够改变实例状态的方法或者破绽。

    Java final的用法
    final类:final类无法被继承
    final方法:如果是实例方法,则无法被子类所覆盖,若是类方法,则无法被子类隐藏。
    final字段:其值只能指定一次,要么在声明时初始化,要么在构造函数中赋值(静态字段则是在static块中初始化)。
    final变量与参数:final变量只能在定义时指定一次,final参数则是仅在调用时传值指定。

    ===================Guarded Suspension Pattern===============

    当满足某条件时,直接执行某动作;如果不满足条件,等待,直到条件满足时被唤醒。
    比如请求队列,如果队列已经为空,则此时要执行获取请求操作则必须等待;当添加新请求时唤醒等待的线程。
    public synchronized Request getRequest(){
        while(queue.size() <= 0){
            try{
            wait();
        }catch(InterruptedException e){}
        return (Request)queue.removeFirst();
    }
    public synchronized void putRequest(Request req){
        queue.addLast(req);
        notifyAll();
    }

    ===================Balking Pattern===============

    当现在不适合进行某个操作,或者没有必要进行某个操作时,就直接放弃进行这个操作而返回。
    public synchronized void save(){
        if(!changed){
          return;
        }
        dosave();
        changed = false;
    }
    使用场景:(1)不需要刻意去执行的时候(提高执行性能);(2)不想等待警戒条件时(提高响应性);(3)警戒条件只有第一次成立时(比如初始化)。

    ===================Producer-Consumer Pattern===============

    Producer-Consumer模式假设所生产的产品放置在一个长度有限制的缓冲区,如果缓冲区满了,则生产者必须停止继续将产品放到缓冲区中,直到消费者取走了产品而有了空间,而如果缓冲区中没有产品,当然消费者必须等待,直到有新的产品放到缓冲区中。与Guarded Suspension 模式类似的,只不过Guarded Suspension模式并不限制缓冲区的长度。
    可能有多个生产者和多个消费者,当只有一个生产者和一个消费者的时候,又叫管道Pipe Pattern。
    Java主要通过synchronized,wait与notify/notifyAll方法来实现producer与consumer的协调。

    ===================Read-Write Lock Pattern===============

    Read-Write Lock Pattern将读取与写入分开处理。在读取数据之前,必须获取用来读取的锁定。而要写入的时候,则必须获取用来写入的锁定。
    因为读取的时候,实例的状态不会改变,所以就算有多个线程在同时读取也没有关系。但有人在读取的时候就不可做写入的操作。
    写入的时候,实例的状态就会改变,当有一个线程在写入的时候,其他的线程不可以进行读取或写入。一般来说,进行共享互斥会使程序性能变差,但将写入的共享互斥与读取的共享互斥分开思考,就可以提升程序的性能。
    Java语言中,使用finally可以避免忘记解除锁定。比如:
    获取锁定
    try{
        实际的操作   
    }finally{
        解除锁定
    }

  • 相关阅读:
    崩漏
    李翰卿治疗小儿五更咳嗽经验
    34名国宝级名老中医秘方集锦(一)
    黄煌儿科经方
    中医临床参考
    HTML 动态云启动画面
    CANVAS 水波动态背景
    PHP MYSQL单向同步方案
    PHP 获取指定目录下所有文件(包含子目录)
    JS 深度拷贝 Object Array
  • 原文地址:https://www.cnblogs.com/gaojing/p/1755644.html
Copyright © 2011-2022 走看看