线程

线程与进程本质的区别在于每个进程拥有自己的一整套变量, 而线程之间可以有共享变量。另外创建、销毁一个线程的代价比启动新进程的代价要小。

在java中,没有可以强制线程终止的方法,然而, interrupt 方法可以用来请求终止线程。 当对一个线程调用 interrupt 方法时,线程的中断状态将被置位。这是每一个线程都具有的 boolean 标志。每个线程都应该不时地检査这个标志, 以判断线程是否被中断。但是, 如果线程被阻塞, 对阻塞线程调用interrupt 方法时,将会产生异常。

while (!Thread.currentThread().islnterrupted())//判断中断状态是否被置位,被中断的线程可以决定如何响应中断

Thread.currentThread().getName()//获得当前线程名,可以通过自定义代码模板->tgetn

t.setDaemon(true);//将线程转换为守护线程

线程安全

不可变对象(final)可以通过任意方式发布。在没有额外同步的情况下,任何线程都可以安全的使用被安全发布的事实不可变对象(非final的不可变对象)。对于可变对象,不仅在发布时需要使用同步,而且在每次访问对象的时候同样需要使用同步确保可见性。

守护线程(Daemon)

Thread.setDaemon(true)

可以将线程设置为Daemon线程,其主要用于程序后台的调度和支持性工作,例如垃圾回收线程,仅当所有用户线程结束后才会终止。当java虚拟机中所有线程都是Deamon线程时,java虚拟机将会退出,所以,在Java虚拟机退出时Daemon线程中的finally块并不一定会执行

锁的释放和获取

当线程释放锁时,JMM会把该线程对应的本工作内存中的共享变量刷新到主内存中, 当线程获取锁时,JMM会重新在主内存中拉取共享变量的值

监视器

一个监视区域前后各有一个区域:入口区域【同步队列、线程状态为阻塞BLOCKED】,等待区域【等待队列、线程状态为等待、计时等待Waiting】:如果监视区域有线程,那么入口区域需要等待,否则可以进入;监视区域内执行的线程可以通过命令进入等待队列,也可以将等待队列的线程唤醒,唤醒后的线程就相当于是入口区域的队列一样,可以等待进入监控区域。

同步代码块本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视。

notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED

六种线程状态

New (新创建)、Runnable (可运行)、Blocked (被阻塞)、Waiting (等待)、Timed waiting (计时等待)、Terminated (终止状态)

当使用new操作符创建一个新线程时,此时线程的状态是new;一旦调用 start 方法,线程处于 runnable 状态,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间

线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。

线程执行完run方法后将会进入终止状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

线程实现方式

1.通过实现runnable接口,并把接口实现类当作参数传入Thread即可,必须调用start方法开启

//lambda形式实例化Runnable()函数接口
new Thread( () -> System.out.println("In Java8, Lambda expression") ).start(); //匿名内部类的方式实现Runnable接口
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
}, "ThreadName");
thread.start();
//当线程srart后,线程进入就绪状态,等待CPU底层调用

2.继承Thread类,重写Runnable()接口中的run函数即可,必须调用start方法开启

class MyThread extends Thread{
@Override
public void run() {
super.run();
}
}

3.实现Callable<>接口 与runable接口的区别是,重写方法是call,并且具有一个返回值

FutureTask futureTask = new FutureTask(()->{
return null;
});
new Thread(futureTask, "Thread").start();

Synchornized

对于普通同步方法,锁是当前实例对象。对于静态同步方法,锁是当前类的Class对象。对于同步方法块,锁是Synchonized括号里配置的对象

对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。

每一个 Java 对象都可以用作实现同步的锁,线程在进入同步代码块之前会自动的获取锁,然后在推出代码块之后释放锁。Synchornized代码块中的代码每次只能由一个线程执行,因此,代码块具有原子性(以一种不可分割的方式执行)。

synchronized(obj){
while(conditions){
obj.wait()
}}

如果一个线程获取了对应的锁,并且重新请求进入Synchornized中的代码块中,请求会成功,因而Synchornized是可重入的。

等待&通知

是指一个线程A调用了对象O【锁对象】的wait()方法进入等待状态【等待状态释放锁,进入等待状态,其他线程可以获取对应的监控器】,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

