多线程 高并发知识点总结

多线程 高并发知识点总结(单机)

  • 关于Thread
    • 如何创建一个线程
    • 线程的基本方法
    • 线程的状态
  • 关于synchronized的实现
  • CAS
  • volatile
  • AtomicXXX
  • Unsafe
  • AQS(AbstractQueuedSynchronizer)
    • ReentrantLock
    • CountDownLatch
    • CyclicBarrier
    • Phaser
    • ReentrantReadWriteLock
    • Semaphore
    • Exchanger
    • LockSupport
  • 强软弱虚引用
    • 1.强引用
    • 2.软引用(SoftReference)
    • 3.弱引用(WeakReference)
    • 4.虚引用(PhantomReference)
  • 什么是ThreadLocal

关于Thread

如何创建一个线程

关于如何创建一个线程

  1. 继承Thread类
  2. 实现Runnable接口 new一个Thread 把实现类丢进去
  3. Executors创建线程池

线程的基本方法

  1. sleep()
    sleep()方法简单来说,就是让线程进入沉睡状态。经常会有人把线程的sleep()方法和Object类的wait()来进行比较。最大的区别就是sleep()不会释放CPU资源,wait()方法会释放CPU资源。在多任务的情况下调用wait()方法后,该任务重新回到任务队列中进行等待。
  2. yield()
    暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。(几乎只有面试用到)
  3. join()
    若是两个线程A,B,在线程A中调用B线程的join()方法,则A会进入阻塞状态,让B先进行执行。等B线程执行完毕,再接着执行A剩下的部分。如果需要两个线程串行执行时可以用到。

线程的状态

在Thread的类中,从jdk1.5开始定义了一个enum来表示线程的六种不同的状态:

  1. NEW–刚创建出来的线程状态
 		/**
         * Thread state for a thread which has not yet started.
         */
  1. RUNNABLE–执行中的线程状态
    注:若调用方法在native底层方法中进行的阻塞,JVM感知不到。该线程仍为RUNNABLE状态
		/**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
  1. BLOCKED–线程正在等待获取锁的状态
		/**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
  1. WAITING–指线程正在等待其他线程发来的通知(notify或LockSupport.unpark)的状态
		/**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */

  1. TIMED_WAITING–一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态
		/**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * 
    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
*/
  1. TERMINATED–结束任务后的线程状态
		/**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */

关于synchronized的实现

在JKD早期synchronized是一把重量级的锁,每次使用都需要从用户态到内核态去申请,因此效率比较差。经过JDK不断的升级,也对synchronized进行了一系列的优化,把重量级的锁变成了一次锁升级的过程。说到synchronized,就不得不先提一下对象的内存布局(可以用Java Object Layout包进行查看)。其中有一项markword,用来记录对象的锁信息。多线程 高并发知识点总结_第1张图片

  • 在对象刚new出来的时候,锁状态正常。
  • 在有一个线程进行调用时,将锁升级成为偏向锁。
  • 当有多个线程调用该对象时,将锁升级为轻量级锁(自旋锁)。
  • 当某个线程自旋超过十次后将锁升级成为重量级的锁。

因此,在某些时候加上synchronized操作不一定会比Lock或者原子类操作慢,视情况而定。
小贴士:尽量不要用synchronized锁基本类型或者String,以防产生不必要的死锁。


CAS

在java底层代码中,存在很多的Compare and Set操作,简称为CAS。
顾名思义Compare and Set的意思就是比较后进行设置,该操作也被称为自旋。将要改的值在修改前进行判断,如果没有被其他的线程所改动过,则进行修改。若被其他线程改动过,则重新进行取值并判断。
执行时间短(加锁代码),线程数少,用自旋。
执行时间长,线程数多,用系统锁。
因为在自旋的时候会持续消耗CPU,而重量级的系统锁会让任务进入等待队列,不产生CPU的消耗。
小贴士:在比对修改前的值后,不允许有线程对该值再进行修改。因此比对后结果为true,再进行赋值这两个操作是不可被打断的。


