美文网首页
Java并发笔记

Java并发笔记

作者: shaYanL | 来源:发表于2018-08-15 19:08 被阅读0次

    http://ifeve.com/java-concurrency-thread-directory/系列博文总结

    1. 多线程相对于单线程的优势:

    • 提高资源利用率【减少CPU空闲时间】
    • 在特定情况下【线程间很少共享数据】,编码简洁、容易
    • 程序响应更快

    2. 多线程的代价

    • 可能会转让代码更复杂,调试更困难
    • 线程的切换伴随着上下文切换,增加额外耗时
    • 增加资源消耗

    3. 几种并发编程模型

    • 并行工作者模型:容易理解,但涉及共享状态【内存、数据库】会显得复杂;且工作者无法在内部保存共享状态,每次使用需重新读取,这种状况也称为无状态;任务执行的先后顺序也是不确定的
    • 流水线并发模型:无共享并行模型/反应器系统/事件驱动系统。通过事件传递作业,形成各种执行链条,就像流水线一样;大多优劣处恰与并行工作者模型相反

    4. 创建及运行Java线程:

    • 创建Thread类的子类,并重写run()方法;实例化Thread类的子类并调用start()方法
    • 创建一个Thread类的匿名子类的实例,并调用start()方法
    • 实现了java.lang.Runnable接口的类,并实现run()方法;在Thread类的构造函数中传入实现了Runnable接口的类的实例,并调用start()方法
    • 在Thread类的构造函数中传入实现了Runnable接口的匿名类的实例,并调用start()方法

    5. 竞态条件 & 临界区

    • 如果两个线程竞争同一个资源【内存区,系统,文件】,此时如果对资源的访问顺序敏感【不同的顺序产生不同的结果,主要是写操作】,就称存在竟态条件
    • 导致竟态条件发生的代码的区域称为临界区
    • 避免竟态条件:对临界区代码使用同步机制

    6. 线程安全与共享资源

    • 允许被多个线程同时执行的代码称作线程安全的代码;局部变量存在在Java虚拟机栈中是私有的,线程安全,但局部变量中的引用类型指向的对象在Java堆中,如果被其他线程访问到则是线程不安全的
    • 线程控制逃逸规则:如果一个资源的创建,使用,销毁都在同一个线程内完成,且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。
    • "即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了"

    7. 线程安全及不可变性

    • 引用不是线程安全的, 一个不可变的类的引用不是线程安全的
    • 实现一个不可变共享资源value,一旦ImmutableValue定义,value不可再改变
    public class ImmutableValue{
        private int value = 0;
    
        public ImmutableValue(int value){
            this.value = value;
        }
    
        public int getValue(){
            return this.value;
        }
    }
    
    • 不变与只读:“可将不变比作生日,把只读比如年龄;生日不会变化,年龄却会改变”

    8. Java同步关键字synchronzied

    • 可用于实例方法及实例方法内
    • 可用于类方法及类方法内
    • 直接用于方法,则在方法定义前加 synchronzied
    public synchronized void add(long value) {
            count += value;
            System.out.println(Thread.currentThread().getName() + ":====" +count);
        }
    
    • 作用域方法内部,则使用同步块
    public void add2(long value) {
            synchronized (this) {
                count += value;
                System.out.println(Thread.currentThread().getName() + ":====" + count);
            }
        }
    

    9. 线程通信

    • 在共享对象的变量里设置信号值
    • 忙等待(Busy Wait) 持续访问特定信号值,直至信号值改变
    • wait(),notify()和notifyAll() 实现等待-唤醒机制:一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块才可以,因为:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用
    • 丢失的信号(Missed Signals)没有等待的线程,但调用了notify;可增加通知信号避免丢失信号
    • 假唤醒 线程有可能在没有调用过notify()和notifyAll()的情况下醒来
    • 在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象作为管程

    10. TheadLocal-提供线程内的局部变量

    • 各线程私有,初始默认值为null
    • 继承TheadLocal的子类复写initialValue()方法可构造统一初始值
    • ThreadLocalMap是使用ThreadLocal的弱引用作为Key的,gc时容易内存泄露:在调用get,set时将key为null的这些Entry都删除;手动调用ThreadLocal的remove函数;将ThreadLocal对象定义为static的,保持强引用

    11. 死锁

    • 多个线程同时但以不同的顺序请求同一组锁的时候,形成死锁

    12. 避免死锁

    • 加锁顺序:所有线程都按照一定的顺序加锁
    • 限制加锁时间:线程在规定时间内未获得需要的锁,则放弃尝试获取锁且放弃已获得的锁,之后再重试--这种超时和重试机制,在线程过多的情况下,可能出现连续死锁的情况
    • 死锁检测: 通过记录线程和线程持有的锁及请求的锁,检测是否有死锁的情况发生,若发生则可利用释放所有锁,再重试,或者释放特定的锁,再优先级高的线程先运行

    13. 饥饿和公平

    • 饥饿:一个线程得不到CPU时间来执行的状态
    • 公平:解决饥饿的方案
    • 提高等待线程的公平性:使用锁方式代替同步块
    • 公平锁实现:加锁方法只是将线程放入队列,解锁后,只允许队列的第一个线程获得公平锁实例;各个线程只会唤醒各自的wait()方法,因为每个线程都有自己的排队对象,等待唤醒都在各自对象上进行,互无影响,只是靠公平锁来统一调用

    14. 嵌套管程锁死

    • 与死锁很像:都是线程最后被一直阻塞着互相等待
    • 死锁中,二个线程都在等待对方释放锁;嵌套管程锁死中,线程1持有锁A,同时等待从线程2发来的信号,线程2需要锁A来发信号给线程1。

    15. Java中的锁

    • 可重入:线程可以进入任何一个它已经拥有的锁所同步着的代码块
    • finally语句中调用unlock():保证当临界区抛出异常时Lock对象可以被解锁

    16. Java中的读/写锁

    • 读-读能共存,读-写不能共存,写-写不能共存
    • 读取: 没有线程正在做写操作,且没有线程在请求写操作
    • 写入: 没有线程正在做读写操作
    • 读锁重入:要么满足获取读锁的条件(没有写或写请求),要么已经持有读锁(不管是否有写请求);重入后的读锁比写锁优先级高
    • 写锁重入:已经持有写锁,才允许写锁重入(再次获得写锁)

    17. 重入锁死

    • 如果一个线程持有某个管程对象上的锁,那么它就有权访问所有在该管程对象上同步的块。这就叫可重入
    • 当一个线程重新获取不可重入的同步器时,就可能发生重入锁死
    • 避免重入锁死:编写代码时避免再次获取已经持有的锁;使用可重入锁

    18. 信号量

    • 用于在线程间传递信号,以避免出现信号丢失
    • 信号量的数量上限是1时,Semaphore可以被当做锁来使用

    19. 阻塞队列

    • 阻塞队列:从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞
    • 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来
    • 实现类似于带上限的Semaphore的实现
    • 可解决生产者、消费者速率不一致的问题

    20. 线程池

    • 限制应用程序中同一时刻运行的线程数
    • 实现,可用阻塞队列维持需要执行的任务,让后让线程池的线程一直从阻塞队列中取任务、执行任务,直至线程停止

    21. Java并发编程之CAS

    • 一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值

    22. 同步器

    • 状态:是用来确定某个线程是否有访问权限
    • 访问条件:决定改变状态的方法的线程是否可以对状态进行设置;通常是放在一个while循环里,以避免虚假唤醒
    • 状态变化:一个线程获得了临界区的访问权限,它得改变同步器的状态,让其它线程阻塞
    • 通知策略: 通知其它等待的线程状态已经变,3种策略(all, one, the one)
    • Test-and-Set方法:检查访问条件,如若满足,该线程设置同步器的内部状态来表示它已经获得了访问权限
    • set方法:仅是设置同步器的内部状态,而不先做检查

    23. 非阻塞算法

    • 阻塞算法会阻塞线程直到请求操作可以被执行。非阻塞算法会通知请求线程操作不能够被执行,并返回。
    • volatile变量:非阻塞的;适用于单线程写的情况:只有一个【不一定是固定一个】线程在执行一个 raed-update-write 的顺序操作,其他线程都在执行读操作,将不会发生竞态条件
    • 传统的锁:会使用同步块或其他类型的锁阻塞对临界区域的访问,可能会导致线程挂起
    • 乐观锁:允许所有的线程在不发生阻塞的情况下创建一份共享内存的拷贝,通过 CAS操作就可以把变化写入共享内存,线程获得它们想修改的数据的拷贝并做出修改;用于共享内存竞用不是非常高的情况,也用于拷贝时间短的情况
    • 共享预期的修改:可以共享预期的修改,但变相添加了锁,阻止其他线程提交预期修改
    • A-B-A问题:一个变量被从A修改到了B,然后又被修改回A的一种情景。其他线程对于这种情况却一无所知
    • A-B-A问题的解决方案:不再仅仅替换指向一个预期修改对象的指针,而是指针结合一个计数器,然后使用一个单个的CAS操作来替换指针 + 计数器

    24. 阿姆达尔定律

    T(N) = N个线程(并行因子为N)下执行的总时间
    B = 不可以并行的总时间
    T- B = 并行部分的总时间

    • T(N) = B + (T(1) – B) / N
    • T(O, N) = B / O + (1 - B / O) / N;不可并行部分通过一个因子O来优化
    • 增速比: Speedup = T / T(O , N);
    • 用于量化程序优化的效果

    相关文章

      网友评论

          本文标题:Java并发笔记

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