使用wait()、notify()和notifyAll()时需要先对调用对象加锁; notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回;notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED

等待方获取对象锁,如果不满足调用wait方法,被通知重新获得锁后仍要检查这一条件。通知方获得对象的锁,改变条件,通知所有等待队列上的线程,释放锁。

Synchornized与lock的区别

Synchornized是一个系统内置关键字,而ReentrantLock是一个类

synchornized无法判断锁的状态,通过object中的wait方法使得线程被阻塞并释放锁,可以通过notify唤醒,非公平的,而lock锁需要手动释放锁,不一定会一直等待下去。

可以利用lock中的condition.signal()和condition.await()实现线程的唤醒

ReentrantLock reentrantLock = new ReentrantLock();
Condition c = reentrantLock.newCondition();
c.signal();//精准的唤醒被c.await()阻塞的线程

内存屏障

内存屏障用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。

final内存语义

final变量只能被赋值一次【作为对象的字段必须要初始化,但是初始化可以放到构造函数里,写final域的重排序规则禁止把final域的写重排序到构造函数之外,确保了其他线程看到这个对象后,观测的值是初始化后的值】

Volatile

volatile告知程序任何对该变量的访问均需要从共享内存中获取(而非工作内存),而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

volatile通过Lock前缀指令使得处理器将修改缓存回写到内存;一个处理器的缓存回写到内存会导致其他处理器的缓存无效,通过ESMI协议,其他线程知道缓存行失效,必须从主存中从新拉取。

禁止指令重排序,但是并不能解决由于操作不具备原子性带来的线程不安全问题,原子性可以通过加锁(实现原理:只允许一个线程调用,其他线程阻塞)或者调用Atomic类实现(CAS无锁算法)

系统底层如何保证执行有序性

1.锁总线

2.内存屏障,内存屏障两边的语句不允许重排序,这即为Voliate底层实现指令有序性的原理

可重入锁&ReentrantLock

ReentrantLock(boolean fair)支持重进入,可以用于构建一个带有公平策略的锁。策略是也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的,先到先得FIFO。

myLock.lock(); // a ReentrantLock object
try{
//尽量在try之前上锁,把业务放在Try中,在finally中释放锁,保证锁一定会被释放。
}
finally{
myLock.unlock()
}

成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放同步状态时减少同步状态值

AbstractQueuedSynchronizer

AQS提供了实现锁的基本框架:是ReentrantLock的核心

读写锁&ReentrantReadWriteLock

读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞;一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Lock r = rwl.readLock();
Lock w = rwl.writeLock();
lock->try->finally->unlock();

通过读写锁可以实现HashMap线程安全地的读写

public static final Object get(String key) {
r.lock();
try {
return HashMap.get(key);
} finally {
r.unlock();
}
} public static final Object put(String key, Object value) {
w.lock();
try {
return HashMap.put(key, value);
} finally {
w.unlock();
}
}

写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态

LockSupport工具

Condition接口

每个Condition对象都包含着一个等待队列;在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的

Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); condition.await();
condition.signal();//Object.wait()、Object.notify()

await() 阻塞线程,放弃锁,使得线程进入等待状态;当锁可用时,它仍然处于等待状态,直到另一个线程调用同一条件上的 signalAll 方法时为止。但是调用 signalAll 不会立即激活一个等待线程,它仅仅使得这些线程可以在当前线程退出同步方法之后, 通过竞争实现对对象的访问。 另一个方法 signal, 则是随机解除等待集中某个线程的阻塞状态,如果发现自己仍然不能运行,将再次进入等待队列,若没有线程调用signal则会产生死锁。

private Condition condition = reentrantLock.newCondition();

condition.signal();

while (!(ok to proceed))
condition.await();//对 await 的调用应该在如下形式的循环体中,线程被激活的时候需要确保条件已经满足,防止虚假唤醒:不可以使用if语句,只能使用while

进程间通信

管道【主要用于线程间通信】、FIFO、消息列队、信号量、共享内存、Socket

信号量本质上是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据

PIPE和FIFO用来实现进程间相互发送非常短小的、频率很高的消息,共享内存用来实现进程间共享的、非常庞大的、读写操作频率很高的数据;在多进程、多线程、多模块所构成的分布式系统开发中,socket是跨主机进程间通信的第一选择。