volatile

volatile作为java中的一个关键字,作用主要是两点:

  • 禁止指令重排序
  • 保证对象或者参数在线程之间的可见性

那么问题来了,什么是指令重排序和线程可见性呢?

指令重排序:CPU为了提高运行效率,在运行的时候可能会把上下两句毫无关联的指令调换位置。在单线程的情况下,指令重排序基本上不会造成什么影响,保证结果的一致性。但是在多线程的情况下就不一定了。例如Object o = new Object();一个简单的new,实际上包含以下三个步骤:

  1. 给Object对象分配堆内存
  2. 调用对象的构造方法
  3. 把o存在的栈针指向堆地址

因为步骤2和步骤3没有任何关联关系,因此在多线程调用一个DCL单例时,可能会返回半初始化状态的对象。因此,需要volatile来防止这种情况的发生。(这种情况发生的概率比较小,不过仍需要考虑进去,如果真的发生,问题排查也较为困难)


线程可见性:若一个对象或者参数,需要被多个线程同时用到且需要同步线程之间对该对象或者参数修改的操作,就需要用到volatile。如果不加volatile,各个线程在操作之前,都会把对象和参数拷贝一份到自己的线程堆里面来进行操作。那么数据是如何同步的呢?在CPU中,包含了三块缓存区域,其中有两块是每个核独占的,还有一块是所有CPU核共享的。通过缓存一致性协议,来保证线程之间数据的同步。
多线程 高并发知识点总结_第2张图片

小贴士:volatile可以禁止指令重排和线程之间的可见性,但是不能保证操作的原子性(long和double除外)。


AtomicXXX

在JDK1.5版本开始,加入了许许多多的原子类来保证线程安全。多线程的情况下,使用原子类来进行操作,不需要加锁来进行操作,因为原子类本身就是线程安全的。原子类的底层都是由CAS+volatile的方式来进行实现的。


Unsafe

在Java中存在一个可以直接操作内存的类Unsafe,该类无法被new出来,只能通过反射来进行获取,不过在开发过程中基本上用不到,该类主要是提供给写VM的人进行使用。CAS的具体实现,就是通过Unsafe类中的各个compareAndSwap方法来操作内存进行实现的。


AQS(AbstractQueuedSynchronizer)

从JDK1.5版本开始,添加AQS接口以及AQS的实现类来进行多线程操作。

ReentrantLock

