Java并发编程学习笔记(一)

3 篇文章 0 订阅

第一部分:基础知识
第三章:对象的共享
3.2  发布与溢出
发布的定义:使得对象能够在当前作用域之外的代码中使用。
发布的注意事项:(1)发布对象可能会破坏封装性 (2)难以维持不变性,在构造完成前发布对象则会破坏线程安全性(引用逃逸)  (3)发布是连锁的。即比如我发布了A对象,假设A对象中能够通过对非私有变量的引用到达B对象,那么B对象也会被发布。  (4)不要在构造方法中使this引用逃逸,比如使用匿名内部类Runnable/Thread 
This引用逃逸的两个条件:(1)在构造方法中使用内部类,因为内部类会自动持有外部类的引用,即this (2)使用了内部类后,不要在构造方法中将其发布。因为这样如果在初始化过程中发生了线程上下文切换,那么有可能看到初始化一半的对象。 
错误操作的具体例子:比如将引用保存到一个公有的静态变量中,因为静态变量是全局的。  (2)由public get方法,得到私有的对象。  (3)在构造函数中调用一个可改写的实例方法(不是private/final方法) (4)
正确的采取措施:(1)使用工厂方法来防止this引用在构造过程中逃逸 (2)在静态初始化函数中初始化一个对象的引用 (3)将对象的引用保存到用volatile/final修饰的变量;或由锁保护的域中  (4)在构造函数中可以创建线程,但不要启动它。http://blog.csdn.net/flysqrlboy/article/details/10607295#


3.3 线程封闭
定义:仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭。这是实现线程安全的最简单的技术。
常见应用:Swing的组件都被封装到EDT(事件分发线程)、JDBC的Connection对象。
常用技术:栈封闭、ThreadLocal类
栈封闭:我们知道,在JVM中的虚拟机栈中维护了方法的局部变量,它们被封闭在执行线程的栈中。但要注意不要被意外发布出去,即逃逸,比如将引用传递给静态变量,或实例中
ThreadLocal:为每个线程维护一个独立的变量的副本
不变性对象:注意一点。对象不仅仅要是final的,而且要防止this引用的逃逸。
除非需要使用更高的可见性,否则应该声明为私有域;除非需要是可变的,否则应当声明为不可变的。 
总结:达到线程安全的几种措施
1. 使用不可变对象+volatile
2. 栈封闭
3. ThreadLocal(注意不要滥用ThreadLocal,会降低代码的可重用性,增加耦合度)




第四章:对象的组合


4.2.1 Java监视器模式
1. 定义:这是一种约定。将对象所有可变状态封装起来,并由对象自己的内置锁来保护。
2. 线程安全的检查:(1)域中的变量是不是满足不可变性(注意引用逃逸情况)(2)有没有不安全的“先检查后执行” (3)如果有其中一种是,就需要通过加锁机制来达成。如果都满足,那么是线程安全的。
3. 疑问:(1)为什么4-14可以 4-16不可以,代码基本一致。区别就是后者在构造方法中赋值初始化,并且是终态的  (2)4-14不行,那为什么4-13又可以 也是锁方法。
4. 回答:因为4-14定义的字段List是public的,所以其他的可以通过new ListHelper().list来执行操作, 这样子synchronize修饰的方法相当于锁的this,那么可能就不是同一个对象,自然也就锁不住了。



第五章:基础构建模块
1.迭代器快速失败特性:注意迭代器并不是只有显式的Iterator才算是迭代;而包括Object的toString()方法,hashcode()/equals()方法,以及各种的containsAll/removeAll/retainAll都会执行迭代操作。
2. BlockingQueue添加了可阻塞的插入和获取操作。如果队列为空,那么获取元素的操作将一直阻塞,直到出现可用元素;如果插入空间已满,那么会阻塞到有可用空间。适用于“生产者-消费者”模式。常见情况,线程池和工作队列。  有一种synchronousQueue的实现,它是一种同步队列,即生产者产生的产品直接供给消费者使用,而不进入队列,提高了效率;但他仅适用于有足够多的消费者且总有一个消费者准备好获取时使用。
3. 工作密取模式:在“生产者-消费者”模式中,多个消费者竞争同一个阻塞队列;而在工作密取中,每个消费者都有自己的一个双端队列(synchronize和threadlocal);如果其中某一个消费者完成了自己的工作,那么它还会“秘密”的从其他线程的尾端获取工作,从而进一步减少竞争情况。
4. InterruptException:处理方式一般是rethrows,即在方法级别重新throws出去;在Runnable的run方法这种不能rethrow的,那就先catch,然后在catch里面恢复中断,即catch(exception){Thread.currenThread().Interrupt();} 以前那种粗暴的直接catch是不对的。。。。这样就丧失了中断的证据,不利于后续的处理、
5. Callable:类似于Runnable,也可以用于启动线程。特点是可以返回结果,返回类型为泛型V,而Runnable的run方法是void的,而且不能向上抛出异常,只能在方法中catch。Callable一般结合线程池Excutor和FutureTask使用。



栅栏和闭锁:
1. 栅栏(barrier)用于等待线程;闭锁(latch)用于等待事件。而且闭锁一旦打开不能关上。而栅栏可以重置,且成功通过栅栏后每个线程会有一个唯一的索引标识号,可以通过这个标识号选出一个领导线程。
2. FutureTask:在Excutor中表示异步任务,属于闭锁的一种。通常结合callable和excutor线程池来使用。比如针对一些计算量很大的方法,可以提前加载需要的数据,并行分布式计算。
3. 信号量:和操作系统讲的差不多,主要两个方法,acquire和release请求和释放信号量。主要用于数据库连接池。
4. Cyclicbarrier应用在CPU密集型的时候,开放数量应是CPU的数目或者CPU+1。如果是I/O密集型,则相应的根据密集程度添加数量。
5. Exchanger用于两个线程间的数据交换信息通信。主要用的就是exchange方法,参数是交换的数据,有一个重载,另一个参数是指定超时的时间。因为本类是线程安全的,并且在交换过中,如果只有一个线程调用了exchange方法,那么会阻塞等待另一个交换数据的线程exchang,时间过长就抛出异常。
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerTest {

    private static final Exchanger<String> exgr = new Exchanger<String>();

    private static Thread[] threads=new Thread[2];
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threads[0]=new Thread(new Runnable() {
            String test1="飞刀飞刀,我是手雷";
            public void run() {
                try {
                    String ano=exgr.exchange(test1);
                    System.out.println(Thread.currentThread().getName()+ano);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        threads[0].start();
        threads[1]=new Thread(new Runnable() {
            String test2="手雷手雷,我是飞刀";
            public void run() {
                try {
                    String ano= exgr.exchange(test2);
                    System.out.println(Thread.currentThread().getName()+ano);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        threads[1].start();
        System.out.println("交换测试完毕");
    }
}




关于第一部分线程安全概念和规则的总结
1.可变的状态是判断是否线程安全的重要参考。可变状态越少越容易确保线程安全性。全是基本数据类型一定是线程安全的。
2. 尽量将变量声明为final的,除非需要它们是可变的
3. 不可变对象一定是线程安全的,这个JVM里面也提到过,线程安全的最高级别就是不可变对象,所以不可变一定是线程安全的。
4. 用锁来保护每个可变变量,在保护同一个不变性条件的时候,要使用同一把锁
5. 在执行复合操作,比如“读取-修改”操作 的时候要持有锁
6. 将同步策略文档化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值