线程间通信:Wait&notify【Object中方法】

管道、共享内存【volitate保证数据变化及时刷回共享内存,并从共享内存中获取数据】、wait(),使得当前线程进入等待状态,放开手里的锁;sleep(),使得当前进程进入休眠状态,仍然持有对象锁。

使用 ReentrantLock 结合 Condition

使用JUC工具类 CountDownLatch

全局变量:进程中的线程间内存共享,这是比较常用的通信方式和交互方式。

套接字

线程池

CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*cpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。

可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用

RejectedExecutionHandler.rejectedExecution()方法

ExecutorService es= Executors.newFixedThreadPool();//ExecutorService线程池接口,Executors线程池工厂
es.execute(Runable command);
es.shutdown(); ExecutorService es=Executors.newWorkStealingPool()
es.execute(Runable command);
es.shotdown()

ThreadPoolExecutor

我们可以通过ThreadPoolExecutor来创建一个线程池【用这个方法创建线程池比较合适】;

corePoolSize(线程池的基本大小)、 maximumPoolSize(线程池最大数量)、

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) ExecutorService ThreadPool = new ThreadPoolExecutor(2,5,2L, TimeUnit.SECONDS, new LinkedBlockingDeque(3), new ThreadPoolExecutor.AbortPolicy());

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法:submit方法可以用于提交实现了Runable接口或者Callable接口的实例对象,线程池返回Future类型的对象;而execute只能用于提交实现了Runable的任务,如果遇到异常则直接抛出。

ThreadPool.execute(()->{
System.out.println();
});

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

线程池的拒绝策略

若线程池中的线程数被用完且阻塞队列已排满,线程池将执行拒绝策略:

AbortPolicy 直接抛出异常,阻止线程正常运行

CallerRunsPolicy 的拒绝策略为:如果该线程未关闭,则执行该线程任务

DiscardOldestPolicy 的拒绝策略为:移除线程队列中最早的一个线程任务,并尝试提交当前任务

DiscardPolicy 的拒绝策略为:丢弃当前的线程任务而不做任何处理

ConcurrentHashMap&锁分段

HashTable使用Synchronized方法保证对数据访问的串行化,所有访问HashTable的元素都必须要竞争同一把锁。ConcurrentHashMap容器中包含多个锁,每一把锁用于容器中的一部分数据,当多线程访问容器中不同数据段的数据时,线程间就不存在竞争。

1.8以前ConcurrentHashMap利用Segment数组实现锁分段,1.8开始不再使用Segment数组,采用Node + Synchronized+CAS的方式保证线程的安全性;

第一次初始化时,ConcurrentHashMap将长度初始化为16,在同一个位置的个数又达到了8个以上,如果数组的长度还小于64的时候,则会扩容数组。如果数组的长度大于等于64了的话,在会将该节点的链表转换成树【小于6则会退化成链表】。

ConcurrentHashMap第一次放入元素时开始初始化,检查table是否为空,为空开始初始化,然后计算hash值,找到对应桶的索引。桶没有初始化则通过CAS初始化桶,如果已经初始化,则进入同步代码块,锁对象是对应的桶【相比Segment降低了锁的粒度】;

ConcurrentHashMap的读操作并没有加锁,通过Volitate保证数据的可见性;

乐观锁&CAS&自旋锁

某些共享数据的锁定只会持续很短的一个时间,为了优化阻塞对性能的影响,自旋锁让需要阻塞的线程先不阻塞,当无法获取锁时自旋再次尝试,看看是否能获取锁,这种技术即为自旋锁。

优点是减小了上下文的切换消耗,缺点是占用CPU资源,一般的优化方式为,设计最大自旋次数,避免CPU过度消耗,增加版本技术器,避免ABA问题

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。例如Synchornizied、数据库中的行锁、表锁都是属于悲观锁。

锁消除&锁粗化

在编译器运行时,检测到一些同步代码块中,如果不存在数据竞争的可能性,会进行锁消除。当一串连续的操作都针对同一个对象进行反复加锁和解锁,虚拟机检测到这种情况会对加锁同步的范围扩大到整个操作的外部【粗化】

Synchornized锁升级

锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级