public class ReentrantLock implements Lock, java.io.Serializable
/** Synchronizer providing all implementation mechanics */
    private final Sync sync;
	/**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer

从源码中可以看到ReentrantLock中实现了Lock接口,存在一个私有的Sync变量。Sync继承了AbstractQueuedSynchronizer接口。

ReentrantLock在使用中大部分情况都可以和synchronized进行替换。但是两者之间的区别还是有不少的,比如:

  1. synchronized不需要手动解锁,ReentrantLock需要手动解锁。
  2. synchronized是非公平锁,ReentrantLock存在一个构造方法public ReentrantLock(boolean fair),传入true则可设置为公平锁。
  3. synchronized只存在一个任务等待队列,而ReentrantLock可以存在多个不同的等待队列。

CountDownLatch

特点:

  1. 减计数方式
  2. 计算为0时释放所有等待的线程
  3. 计数为0时,无法重置
  4. 调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响
  5. 不可重复利用
public class CountDownLatch
	/**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer
    
    private final Sync sync;

CyclicBarrier

特点:

  1. 加计数方式
  2. 计数达到指定值时释放所有等待线程
  3. 计数达到指定值时,计数置为0重新开始
  4. 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
  5. 可重复利用
public class CyclicBarrier
	/** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();

由源码可见CyclicBarrier是基于ReentrantLock类来进行实现的

Phaser

阶段器,可作为一个可复用的同步屏障,类似于CyclicBarrier和CountDownLatch的结合体。

ReentrantReadWriteLock

ReentrantReadWriteLock有读锁和写锁。读锁是共享锁,写锁是独享锁
独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。

	/** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

写锁获取写的权限时

protected final boolean tryAcquire(int acquires) {
	Thread current = Thread.currentThread();
	int c = getState(); // 取到当前锁的个数
	int w = exclusiveCount(c); // 取写锁的个数w
	if (c != 0) { // 如果已经有线程持有了锁(c!=0)
    // (Note: if c != 0 and w == 0 then shared count != 0)
		if (w == 0 || current != getExclusiveOwnerThread()) // 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程不是当前线程就返回失败
			return false;
		if (w + exclusiveCount(acquires) > MAX_COUNT)    // 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
      throw new Error("Maximum lock count exceeded");
		// Reentrant acquire
    setState(c + acquires);
    return true;
  }
  if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。
		return false;
	setExclusiveOwnerThread(current); // 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者
	return true;
}
————————————————
版权声明:本文为CSDN博主「LUK流」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35688140/article/details/100546216

读锁:

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;                                   // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
————————————————
版权声明:本文为CSDN博主「LUK流」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35688140/article/details/100546216

Semaphore

Semaphore可以作为限流的工具来进行使用,参数permits表示运行通过的线程数,fair表示是否为公平锁。

	/**
     * Creates a {@code Semaphore} with the given number of
     * permits and the given fairness setting.
     *
     * @param permits the initial number of permits available.
     *        This value may be negative, in which case releases
     *        must occur before any acquires will be granted.
     * @param fair {@code true} if this semaphore will guarantee
     *        first-in first-out granting of permits under contention,
     *        else {@code false}
     */
    public Semaphore(int permits, boolean fair)

Exchanger

Exchanger可以用作两个线程之间交换数据来使用。
当线程A调用exchanger.exchange()方法后,该线程会阻塞住,直到另一个线程调用exchanger.exchange()后,方法中传递的参数进行互换,且两个线程继续运行。
底层使用Unsafe类的park()unpark()来进行线程阻塞和恢复。

LockSupport

LockSupport类主要是对Unsafe类的park()unpark()方法进行了封装,来控制线程的状态。

	public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

强软弱虚引用

JAVA中的引用分为以下四大类型:

1.强引用

当开发者new出一个对象的时候,这个对象的引用就是强引用。不论如何,强引用都不会被GC回收,当强引对象过多时,虚拟机就会抛出OOM(内存溢出)。
一句话概括便是,宁愿抛错也不回收。

2.软引用(SoftReference)

如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
一句话概括便是,内存不足便回收。

3.弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它,所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
一句话概括便是,只要触发GC便进行回收。

4.虚引用(PhantomReference)

虚引用在实际开发中基本用不上,因为虚引用的值正常是获取不到的。只有写JVM的开发者才会用虚引用和引用队列的方式来管理堆外内存(例如:NIO)。
一句话概括便是,在程序中无法获取虚引用的值。

什么是ThreadLocal

很多人在开发过程中可能用到过ThreadLocal,也知道ThreadLocal的用法是只有自己才能获取得到自己丢进去的值,那么具体是如何实现的呢?让我们先来看看ThreadLocal源码的set方法:

	/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t);//获取线程中的threadLocals属性
        if (map != null)
            map.set(this, value);//不等于空就直接set
        else
            createMap(t, value);//等于空就进行创建
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

再献上一段Thread中的源码:

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

由此可见,每个线程中都有一个叫做threadLocals的Map存在,其实ThreadLocal.set方法就是往每个线程自己的Map中存放东西,所以其他线程当然就获取不到各个线程自己的值。
当然看源码我们还可以知道,threadLocals的Map是弱引用,当线程被GC自然对应threadLocals里的东西也会被回收。

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
}

JUC剩下的部分还有queue队列和线程池。单独放一篇进行总结。

你可能感兴趣的:(多线程 高并发知识点总结)