afredlyj / mynote

idea and note
1 stars 0 forks source link

Java 并发 #13

Open afredlyj opened 8 years ago

afredlyj commented 8 years ago

JAVA并发编程实践读书笔记

线程安全

当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。 可见性、顺序性和原子性是线程安全的特性。

最低限的安全性

out-of-thin-air safety。当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值,但是至少它可以看到某个线程设置的一个真实值,而不是凭空而来的值。除了没有声明为volatile的64位double和long,最低限的安全性应用于所有的变量。

锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。

并不推荐过度依赖volatile变量所提供的可见性,只有满足下面所有的标准后,才能使用volatile变量:

  1. 写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值;
  2. 变量不需要与其他的变量共同参与不变约束;
  3. 而且,访问变量时,没有其他的原因需要加锁。

    发布和逸出

发布(publishing)一个对象的意思是,使它能够被当前范围之外的代码所使用。
一个对象在尚未准备好时就将它发布,这种情况称作逸出(escape)。 创建之后状态不能被修改的对象叫做不可变对象,不可变对象是线程安全的,只有满足如下状态,一个对象才是不可变的:

  1. 它的状态不能在创建之后再被修改;
  2. 所有的域都是final类型,并且,
  3. 它被正确的创建(创建期间没有发生this引用的逸出)。

在不可变对象的内部,同样可以使用可变性对象来管理它们的状态。

synchronized说明

synchronized提供了以下功能:

  1. synchronized关键字提供锁机制,保证共享资源的互斥使用,防止数据竞争;
  2. synchronized关键字防止代码重排序;
  3. synchronized 包含了锁的申请和释放,也就是说,在进入synchronized方法或者代码块前,当前线程需要申请锁,然后进入synchronized方法或者代码块,从main memory 而不是cache中读取数据,业务操作完成之后,将变量写入main memory,释放锁。

我们将synchronized使用的基本规则总结为下面3条:

  1. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
  2. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
  3. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

在Java代码中,不能有synchronized 修饰的变量,否则会编译错误,但是可以有volatile修饰的变量。

线程等待和唤醒

只有获取到对象的监视器后,才能调用wait和notify方法。

A thread becomes the owner of the object's monitor in one of three ways:

  • By executing a synchronized instance method of that object.
  • By executing the body of a synchronized statement that synchronizes on the object.
  • For objects of type Class, by executing a synchronized static method of that class.

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:

     synchronized (obj) {
            while (<condition does not hold>)
                  obj.wait(timeout);
              ... // Perform action appropriate to condition
      } 

双端队列和窃取工作

在窃取工作的设计中,每个消费者都有自己的双端队列,如果一个消费者完成了自己双端队列中的全部工作,它可以偷取其他消费者双端队列末尾的工作。因为工作线程并不会竞争一个共享队列,所以效率比生产者消费者模式要高。

Synchronizer

Synhronizer是一个对象,根据本身的状态调节线程的控制流,阻塞队列可以扮演一个Synchronizer的角色,其他类型的Synchronizer的角色包括信号量(semaphore)、关卡(barrier)以及闭锁(latch)。
所有的Synchronizer都享有类似的结构特性:它们封装状态,而这些状态决定着线程执行到在某一点时是通过还是被迫等待;它们提供操控状态的方法,以及高效地等待Synchronizer进入到期望状态的方法。

总结

介绍的闭锁有CountDownLatch和FutureTask,主要用来完成这样的需求:当满足条件时才去做特定的任务。

Executor的生命周期

happens-before 法则(《深入理解Java虚拟机》P376)

happens-before 规则描述的是变量之间的可见性。

重排序分为编译器重排序和处理器重排序,为了实现volatile内存语义,JMM会分别限制这两种类型的重排序。

对于final域,编译器和处理器要遵守两个重排序规则:

线程组中断是一个协作机制,一个线程给另外一个线程发送信号(signal),通知它在方便或者可能的情况下停止正在做的工作,去做其他的事情。 中断通常是实现取消最明智的

afredlyj commented 8 years ago

线程基础

线程状态

在Java中,线程可以分为如下几种状态:NEW, RUNNABLE, BLOCKED, WAITING, TIME_WAITING, TERMINATED.

线程方法

afredlyj commented 8 years ago

HashMap 死循环