偏向锁【消除无竞争情况下的同步代码】:偏向锁通过CAS操作登记线程ID,登记成功获得偏向锁,登记不成功升级为轻量级锁。如果通过比较发现线程ID一致,直接进入同步代码块。如果发现竞争,膨胀为轻量级锁;无竞争的情况下,偏向锁是不会释放的。

-XX:+UseBiasedLocking

轻量级锁【获取和释放都需要通过CAS】:本意是在较少线程竞争的条件下,减少重量级锁带来的性能消耗;当发现无法获取当前对象的锁,会尝试不断自旋,在老版本自旋10次或等待线程过多将锁膨胀为重量级锁,并把自己放在阻塞队列中。当释放锁时,会使用原子的CAS操作将Displaced Mark Word替换为对象头,完成锁的释放,如果失败了说明锁已经膨胀为重量级锁了。

重量级锁【具有阻塞队列和等待队列】:由jvm管理阻塞队列和等待队列,一旦锁升级为重量级锁,就不会再恢复成轻量级锁状态。

原子操作

原子操作的定义为不可被中断的一个或一系列操作;CPU可以通过对缓存加锁或者总线加锁【开销大】的方式实现多处理器的原子操作。当锁住总线的时候,其他CPU无法通过总线操作内存数据。

JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的

同步容器Vector&HashTable

同步容器将对容器中所有状态访问都串形化,但是代价是严重降低了程序的并发性。Vector&HashTable通过将大量方法在同一个锁上进行同步实现状态访问的串行化。

并发容器CopyOnWriteArrayList&ConcurrentHashMap

CopyOnWriteArrayList是juc包下的一个多线程解决方案:其基于写入时复制的原理实现线程安全性,在每次修改时,都会创建并重新发布一个新的容器副本。CopyOnWriteArrayList在迭代期间不需要对容器进行加锁和复制,可以用于在遍历操作为主要操作的情况下代替同步的List。

ConcurrentHashMap利用锁分段实现了一种更细粒度的加锁机制。

线程中断

Java中没有提供任何方法安全的终止线程,但java提供了线程中断机制,线程中Interrupt方法能够设置目标线程的中断状态,在线程中通过isInterrupt返回目标线程的中断状态。

ArrayList&linkedList

LinkedList底层双向链表,而ArrayList底层是数组,初始大小是10,扩容为原来的1.5倍

List<String> list = Collections.synchronizedList(new ArrayList<>());

CopyOnWriteArrayList是juc包下的一个多线程解决方案:

底层是数组,当往列表中添加新元素时,先把使用Arrays.copyof复制一份,单独修改,在替换原先的数组。

HashMap&HashSet&LinkHashMap

HashMap&HashSet都是线程不安全的,HashSet底层是HashMap<E,Object>,add调用的是HashMap的put函数

HashMap底层是数组+链表和红黑树,数组初始大小是16,扩容系数默认0.75【避免hash碰撞和有效利用空间,权衡而来的结果】,扩容为原来的两倍,在linkedHashMap中底层额外维护了一个双向链表,保证输入输出的有序性

如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap,而Hashtable虽然也是线程安全的,但是和Vector一样,并发性不高

解决方案:
Collections.synchronizedMap(new HashMap<>());
Collections.synchronizedSet(new HashSet<>());
Collections.synchronizedList(new ArrayList<>());

数据结构用法与线程安全

ArrayList&CopyOnWriteArrayList&LinkeList&Vector//函数:set、get、add、remove、size

HashMap&LinkedHashMap&HashTable&TreeMap&ConcurrentHashMap//函数:put、size、containsKey、remove

HashSet&CopyOnWriteArraySet&TreeSet&LinkedHashSet//函数:add、remove、contains

String、 StringBuffer、 StringBuilder

String:创建字符串常量池,对字符串拼接其实是直接在字符串或堆内存里新建一个String对象

StringBuffer:动态改变底层value数组的值和大小,同时对类中方法加上Synchronize锁,保证线程安全

StringBuilder:线程不安全,字符串需要反复修改的场景下,由于String内容不可更改,为避免反复创建String对象,使用StringBuilder避免系统资源浪费

ThreadLocal

ThreadLocal即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。

