Java并发

1.基本并发方式

1.1 Callable

这个方式基本不用,会影响线程的思想。

2.线程池 Executor

2.1 ThreadExecutor

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize 线程池Idle状态的线程。
  • maximumPoolSize 线程池支持的最大线程数,这个数达到某个值以后,就会导致OOM,可以理解如果有100多个线程,会不会导致内存不足以分配这么多线程。
  • defaultHandler 这个就是丢弃策略。这个不是线程等待的时候丢弃,是线程池里面有超过maximumPoolSize的时候丢弃。

    2.2 CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

    这个作用是什么,就是没有核心线程,有新的runnable进来,就new 一个线程执行。如果空闲线程在60s内,还没有被回收,就达到复用的目的。所以线程是存在复用的可能,但是不会等待。

    2.3 FixedThreadPool

    ExecutorService executorService = Executors.newFixedThreadPool(2);

    这个才是最常用的线程池方式。

    线程调度,在池满以后等待。

2.4 SingleFixedThreadPool

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

有一个核心线程常驻。可以保证执行顺序。FIFO

sleep,interrupt,yield,join,notify,wait,await

3. 同步

3.1synchronized

  • 作用在局部区域上
    • this
    • class.name
  • 作用的实例方法上
  • 作用在静态方法上
    这4种使用方式作用的区域各不相同。

    3.1.1 synchronized this

    这个锁住的是this,也就是当前实例。

    比如有2个方法,一个read,一个write,这个就是要加锁来解决的。

    如果是2个实例,他们相互直接的synchronized是不相干的。

    3.1.2 synchronized static or class.name

    static synchronized 的作用域是所有的静态方法。

    这个3.1.1 和3.1.2的synchronized 的作用范围不同,所以没有相关性。

    static方法的使用,本质上跟class类是没有关系的,只有一个索引用以找到这个方法。所以它sync的不是所有实例对象,sync的就是这个class.name。

3.2 Synchronizd 的本质

  • synchronized this 本质是在执行的过程中,加上了锁。

    锁是由JVM自动取解析的。

    $ javap -c SynchronizedDemo
    警告: 二进制文件SynchronizedDemo包含com.demanmath.androidms.javabase.concurrent.SynchronizedDemo
    Compiled from "SynchronizedDemo.java"
    public class com.demanmath.androidms.javabase.concurrent.SynchronizedDemo {
    public com.demanmath.androidms.javabase.concurrent.SynchronizedDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return
    
    public void testA();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: iconst_0
       5: istore_2
       6: aload_1
       7: monitorexit
       8: goto          16
      11: astore_3
      12: aload_1
      13: monitorexit
      14: aload_3
      15: athrow
      16: return
    Exception table:
       from    to  target type
           4     8    11   any
          11    14    11   any
    
    public static synchronized void testB();
    Code:
       0: iconst_0
       1: istore_0
       2: return
    }

    monitorenter,monitorexit 这2条指令,就是JVM控制同步的方式。

    关于这两条指令的作用,我们直接参考 JVM 规范中描述:

monitorenter :Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

这段话的大概意思为:每个对象有一个监视器锁(Monitor),当 Monitor 被占用时就会处于锁定状态。

线程执行 Monitorenter 指令时尝试获取 Monitor 的所有权,过程如下:

如果 Monitor 的进入数为 0,则该线程进入 Monitor,然后将进入数设置为 1,该线程即为 Monitor 的所有者。

如果线程已经占有该 Monitor,只是重新进入,则进入 Monitor 的进入数加 1。

如果其他线程已经占用了 Monitor,则该线程进入阻塞状态,直到 Monitor 的进入数为 0,再重新尝试获取 Monitor 的所有权。

monitorexit:The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner.

Other threads that are blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:执行 Monitorexit 的线程必须是 Objectref 所对应的 Monitor 的所有者。

指令执行时,Monitor 的进入数减 1,如果减 1 后进入数为 0,那线程退出 Monitor,不再是这个 Monitor 的所有者。

其他被这个 Monitor 阻塞的线程可以尝试去获取这个 Monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出 Synchronized 的实现原理。

  • 在反编译方法以后,会看到方法带有ACC_SYNCHRONIZED 标志位。

    所以方法在运行的时候,获取当前方法的实例对象,进行monitor,锁住。

    因此整个synchronized 作用方式就2种,对象和方法。当然静态时候 是另外2个case,所以一共4个case。