实战Java高并发程序设计(第2版) 笔记

基于《实战Java高并发程序设计(第2版)》葛一鸣、郭超著 一书整理的笔记

注:使用Sublime Text编辑的,博客显示效果并不理想,可粘贴到本地使用Sublime Text打开阅读。

 

程序并发级别分类:阻塞、无饥饿、无障碍、无锁、无等待

无锁:无锁的情况下,所有线程都尝试对临界区进行访问,无锁的并发保证必然有一个线程在有限步内完成操作离开临界区。
无等待:所有线程都要在有限步骤内完成。

JMM技术的关键点都是围绕多线程的原子性、可见性、有序性来建立的
原子性:一个操作是不可中断的。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
可见性:指当一个线程修改某个共享变量的值,其他线程是否能够立即知道这个修改。
有序性:保证指令重排后不会破坏原有的语义结构

指令重排原则:heppen-before规则 规定那些指令不能重排
    程序顺序原则:一个线程内保证语义的串行性
    volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
    锁规则:解锁(unlock)必然在随后的加锁(lock)前
    传递性:A先于B,B先于C,那么A必然先于C
    线程的start()方法先于它的每一个动作
    线程的所有操作先于现成的终结(Thread.join())
    线程的中断(interrupt())先于被中断线程的代码
    对象的构造函数执行、结束先于finalize()方法

Java线程的状态枚举:NEW,RUNNBALE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;

终止线程,stop()方法已经被废弃,太过暴力,强行把执行到一半的线程中止,可能会导致一些数据不一致的问题

线程中断
public void Thread.interrupt();    //中断线程
public boolean Thread.isinterrupted();    //判断是否被中断
public static boolean Thread.interrupted();    //判断是否被中断,并清除当前中断状态

中断相比于stopMe这类boolean变量的优点在于中断可以识别wait()和sleep()方法,因为他们会抛出InterruptedException

wait()和notify()
任何对象都可以调用wait()方法(wait()方法调用必须包含在synchronized语句中),线程A调用obj.wait()时,则线程A会进入等待状态,直到有其他线程调用了obj.notify(),线程A才会继续执行。
原理:线程A调用obj.wait(),线程A则会进入obj的等待队列,线程B调用obj.notify()时,则会从obj的等待队列中随机唤醒一个线程。botifyAll()是唤醒等待队列中所有线程。wait()方法和notify()方法都尝试获取对象obj上的监视器,执行wait()方法的线程会先获取对象的监视器,wait()方法结束后就释放监视器,执行notify()的线程也会尝试获取对象的监视器,当前线程执行后释放对象监视器,被唤醒的线程首先得获取对象的监视器才能继续执行。

wait()和sleep():两者均可以导致线程等待,wait()会释放目标对象的锁,sleep()不会释放任何资源。

挂起与继续执行线程
suspend()线程挂起
resume()继续执行
已被废弃,suspend()在导致线程暂停的同时,并不会去释放任何锁资源。此时其他线程想要访问被它暂用的锁时,都会被牵连,导致无法正常继续运行。知道对应的线程上进行了resume()操作,被挂起的线程才能继续,从而其他所有阻塞在相关锁上的线程也可以继续执行。但是如果resume()操作意外地在suspend()前执行了,那么被挂起的线程可能很难有机会被继续执行。而且更严重的是被挂起的线程所占用的锁不会被释放,因此可能会导致整个系统工作不正常。而且被挂起的线程其状态仍然是RUNNABLE,严重影响我们对系统当前状态的判断。

等待线程结束和谦让
join():很多时候一个线程的输入可能非常依赖与另一个或多个线程的输入,此时这个线程就需要等待依赖线程执行完毕,才能继续执行。
yield()

synchronized多种用法:
指定对象加锁:略。
作用于实例方法:相当对当前实例加锁(当前实例对象)。
作用于静态方法:相当于对当前类加锁(当前类对象)。
注:不要在不可变对象上加锁(Integer,String等),因为每次该对象变时都相当于创建了一个新的对象,就会导致是在不同对象上加锁。


ArrayList是一个线程不安全的容器,可用线程安全的Vector代替ArrayList.
HashMap是一个线程不安全的容器,可用线程安全的ConcurrentHashMap代替HashMap.

重入锁ReentrantLock lock = new ReentrantLock ();
lock.lock();    //加锁
lock.unlock();    //解锁
重入锁可以提供中断处理能力,如果一个线程在等待锁,那么它依旧可以收到一个通知,被告知无需再等待,可以停止工作了。这种情况对于处理死锁有一定帮助。
lock.lockInterruptibly();     //对中断进行响应的锁申请动作,即在等待锁的过程中可以响应中断。
锁申请等待限时
lock.tryLock(5,TimeUnit.SECONDS);     //进行一次限时等待的锁申请动作,参数一表示等待时长,参数二表示计时单位,返回值为boolean类型
lock.tryLock();无参的tryLock当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请锁会成功,返回true,否则不会等待,直接返回false;
公平锁:不会产生饥饿现象,只要你排队总会等到资源的
public ReentrantLock(boolean fair);
synchronized锁是非公平的,重入锁允许设置公平锁,但是实现公平锁必须要求系统维护一个有序队列,实现成本高,性能低下。

