《Java并发编程之美》学习笔记及补充

文章目录

前言

本博客是对这本书的补充。

第一部分 Java并发编程基础篇

第1章 并发编程线程基础

1.1 什么是线程

JVM内存区域(运行时数据区域)

1.2 线程创建与运行

public class Thread implements Runnable {
	/* What will be run. */
    private Runnable target;
	
	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     * 与此线程有关的ThreadLocal值。该映射由ThreadLocal类维护。*/
    ThreadLocal.ThreadLocalMap threadLocals = null;

	/*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     * 与此线程有关的InheritableThreadLocal值。该映射由InheritableThreadLocal类维护
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
	/* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     * 线程的状态,涉及到Thread类的内部枚举类:State(线程的六种状态)
     */
    private volatile int threadStatus = 0;

	/**
     * The argument supplied to the current call to
     * java.util.concurrent.locks.LockSupport.park.
     * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
     * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
     * 提供给LockSupport.park的当前调用的参数。 由(私有)LockSupport.setBlocker设置,使用LockSupport.getBlocker进行访问  
     */
    volatile Object parkBlocker;

	/** The current seed for a ThreadLocalRandom */
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;

	public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
	
	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

	@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

分析代码可知:

  • Thread类实现了Runnable接口
  • Thread类有个属性:Runnable target,默认为null,
  • Thread类有两个主要的构造函数new Thread(Runnable target)、new Thread(),前面的那个会将target参数赋值给target属性
  • Thread类的run()方法:若target不为null,就执行target.run(),否则啥也不做。
    所以创建线程应该是有两种方式:要么使用new Thread(Runnable target):直接传入Runnable对象;要么使用new Thread():定义一个Thread的子类并重写run()方法,改为要执行的任务的具体逻辑,而不再使用target属性。至于书中说的FutureTask方式其实也是使用new Thread(Runnable target):因为FutureTask是Runnable的实现类,所以可以使用new Thread(FutureTask futureTask)。
public interface RunnableFuture<V> extends Runnable, Future<V> {
	/**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

public class FutureTask<V> implements RunnableFuture<V> {
	/** The underlying callable; nulled out after running */
    private Callable<V> callable;
    
	public FutureTask(Callable<V> callable) { this.callable = callable; }
}

1.3 线程通知与等待

为什么wait/notify/notifyAll必须要放在synchronized中

参考为什么wait/notify必须要强制要求放在synchronized中

另外,调用这三个方法之前必须拿要到当前锁对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,而monitor存在于对象头的Mark Word中(存储monitor引用指针),而synchronized关键字可以获取monitor ,所以,notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法中调用。

线程的六种状态

1.4 等待线程执行终止的join方法

1.5 让线程睡眠的sleep方法

1.6 让出CPU执行权的yield方法

1.7 线程中断

1.8 理解线程上下文切换

1.9 线程死锁

1.11 ThreadLocal

强引用、软引用、弱引用、虚引用

参考Java:强引用,软引用,弱引用和虚引用

第2章 并发编程的其他基础知识

2.9 Unsafe类

Unsafe只提供了三种CAS方法,compareAndSwapObject, compareAndSwapInt和compareAndSwapLong。其它方法都是基于这三个方法实现的。而AtomicLong又是对Unsafe的使用的进一步封装。

Unsafe为什么不安全

具体表现就是可以直接操作内存,而不做任何安全校验,如果有问题,则会在运行时抛出 Fatal Error,导致整个虚拟机的退出

Unsafe类与反射去区别

见第3章

第二部分 Java并发编程高级篇

第3章 Java并发包中ThreadLocalRandom类原理剖析

注意:ThreadLocalRandom只是类似与ThreadLocal,但没有使用ThreadLocal来实现线程间数据隔离,而是使用Thread类专门提供的属性threadLocalRandomSeed+使用Unsafe操作这个属性实现的。

ThreadLocalRandom 为什么非要使用 Unsafe 来修改 Thread 对象内的随机种子?

Thread类的threadLocalRandomSeed属性是没有任何访问权限修饰符的,Threa类也没有提供 get/set 方法(为了不违反类的封闭性原则),所以只能在Thread类所在包下的类才能访问Thread的这个属性。而Thread位于java.lang包,ThreadLocalRandom位于java.util.concurrent包,因此ThreadLocalRandom并没有办法访问Thread的threadLocalRandomSeed等变量。

反射是一种可以绕过封装,直接访问对象内部数据的方法,但是,反射的性能不太好,并不适合作为一个高性能的解决方案。

有没有什么办法可以让ThreadLocalRandom访问Thread的内部成员,同时又具有远超于反射的,且无限接近于直接变量访问的方法呢?答案是肯定的,这就是使用Unsafe类。这类类似C的操作方法(直接操作内存),带来了极大的性能提升,更重要的是,由于它避开了字段名,直接使用偏移量,就可以轻松绕过成员的可见性限制了。所以JDK源码中大量使用Unsafe类,而不是反射!

第4章 Java并发包中原子操作类原理剖析

4.1 原子变量操作类

JUC包中有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作类,它们原理类似,下面以AtomicLong为例进行讲解。

4.2 JDK 8新增的原子操作类LongAdder

第6章 Java并发包中锁原理剖析

6.1 LockSupport工具类

直接使用Unsafe还是不太放便,因此lock包提供了一个辅助类LockSupport封装了park和unpark。使用LockSupport要比直接只用Unsafe更加便捷。此外,LockSupport还可以用来给线程设置一个Blocker对象,便于调试和检测线程,其原理是使用Unsafe的putObject方法直接设置Thread对象的parkBlocker属性,并在合适的时候读取这个Blocker对象

LockSupport类源码:

public class LockSupport {
	private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    
	static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
	private LockSupport() {} // Cannot be instantiated.
	
