第二章,线程安全性
1、每个Java对象都可以用做一个实现同步的锁,称为内置锁。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。
2、Java的内置锁相当于互斥锁,由这个锁保护的同步代码块以原子方式执行。
3、当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。
4、重入的一种实现方式是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减。当计数值为0时,这个锁将被释放。
5、如果使用同步来协调对某个变量的访问,那么在访问这个变量的所有位置上都要使用同步。而且,当使用锁来协调对某个变量的访问时,在访问变量的所有位置上都要使用同一个锁。
6、一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在改对象上不会发生并发访问。
7、只有被多个线程同时访问的可变数据才需要通过锁来保护。
8、对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
9、缩小同步代码块的作用范围;尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去。
第三章,对象的共享
1、同步的另一个重要方面是内存可见性,当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
2、无法确保执行读操作的线程能适时看到其他线程写入的值,为了确保多个线程对内存写入操作的可见性,必须使用同步机制。
3、无法确保线程中的操作将按照程序中指定的顺序执行,称为重排序。
4、对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位,因此在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明他们,或者用锁保护起来。
5、在访问某个共享且可变的变量时要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变量的值对于其他线程来说都是可见的。
6、加锁不仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。
7、volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞。
8、volatile变量的一种典型用法:检查某个状态标记以判断是否退出循环。加锁机制即可确保可见性,又可确保原子性,而volatile变量只能确保可见性。
9、线程封闭是实现线程安全性的最简单方式之一,把对象封闭在一个线程中,就自动实现线程安全性。维持线程封闭性的一种更规范方式是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来,get方法总是返回由当前执行线程在调用set时设置的最新值。
10、当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue方法来获取初始值。你可以将ThreadLocal视为包含了Map<Thread, T>对象,其中保存了特定于该线程的值,但ThreadLocal的实现并非如此。
11、不可变对象一定是线程安全的。除非需要某个域是可变的,否则应将其声明为final域。
12、线程封闭,只读共享(在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它),线程安全共享(线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步),保护对象。
13、可以确保对该对象只能由单个线程访问(线程封闭),或者通过一个锁来保护对该对象的所有访问,实例封闭是构建线程安全类的一个最简单方式。
14、 CopyOnWriteArrayList(写入时复制)是一个线程安全的链表,特别适用于管理监听器列表,它是写时复制的容器。当我们往一个容器添加元素时,不直接往当前容器添加,而是先将当前容器进行拷贝,复制出一个新的容器,然后往新的容器添加元素,再把原容器的引用指向新的容器。用于在遍历操作为主要操作的情况下代替同步的list。
15、并发容器是针对多个线程并发访问设计的。ConcurrentLinkedQueue是一个传统的先进先出队列,PriorityQueue是一个优先队列。阻塞队列,BlockingQueue增加了可阻塞的插入和获取等操作。
16、ConcurrentHashMap使用分段锁来实现更大程度的共享,执行读取操作的线程和执行写入操作的线程可以并发的访问map。
17、Deque是一个双端队列,实现了在队列头和队列尾的高效插入和移除。
18、闭锁可以延迟线程的进度直到其到达终止状态。CountDownLatch的countDown方法递减计数器,表示有一个事件已经发生了,而await方法等待计数器达到零,如果非零,则await会一直阻塞直到计数器为零。闭锁是一次性对象,一旦进入终止状态,就不能被重置。闭锁可以确保某些活动直到其他活动都完成后才继续执行。
FutureTask也可以用做闭锁。
信号量可以控制访问某个资源的操作数量或执行某个操作的数量。可以用来实现某种资源池。acquire表示获取一个许可,release表示归还一个许可。如果没有许可,那么acquire将阻塞直到有许可。
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别是,所有线程必须同时到达栅栏位置,才能继续执行。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。
网友评论