其用于使得不同线程之间的数据互不干扰。问题是,一般都是使用线程池创建线程,也就是说,线程池中的线程不会死亡,如果不及时释放对应的内存,可能造成内存泄漏;每个ThreadLocal只能存储一个值,存储后,只有指定的线程能够释放键值对

ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("hello");
stringThreadLocal.get();

优点:不需要线程锁阻塞其他线程,避免了线程的等待

问题:

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap.

Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key.

每个key都弱引用指向threadlocal.

所以当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal就可以顺利被gc回收;假如每个key都强引用指向threadlocal,也就是上图虚线那里是个强引用,那么这个threadlocal就会因为和entry存在强引用无法被回收;但是value仍然可能纯在内存泄漏的情况,所以需要及时调用ThreadLocal的Remove方法清除Value

死锁

当程序挂起时, 键入 CTRL+\ , 将得到一个所有线程的列表

产生的条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。【wait方法不会】

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免一个线程同时获取多个锁,避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

线程优先级

线程优先级最低1,最高10,main线程默认5,优先级高的线程更容易抢到时间片

Thread.currentThread().setPriority(1);//设置线程优先级

Thread.yield();//线程让位,返回就绪状态

Thread t = new Thread();
t.start();
t.join();//线程合并,当前线程阻塞,直到t执行结束

ESMI缓存一致性协议

频繁使用的内存会缓存在CPU的高速缓存里。CPU中L1、L2、L3缓存需要保证内容的一致性;

在MESI协议中,每个Cache line有4个状态,可用2个bit表示,它们分别是:

状态 描述 监听任务
M 修改 (Modified) 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。
E 独享、互斥 (Exclusive) 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。 缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。
S 共享 (Shared) 该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
I 无效 (Invalid) 该Cache line无效。

CountDownLatch:数到0的时候开始

CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能;当一个或多个线程调用await方法时,这些线程会被阻塞,当其他线程调用countdown方法时,会将计数器减一,当计数器的值变为0时,因为await方法进入等待状态的线程会被唤醒

public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减1
final CountDownLatch latch = new CountDownLatch(1);

