《Java并发编程实战》读书笔记

3.1可见性

     “...因此,从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。”

       是否理解为,读取的时候进入同步代码块,所以读到是最新的数据,同时不会被写入操作更改。写入的时候退出同步代码块,所以不安全。

       volatile变量适用于一写多读。

     

3.2发布和逸出

     程序清单3.7和3.8看了半天一直很模糊,不过最后的一句解释还是很明显的。

     看完注释【2】才明白作者的意图,因为构造方法还未完成前,this不能给外部对象使用,所以,不建议将this发布出去。


3.52 

      “即使某个对象的引用对其他线程来说是可见的,也不意味着对象状态对于该线程一定是可见的。为了使对象状态呈现出一致性视图,必须使用同步”

       我理解为:如果对象是volatile的那么你重新new 一个对象赋给的引用,其他线程会立即可见。但是如果你并不是重新new的,而是修改对象的某个参数,它并不能立即可见。


3.53 “安全发布 安全发布的常用模式”

        ”在静态初始化函数中初始化一个对象引用”

          理解为:静态初始化函数指的是class xx{   static{}   } 或者 static XX x = new XX()  ,其它的初始化都会遇到并发问题。


4.33 当委托失效时

        “当且仅当一个变量参与到包含其他状态变量的不变性条件时,才可以声明为volatile类型。” 

        这里是不是有错? 3.14是这样描述的,“该变量不参与到不变性条件的判断”。

        不变性什么意思一直理解的不好。后来看了一下注释。例如程序清单4-10中的 // INVARIANT: lower <= upper 就是一个不变性。

         

4.35 “[2]如果将拷贝构造函数实现为this(p.x, p.y),那么会产生竞态条件,而私有构造函数则可以避免这种竞态条件。这是私有构造函数捕获模式(Private Constructor Capture Idiom, Bloch and Gafter,2005)的一个实例。

          开始一直没看懂,后来网上搜索到了例子代码才看明白,code如下:

package test;

 class SafePoint {
    private int x;
    private int y;

    public SafePoint(int x, int y){
        this.x = x;
        this.y = y;
    }

    public SafePoint(SafePoint safePoint){
        this(safePoint.x, safePoint.y);
    }

    public synchronized int[] getXY(){
        return new int[]{x,y};
    }

    public synchronized void setXY(int x, int y){
    	System.out.println("~~~~~");
        this.x = x;
        //Simulate some resource intensive work that starts EXACTLY at this point, causing a small delay
        try {
            Thread.sleep(10 * 100);
        } catch (InterruptedException e) {
         e.printStackTrace();
        }
        this.y = y;
    }

    public String toString(){
      return x+""+y;
    }
}

public class SafePointMain {
public static void main(String[] args) throws Exception {
    final SafePoint originalSafePoint = new SafePoint(1,1);

    //One Thread is trying to change this SafePoint
    new Thread(new Runnable() {
        @Override
        public void run() {
            originalSafePoint.setXY(2, 2);
            System.out.println("Original : " + originalSafePoint.toString());
        }
    }).start();

    //The other Thread is trying to create a copy. The copy, depending on the JVM, MUST be either (1,1) or (2,2)
    //depending on which Thread starts first, but it can not be (1,2) or (2,1) for example.
    new Thread(new Runnable() {
        @Override
        public void run() {
            SafePoint copySafePoint = new SafePoint(originalSafePoint);
            System.out.println("Copy : " + copySafePoint.toString());
        }
    }).start();
}
}

主要原因在于构造函数无法使用同步!