	public static void park() {
        UNSAFE.park(false, 0L);
    }

	public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }
    
	public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

	public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }
    
	private static void setBlocker(Thread t, Object arg) {
	    // Even though volatile, hotspot doesn't need a write barrier here.
	    UNSAFE.putObject(t, parkBlockerOffset, arg);
	}
	
	public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }
	
	public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
}

6.2 抽象同步队列aqs概述

6.2.1 AQS——锁的底层支持

详细分析结点状态waitStatus和public final void acquire(int arg)方法和public final boolean release(int)方法。参考Java并发之AQS详解

分析Node的nextWaiter属性。

	static final class Node {
		/** 指示节点正在共享模式下等待的标记 */
	    static final Node SHARED = new Node();
	    /** 指示节点正在以独占模式等待的标记 */
	    static final Node EXCLUSIVE = null;

		/** waitStatus值,指示线程已取消 */
        static final int CANCELLED =  1;
        /** waitStatus值,指示下一个节点中的线程需要unpark */
        static final int SIGNAL    = -1;
        /** waitStatus值,指示线程正在condition上等待 */
        static final int CONDITION = -2;
        /**  waitStatus值指示下一个acquireShared应无条件传播 */
        static final int PROPAGATE = -3;
		
		volatile int waitStatus;
		
		/**
         * 指向条件队列的下一个节点,或是特殊值:SHARED。因为条件队列只有在保持在独占模式时才被访问,
         * 所以节点在condition上等待时我们只需要一个简单的链接队列来保存它们即可。然后将它们转移到队列中以re-acquire。
         * 并且因为条件只能是独占模式的,我们通过使用特殊的值来保存一个字段来指示共享模式。
         * <p>在当前Node对象作为Condition的等待队列节点使用时,nextWaiter属性保存后继节点。
         * <p>在当前Node对象作为同步队列节点时,nextWaiter属性可能有两个值:{@link #EXCLUSIVE}、{@link #SHARED} 标识当前节点是独占模式还是共享模式,见{@link #Node(Thread, Node)},{@link #isShared()};
         */
        Node nextWaiter;
	}
6.2.2 AQS——条件变量的支持