new Thread(){
public void run() {
Thread.sleep(3000);
latch.countDown();
}.start() new Thread(){
public void run() {
latch.await()//利用它可以实现类似计数器的功能。它要等待其他1个任务执行完毕之后才能执行
}

阻塞队列&BlockingQueue

当队列是满的,从队列中添加元素的操作会被堵塞,直到其他线程移除其中的某个元素。

当队列是空的,从队列中获取元素的操作会被堵塞,直到新元素到来。

ArrayBlockingQueue&LinkedBlockingDeque

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,默认情况下不保证线程公平的访问队列,底层的实现原理是可重入锁;

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素

DelayQueue

DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素

CyclicBarrier:加法计数器

通过它可以实现让一组线程等待至某个状态之后再全部同时执行;await()用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;

 CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
//只有当6个线程都执行到await之后,才会开启新线程执行该run方法,然后执行6个线程await之后的语句,而main线程是不受影响的
}); for (int i = 0; i < 6; i++) {
int temp = i;
new Thread(()->{
try {
System.out.println(temp);
cyclicBarrier.await();
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"ThreadName").start();

Semaphore:信号量

Semaphore同样位于JUC下,可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

Semaphore.acquire() //用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。

Semaphore.release() //用于释放许可证,但在释放之前必须拥有许可

阻塞队列

向队列添加元素而队列已满, 或是想从队列移出元素而队列为空的时候, 阻塞队列( blocking queue ) 导致线程阻塞。 如果将队列当

作线程管理工具来使用, 建议使用 put 和 take 方法。

处理方式 抛出异常 不抛出异常 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查队首元素 element() peek() 不可用 不可用

ArrayBlockingQueue: 在构造时需要指定容量 ,是有边界的阻塞队列。长度大小初始化时制定,即内部由数组实现。

LinkedBlockingQueue: 没有边界的阻塞队列。也可以选择指定最大容量

PriorityBlockQueue: 带有优先级的阻塞队列。没有边界。元素按照优先级被取出。

线程安全与数据结构JAVA的更多相关文章

  1. 线程并发线程安全介绍及java.util.concurrent包下类介绍

    线程Thread,在Java开发中多线程是必不可少的,但是真正能用好的并不多! 首先开启一个线程三种方式 ①new Thread(Runnable).start() ②thread.start(); ...

  2. 线程同步(基于java)

    java线程 同步与异步 线程池 1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成 ...

  3. 【Android】线程池原理及Java简单实现

    线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为: T1 创建线程时间 T2 在线程中 ...

  4. Java多线程--线程及相关的Java API

    Java多线程--线程及相关的Java API 线程与进程 进程是线程的容器,程序是指令.数据的组织形式,进程是程序的实体. 一个进程中可以容纳若干个线程,线程是轻量级的进程,是程序执行的最小单位.我 ...

  5. 纯数据结构Java实现(5/11)(Set&Map)

    纯数据结构Java实现(5/11)(Set&Map) Set 和 Map 都是抽象或者高级数据结构,至于底层是采用树还是散列则根据需要而定. 可以细想一下 TreeMap/HashMap, T ...

  6. 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

    摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...

  7. 数据结构Java实现05----栈:顺序栈和链式堆栈

    一.堆栈的基本概念: 堆栈(也简称作栈)是一种特殊的线性表,堆栈的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置进行插入和删除操作,而堆栈只允许在固定一端进行插入和删除 ...

  8. Java数据结构: java.util.BitSet源码学习

    接着上一篇Blog:一道面试题与Java位操作 和 BitSet 库的使用,分析下Java源码中BitSet类的源码. 位图(Bitmap),即位(Bit)的集合,是一种常用的数据结构,可用于记录大量 ...

  9. 数据结构Java实现03----栈:顺序栈和链式堆栈

    一.堆栈的基本概念: 堆栈(也简称作栈)是一种特殊的线性表,堆栈的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置进行插入和删除操作,而堆栈只允许在固定一端进行插入和删除 ...

  10. 数据结构(java)

    数据结构1.什么是数据结构?数据结构有哪些? 数据结构是指数据在内存中存放的机制. 不同的数据结构在数据的查询,增删该的情况下性能是不一样的. 数据结构是可以模拟业务场景. 常见的数据结构有:栈,队列 ...

随机推荐

  1. vvvvvvue

    <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="" ...

  2. unity默认管线lightmap

    lightmap采样 https://blog.csdn.net/wodownload2/article/details/94431040

  3. c语言学习---void 数据类型

    这样的语法是错误的: void a = 10;  void表示无类型, 这样定义一个变量a, 编译器是无法知道给a分配多大的内存空间的 #include<stdio.h> #include ...

  4. [AGC043B] 123 Triangle

    个人思路: 首先,经过 \(1\) 轮就没有 \(3\) 了. 先考虑能否递推前 \(i\) 个数的答案,发现不行. 再考虑能否推出 \(i\) 个数的答案的计算公式,也发现不行. 然后就不会了. 正 ...

  5. 杭电oj 素数判定

    Problem Description 对于表达式n^2+n+41,当n在(x,y)范围内取整数值时(包括x,y)(-39<=x<y<=50),判定该表达式的值是否都为素数.   I ...

  6. Safari浏览器如何收藏网页?

    Safari浏览器是MacOS所自带的一款功能强劲的浏览器,许多MacOS的用户在使用过Safari浏览器后就不会去下载其他浏览器了.对于很多Mac新手用户来说,如何使用Safari浏览器来收藏喜欢的 ...

  7. axios上传excal方法

    方法一(适合传文件且带参数的方法) HTML内容 <a href="javascript:;" class="select-file"> <i ...

  8. 查看linux进程启动运行时间

    ps -eo pid,tty,user,lstart,etime,cmd|grep nginx 参数说明: pid:进程ID tty:终端 user:用户 lstart:开始时间 etime:运行时间 ...

  9. Pytest Fixture(二)

    作用域 固件的作用是为了抽离出重复的工作和方便复用,为了更精细化控制固件(比如只想对数据库访问测试脚本使用自动连接关闭的固件),pytest 使用作用域来进行指定固件的使用范围. 在定义固件时,通过  ...

  10. mac 查看,终止进程

    找到其他 活动监视器 在这里可以查看和cpu占用 杀死有问题的过程: 1.它占用了大量CPU周期或内存, 2.在"活动监视器"中被突出显示为已崩溃, 先单击该过程,点击x可以终止进 ...