当HashMap用在并发环境下时,有一定几率导致死循环,从而导致CPU 100%。详细分析可以参考这里,大致就是多个线程同时resize,形成环形链表,如果此时有get操作,并hash到这个环形链表,就会进入死循环。

说到底,这是错误使用JDK导致的,因为HashMap并不能用于多线程环境。

afredlyj commented 8 years ago

死锁

产生死锁的四个必要条件:

afredlyj commented 8 years ago

技术文档

  1. ExecutorService优化
  2. ForkJoin框架
  3. 官方ForkJoin入门文档
  4. Treiber Stack
afredlyj commented 8 years ago

AtomicBoolean VS volatile boolean

use volatile fields when said field is ONLY UPDATED by its owner thread and the value is only read by other threads, you can think of it as a publish/subscribe scenario where there are many observers but only one publisher. However if those observers must perform some logic based on the value of the field and then push back a new value then I go with Atomic* vars or locks or synchronized blocks, whatever suits me best. In many concurrent scenarios it boils down to get the value, compare it with another one and update if necessary, hence the compareAndSet and getAndSet methods present in the Atomic* classes.

Check the JavaDocs of the java.util.concurrent.atomic package for a list of Atomic classes and an excellent explanation of how they work (just learned that they are lock-free, so they have an advantage over locks or synchronized blocks)

出处:Volatile boolean vs AtomicBoolean

但是不知道为啥题主选择的另外一个答案=_=...

afredlyj commented 7 years ago

AtomicIntegerFieldUpdater 类

该类用于原子性修改对象的int成员变量,AtomicLongFieldUpdater也是类似。该类是抽象类,使用时通过newUpdater返回一个内部实现类AtomicIntegerFieldUpdaterImpl,在初始化时,会使用反射确认调用类的访问权限,确定访问权限之后,继续判断类型是否是int.class,是否有volatile标识,最后获取目标域的偏移量,为后面的CAS操作做准备。

AtomicIntegerFieldUpdaterImpl(Class<T> tclass, String fieldName, Class<?> caller) {
            Field field = null;
            int modifiers = 0;
            try {
                field = tclass.getDeclaredField(fieldName);
                modifiers = field.getModifiers();
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            Class fieldt = field.getType();
            if (fieldt != int.class)
                throw new IllegalArgumentException("Must be integer type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            this.cclass = (Modifier.isProtected(modifiers) &&
                           caller != tclass) ? caller : null;
            this.tclass = tclass;
            offset = unsafe.objectFieldOffset(field);
        }

java doc的说明比较明了:

/**
 * A reflection-based utility that enables atomic updates to
 * designated {@code volatile} reference fields of designated
 * classes.  This class is designed for use in atomic data structures
 * in which several reference fields of the same node are
 * independently subject to atomic updates. For example, a tree node
 * might be declared as
 *
 *  <pre> {@code
 * class Node {
 *   private volatile Node left, right;
 *
 *   private static final AtomicReferenceFieldUpdater<Node, Node> leftUpdater =
 *     AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
 *   private static AtomicReferenceFieldUpdater<Node, Node> rightUpdater =
 *     AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");
 *
 *   Node getLeft() { return left;  }
 *   boolean compareAndSetLeft(Node expect, Node update) {
 *     return leftUpdater.compareAndSet(this, expect, update);
 *   }
 *   // ... and so on
 * }}</pre>
 *
 * <p>Note that the guarantees of the {@code compareAndSet}
 * method in this class are weaker than in other atomic classes.
 * Because this class cannot ensure that all uses of the field
 * are appropriate for purposes of atomic access, it can
 * guarantee atomicity only with respect to other invocations of
 * {@code compareAndSet} and {@code set} on the same updater.

AtomicIntegerFieldUpdater的使用场景是当一个原子性的类,有多个相互独立的成员变量需要变更时。 另外,AtomicIntegerFieldUpdater只能保证当前updater范围内的原子性,当多个updater实例操作同一个对象时并不能保证(这是显然的,自己管自己能力范围之内的事儿)。而AtomicReference将对象作为自己的成员变量value,更容易控制。

补充

AtomicXXX原子操作类相比于volatile,个人理解多了一个compareAndSet操作。

参考

When to use AtomicReference in Java? AtomicReferenceFieldUpdater - methods set, get, compareAndSet semantics