分析ConditionObject的源码

	/**
     *  如果同步相对于当前(调用)线程排他地保持,则返回{@code true}。每次调用不等待的{@link ConditionObject}方法时,将调用此方法。
     *  (等待方法调用{@link #release}。)。
     * 
     *  <p>默认实现会抛出{@link UnsupportedOperationException}。
     * 此方法仅在{@link ConditionObject}方法内部调用,因此如果不使用条件,则不需要定义。

     */
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }
    
	public class ConditionObject implements Condition, java.io.Serializable {
		/** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

		/**
         * 将等待时间最长的线程(如果存在)从此条件的等待队列移动到拥有锁的等待队列。
         */
        public final void signal() {
        	//若当前线程未拥有锁,就抛异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

		private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

		/**
	     * 将节点从条件队列移到同步队列。如果成功,返回true。
	     */
	    final boolean transferForSignal(Node node) {
	        /*
	         * If cannot change waitStatus, the node has been cancelled.
	         * <p>
	         *  如果无法更改waitStatus,则该节点已取消。
	         * 
	         */
	        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
	            return false;
	
	        /*
	         * Splice onto queue and try to set waitStatus of predecessor to
	         * indicate that thread is (probably) waiting. If cancelled or
	         * attempt to set waitStatus fails, wake up to resync (in which
	         * case the waitStatus can be transiently and harmlessly wrong).
	         * <p>
	         *  接合到队列并尝试设置前导的waitStatus以指示线程(可能)等待。如果取消或尝试设置waitStatus失败,唤醒以重新同步(在这种情况下,waitStatus可能会暂时和无害地错误)。
	         * 
	         */
	        Node p = enq(node);
	        int ws = p.waitStatus;
	        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
	            LockSupport.unpark(node.thread);
	        return true;
	    }
	}
6.2.3 基于AQS实现自定义同步器

6.3 ReentrantLock的原理

6.3.1 类图结构
6.3.2 获取锁

梳理ReentrantLock#lock()的底层调用关系

public class ReentrantLock implements Lock, java.io.Serializable {
	public void lock() {
        sync.lock();
    }

	abstract static class Sync extends AbstractQueuedSynchronizer {
        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();
    }
	
	static final class NonfairSync extends Sync {
        /**
         *  执行锁定。尝试立即插入,在故障时备份到正常获取。
         */
        final void lock() {
            //采用cas将AQS中的state从0变为1
            if (compareAndSetState(0, 1))
            /**
             * 在ReentrantLock语境下,state代表锁被重入的次数,这意味着只有当前锁未被任何线程持有时该动作才会返回成功.
             * 获取锁成功后,将当前线程标记成为当前锁的线程,此时,加锁流程结束
             */
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
		
		final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
}

分析公平锁与非公平锁的tryAcquire()和tryRelease()代码的区别。

6.3.3 释放锁

6.4 读写锁ReentrantReadWriteLock原理

6.4.1 类图结构
6.4.2 写锁的获取与释放
6.4.3 读锁的获取与释放
6.4.4 案例介绍

6.5 JDK 8中新增的StampedLock锁探究

第7章 Java并发包中并发队列原理剖析

7.1 ConcurrentLinkedQueue原理探究

使用单向链表(head:Node[item、next]、tail:Node[item、next])方式实现的无界非阻塞(CAS)队列

7.2 LinkedBlockingQueue原理探究

使用单向链表(head:Node[item、next]、tail:Node[item、next])方式实现的无界(Integer.MAX)阻塞(ReentrantLock)队列。有takeLock(内部有notEmpty条件变量)、putLock(内部有notFull条件变量)

7.3 ArrayBlockingQueue原理探究

使用有界数组(items:Objet[])方式实现的阻塞(ReentrantLock)队列。只有一个独占锁lock用来保证出、入队操作的原子性,这保证了同时只有一个线程可以进行入队、出队操作。

第8章 Java并发包中线程池ThreadPoolExecutor原理探究

8.1 介绍

8.2 类图介绍

8.3 源码分析

补充ThreadPoolExecutor继承的ExecutorService#awaitTermination()。一般是shutdown()后马上调用该方法,调用该方法会阻塞当前线程,直到调用shutdown()后所有任务完成执行,或者发生超时,或者当前线程被中断(以先发生者为准)。

合理配置线程池

8.4 总结

第9章 Java并发包中ScheduledThreadPoolExecutor原理探究

第10章 Java并发包中线程同步器原理剖析

10.1 CountDownLatch原理剖析

调用一个子线程的join()方法后,该线程会一直被阻塞直到子线程运行完毕,而CountDownLatch则使用计数器来允许子线程运行完毕或者在运行中递减计数,也就是CountDownLatch可以在子线程运行的任何时候让await方法返回而不一定必须等到线程结束

书中说"使用ExecutorService时传递的参数是Runable或者Callable对象,这时候你没有办法直接调用这些线程的join()方法,这就需要选择使用CountDownLatch了"。其实也可以选择使用ExecutorService#shutdown()。我的理解是:第一、若要在子线程执行的某一时刻就能唤醒父线程,就只能使用CountDownLatch;第二、若创建ExecutorService时不知道累积要执行多少个子线程任务,就只能用ExecutorService#shutdown()

CountDownLatch是使用AQS实现的。使用AQS的状态变量来存放计数器的值。

第三部分 Java并发编程实践篇

第11章 并发编程实践

11.1 ArrayBlockingQueue的使用

11.3 并发组件ConcurrentHashMap使用注意事项

11.4 SimpleDateFormat是线程不安全的

11.7 创建线程和线程池时需要指定与业务相关的名称

11.8 使用线程池的情况下当程序结束时记得调用shutdown关闭线程池

11.9 线程池使用FutureTask时需要注意的事情

11.10 使用ThreadLocal不当可能会导致内存泄漏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值