重入锁的好搭档:Condition条件
1.await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()或者signAll()方法时,线程会重新获得锁并继续执行。还活着当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
2.awaitUninterruptibly()方法与await()方法基本相同,但它不会在等待过程中响应中断。
3.signal()方法用于唤醒一个在等待的线程。相对的signalAll()方法会唤醒所有在等待中的线程。这和Object.notify()方法很类似。

信号量
public Semaphore(int permits)    //指定信号量的准入数,即同时能申请多少个许可。
acquire()方法尝试获取一个准入的许可。若无法获得线程会等待,直到有线程释放许可或者当前线程被中断。
acquireUninterruptibly()    //不响应中断的acquire();
tryAcquire();    //尝试获取一个许可.立即返回

ReadWriteLock读写锁
如果使用重入锁或内部锁,理论上所有读之间、读与写之间、写与写之间都是串行操作。由于读操作并不破坏数据完整性,这种等待显然不合理。于是读写锁就有了发挥功能的余地。

CountDownLatch倒计时器
public CountDownLatch(int count);    //参数为计数器的计数个数

CyclicBarrier循环栅栏
public CyclicBarrier(int parties,Runnable barrierAction);    //parites表示计数器计数个数,Barrier Action表示一次计数完成后,系统会执行的动作。
假如设置计数器为10,凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程。

LockSupport
LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,他弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Object.wait()相比,他不需要某个对象的锁,也不会抛出InterruptedException异常。
park();    //阻塞当前线程

提高锁性能的几点建议:
    1减少锁持有时间(只给必须要加锁的代码加锁)
    2减少锁粒度(例ConcurrentHashMap内部默认有16个HashMap,每次加锁只需对某个HashMap加锁,而不是全局,可以做到真正的并行,缺点是要获取全局锁时,消耗的资源较多。)
    3用读写分离锁代替独占锁(适合读多写少的场景,读读不需要同步)
    4锁分离(例LinkedBlockingQueue中出队和入队在队列的两头,采用两把锁,理论上可以并行出入队)
    5锁粗化(虚拟机将一连串对同一个锁的申请和释放操作合并成对锁的一次请求)

Java虚拟机对锁做的优化
    1偏向锁(如果一个线程获得了锁,锁就进入偏向模式,当此线程再次申请该锁时,无需做任何同步操作。不适合锁竞争激烈的场景)
    2轻量级锁(简单地将对象头部作为指针指向持有锁的线程堆栈地内部,来判断一个线程是否持有锁。获得轻量级锁成功则顺利进入临界区,否则锁请求膨胀为重量级锁)
    3自旋锁(锁膨胀后,为了避免说在操作系统层面真正的挂起,虚拟机会让线程先做几个空循环(自旋),如果在循环过程中得到了锁则进入临界区,否则挂起)
    4锁消除(去除不存在共享资源竞争的锁)

乐观锁和悲观锁
    1锁是一种悲观策略,它假设每一次临界区操作都可能发生冲突。
    2无锁是一种乐观的策略,它假设对资源的访问时没有冲突的,也就不用等待。发生冲突后采取比较交换策略CAS(Compare And Swap)来鉴别是否线程冲突,发生了冲突则会不断重试直到不冲突为止。
    3 CAS相比于锁的优点:没有死锁,没有锁竞争带来的系统开销,没有线程间的频繁调度开销。
    4 CAS算法过程:它包含三个参数CAS(V,E,N), V是要更新的变量,E是预期值(没修改的),N是新值。仅当V==E时才会将V更新为N,当V!=E时表示其他线程对该变量进行了操作,则当前线程什么都不做,CAS操作成功后会返回当前V的真实值。
    5 boolean sun.misc.Unsafe.compareAndSet(Object o,int offset,int expect,int update)。o为给定的对象,offset为指定字段到对象头部的偏移量,expect为期待值,update为新值。
    6 CAS判断对象是否被修改的例外:在获得旧值后和修改为新值前的期间,如果一个对象被连续修改了两次导致对象又改成了旧值,这样线程无法判断对象是否发生了修改,但这种情况一般不会导致重要的错误。


不变模式
核心思想:一个对象一旦被创建它的内部状态将永远不会发生改变。所以,没有一个线程可以修改其内部状态和数据,同时其内部状态也绝不会自行发生改变。
不变模式和只读属性的区别:不变模式比只读模式拥有更强的一致性和不变性,对于只读属性的对象而言,对象本身不能被其他线程修改,但是对象的自身状态却可能自行修改。
不变模式的实现:
1去除setter方法以及所有修改自身属性的方法。
2将所有属性设置为私有,并用final标记,确保其不可修改
3确保没有子类可以重载修改它的行为(class用final修饰)
4有一个可以创建完整对象的构造函数
Java中所有元数据类包装类都是用不变模式实现的(Integer,Long,Double......)String也是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值