8.33饱和策略

      当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。(如果某个任务被提交到一个已被关闭的Executor时,也会用到饱和策略。)JDK提供了几种不同的RejectedExecutionHandler实现,每种实现都包含有不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。

  1. public class CallerRunTest {  
  2.     private final ThreadPoolExecutor exec ;  
  3.     public CallerRunTest(){  
  4.         exec = new ThreadPoolExecutor(2,2,0L,TimeUnit.MICROSECONDS,  
  5.                 new LinkedBlockingQueue<Runnable>(2));  
  6.         exec.setRejectedExecutionHandler(  
  7.                 new ThreadPoolExecutor.CallerRunsPolicy());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {  
  11.         MyCommand c1 = new MyCommand("c1");  
  12.         MyCommand c2 = new MyCommand("c2");  
  13.         MyCommand c3 = new MyCommand("c3");  
  14.         MyCommand c4 = new MyCommand("c4");  
  15.         MyCommand c5 = new MyCommand("c5");  
  16.         CallerRunTest c = new CallerRunTest();  
  17.         c.submit(c1);  
  18.         c.submit(c2);  
  19.         c.submit(c3);  
  20.         c.submit(c4);  
  21.         c.submit(c5);  
  22.     }  
  23.   
  24.     public void submit(Runnable command){  
  25.         System.out.println(Thread.currentThread().getName()+" submit tast...");  
  26.         exec.submit(command);  
  27.     }  
  28. }  


线程不足了,调用者 自己运行。


11.4.4避免热点域

         在单线程或者采用完全同步的实现中,使用一个独立的计数能很好地提高类似size和isEmpty这些方法的执行速度,但却导致更难以提升实现的可伸缩性,因为每个修改map的操作都需要更新这个共享的计数器。即使使用锁分段技术来实现散列链,那么在对计数器的访问进行同步时,也会重新导致在使用独占锁时存在的可伸缩性问题。一个看似性能优化的措施——缓存size操作的结果,已经变成了一个可伸缩性问题。在这种情况下,计数器也被称为热点域,因为每个导致元素数量发生变化的操作都需要访问它。
  为了避免这个问题,ConcurrentHashMap中的size将对每个分段进行枚举并将每个分段中的元素数量相加,而不是维护一个全局计数。为了避免枚举每个元素,ConcurrentHashMap为每个分段都维护了一个独立的计数,并通过每个分段的锁来维护这个值。


7.2.1 示例 日志服务

       为LogWriter提供可靠关闭操作的方法是解决竞态条件问题,因而要使日志消息的提交操作成为原子操作。然而,我们不希望在消息加入队列时去持有一个锁,因为put方法本身就可以阻塞。我们采用的方法是:通过原子方式来检查关闭请求,并且有条件地递增一个计数器来“保持”提提交消息的权利,如下所示:


14.1.2 示例:通过轮询与休眠来实现简单的阻塞

         这种通过轮询与休眠来实现阻塞操作的过程需要付出大量的努力。如果存在某种挂起线程的方法,并且这种方法能够确保当某个条件成真时线程立即醒来,那么将极大地简化实现工作。这正是条件队列实现的功能。


14.1.3 条件队列

        正如每个Java对象都可以作为一个锁,每个对象同样可以作为一个条件队列,并且Object中的wait、notify和notifyAll方法就构成了内部条件队列的API。

        与使用“休眠”的有界缓存相比,条件队列并没有改变原来的语义。它只是在多个方面进行了优化:CPU效率、上下文切换开销和响应性等。如果某个功能无法通过“轮询和休眠”来实现,那么使用条件队列也无法实现[5],但条件队列使得在表达和管理状态依赖性时更加简单和高效。


摘取取JDK部分代码,线程退出的时候会执行notifyAll(); 用于解释为什么有时候notify()会调用多次。因为线程结束会调用notifyAll();要注意锁的对象!

    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

    void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);


            if (nthreads == 0) {
                notifyAll();
            }
            if (daemon && (nthreads == 0) &&
                (nUnstartedThreads == 0) && (ngroups == 0))
            {
                destroy();
            }
        }
    }


16.2.2 安全的发布

        事实上,Happens-Before比安全发布提供了更强可见性与顺序保证。如果将X从A安全地发布到B,那么这种安全发布可以保证X状态的可见性,但无法保证A访问的其他变量的状态可见性。然而,如果A将X置入队列的操作在线程B从队列中获取X的操作之前执行,那么B不仅能看到A留下的X状态(假设线程A或其他线程都没有对X再进行修改),而且还能看到A在移交X之前所做的任何操作(再次注意同样的警告)。[1]

         [1]JMM确保B至少可以看到A写入的最新值,而对于随后写入的值,B可能看到也可能看不到。

         解释一下:指的是发布X,那么X指向哪块内存,是实时可见的。但是X内存内部的变量指引被修改了,是无法保证可见性的。


16.2.4 双重检查加锁

         DCL的真正问题在于:当在没有同步的情况下读取一个共享对象时,可能发生的最糟糕事情只是看到一个失效值(在这种情况下是一个空值),此时DCL方法将通过在持有锁的情况下再次尝试来避免这种风险。然而,实际情况远比这种情况糟糕——线程可能看到引用的当前值,但对象的状态值却是失效的,这意味着线程可以看到对象处于无效或错误的状态


16.3 初始化过程中的安全性
         初始化安全性只能保证通过final域可达的值从构造过程完成时开始的可见性。对于通过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性。


名词解释

lock.tyrlock(time ) //定时锁

while(true){lock.trylock()} //轮询锁



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值