关于如何创建一个线程
在Thread的类中,从jdk1.5开始定义了一个enum来表示线程的六种不同的状态:
/**
* Thread state for a thread which has not yet started.
*/
/**
* 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.
*/
/**
* 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}.
*/
/**
* 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.
*/
/**
* 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}
*
*/
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
在JKD早期synchronized是一把重量级的锁,每次使用都需要从用户态到内核态去申请,因此效率比较差。经过JDK不断的升级,也对synchronized进行了一系列的优化,把重量级的锁变成了一次锁升级的过程。说到synchronized,就不得不先提一下对象的内存布局(可以用Java Object Layout包进行查看)。其中有一项markword,用来记录对象的锁信息。
因此,在某些时候加上synchronized操作不一定会比Lock或者原子类操作慢,视情况而定。
小贴士:尽量不要用synchronized锁基本类型或者String,以防产生不必要的死锁。
在java底层代码中,存在很多的Compare and Set操作,简称为CAS。
顾名思义Compare and Set的意思就是比较后进行设置,该操作也被称为自旋。将要改的值在修改前进行判断,如果没有被其他的线程所改动过,则进行修改。若被其他线程改动过,则重新进行取值并判断。
执行时间短(加锁代码),线程数少,用自旋。
执行时间长,线程数多,用系统锁。
因为在自旋的时候会持续消耗CPU,而重量级的系统锁会让任务进入等待队列,不产生CPU的消耗。
小贴士:在比对修改前的值后,不允许有线程对该值再进行修改。因此比对后结果为true,再进行赋值这两个操作是不可被打断的。
volatile作为java中的一个关键字,作用主要是两点:
那么问题来了,什么是指令重排序和线程可见性呢?
指令重排序:CPU为了提高运行效率,在运行的时候可能会把上下两句毫无关联的指令调换位置。在单线程的情况下,指令重排序基本上不会造成什么影响,保证结果的一致性。但是在多线程的情况下就不一定了。例如
Object o = new Object();
一个简单的new,实际上包含以下三个步骤:
- 给Object对象分配堆内存
- 调用对象的构造方法
- 把o存在的栈针指向堆地址
因为步骤2和步骤3没有任何关联关系,因此在多线程调用一个DCL单例时,可能会返回半初始化状态的对象。因此,需要volatile来防止这种情况的发生。(这种情况发生的概率比较小,不过仍需要考虑进去,如果真的发生,问题排查也较为困难)
线程可见性:若一个对象或者参数,需要被多个线程同时用到且需要同步线程之间对该对象或者参数修改的操作,就需要用到volatile。如果不加volatile,各个线程在操作之前,都会把对象和参数拷贝一份到自己的线程堆里面来进行操作。那么数据是如何同步的呢?在CPU中,包含了三块缓存区域,其中有两块是每个核独占的,还有一块是所有CPU核共享的。通过缓存一致性协议,来保证线程之间数据的同步。
:
小贴士:volatile可以禁止指令重排和线程之间的可见性,但是不能保证操作的原子性(long和double除外)。
在JDK1.5版本开始,加入了许许多多的原子类来保证线程安全。多线程的情况下,使用原子类来进行操作,不需要加锁来进行操作,因为原子类本身就是线程安全的。原子类的底层都是由CAS+volatile的方式来进行实现的。
在Java中存在一个可以直接操作内存的类Unsafe,该类无法被new出来,只能通过反射来进行获取,不过在开发过程中基本上用不到,该类主要是提供给写VM的人进行使用。CAS的具体实现,就是通过Unsafe类中的各个compareAndSwap方法来操作内存进行实现的。
从JDK1.5版本开始,添加AQS接口以及AQS的实现类来进行多线程操作。
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进行替换。但是两者之间的区别还是有不少的,比如:
public ReentrantLock(boolean fair)
,传入true则可设置为公平锁。特点:
public class CountDownLatch
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer
private final Sync sync;
特点:
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类来进行实现的
阶段器,可作为一个可复用的同步屏障,类似于CyclicBarrier和CountDownLatch的结合体。
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可以作为限流的工具来进行使用,参数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可以用作两个线程之间交换数据来使用。
当线程A调用exchanger.exchange()
方法后,该线程会阻塞住,直到另一个线程调用exchanger.exchange()
后,方法中传递的参数进行互换,且两个线程继续运行。
底层使用Unsafe类的park()
和unpark()
来进行线程阻塞和恢复。
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中的引用分为以下四大类型:
当开发者new出一个对象的时候,这个对象的引用就是强引用。不论如何,强引用都不会被GC回收,当强引对象过多时,虚拟机就会抛出OOM(内存溢出)。
一句话概括便是,宁愿抛错也不回收。
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
一句话概括便是,内存不足便回收。
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它,所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
一句话概括便是,只要触发GC便进行回收。
虚引用在实际开发中基本用不上,因为虚引用的值正常是获取不到的。只有写JVM的开发者才会用虚引用和引用队列的方式来管理堆外内存(例如:NIO)。
一句话概括便是,在程序中无法获取虚引用的值。
很多人在开发过程中可能用到过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队列和线程池。单独放一篇进行总结。