在 Java 5.0 提供了 java.util.concurrent (简称 JUC )包,在此包中增加了在并发编程中很常用 的实用工具类,用于定义类似于线程的自定义子 系统,包括线程池、异步 IO 和轻量级任务框架。 提供可调的、灵活的线程池。还提供了设计用于 多线程上下文中的 Collection 实现等。
概述
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态 而另一个线程在同时修改该状态,需要确保当一个线程修改了对象 状态后,其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无 法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚 至是根本不可能的事情。
我们可以通过同步来保证对象被安全地发布。除此之外我们也可以 使用一种更加轻量级的 volatile 变量。
volatile关键字
volatile关键字:调用计算机底层代码,将线程修改过后的数据(堆之类的)实时刷新。其实是volatile关键字修饰的变量不会再被线程缓存,线程向操作就只能去主存中读取,保证每个线程拿到的数据都是最新的。
Java 提供了一种稍弱的同步机制,即 volatile 变 量,用来确保将变量的更新操作通知到其他线程。 可以将 volatile 看做一个轻量级的锁,但是又与 锁有些不同:
volatile 关键字:但多个线程操作共享数据时,可以保证内存中的数据可见。相较于synchronized是一种较为轻量级的同步策略。
// 这个变量在内存中可见。因为每个线程都有自己的缓冲区,导致线程之间内存不可见。
// 如果线程需要操作主存中的数据(deap,method area),为了提交效率线程会将要操作的主存数据copy到当前线程的缓存中
// 加了volatile 之后,线程操作使用volatile修饰的数据时,能够实时的读取到主存的数据(底层是有一个内存栅栏调用底层代码保证数据的实时性)
private volatile boolean flag = false;
死循环问题
jvm为了提高效率,会给每个线程分配缓冲空间(虚拟机栈),如果线程有使用到堆里面的东西,会先把这个东西copy到当前线程的缓冲中,如果该线程对这个数据进行的修改操作,会在缓存中修改,然后copy到主线程,实现数据的操作(一个进程有多个线程,进程有自己的堆和方法区,线程有自己的虚拟机栈和程序计数器,进程里面的线程可以操作进程里面的数据,实现线程间数据共享。)
然后where(true) 是很底层的代码,执行效率很快,没有时间读取主存中刚修改过的数据,导致死循环。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBZFiRmu-1600069656746)(/Users/haitao/Pictures/TyporaPic/17_juc/image-20200914085810672.png)]
class Test {
public static void main(String[] args) {
Runnable1 rb = new Runnable1();
new Thread(rb).start();
// 由于线程的之间的内存不可见性,会导致死锁
while (true) {
// 解决办法一:休眠一段时间,让子线程先执行完
/*try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
*/
// 解决办法二,加锁
// synchronized (rb) {
if (rb.isFlag()) {
System.out.println("-----------------");
break;
}
// }
}
}
}
class Runnable1 implements Runnable {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
flag = true;
if (flag) {
System.out.println("flag=" + flag);
}
}
}
使用volatile关键字
==使用volatile关键字之后,就会有一个内存栅栏调用底层代码实时刷新线程里面的共享数据到主存中。我们可以理解成我们读写都是直接操作主存的数据。==因为实时读取刷新主存的数据所以效率相对低一点,但是比使用synchronized快。
class Test {
public static void main(String[] args) {
Runnable1 rb = new Runnable1();
new Thread(rb).start();
while (true) {
if (rb.isFlag()) {
System.out.println("-----------------");
break;
}
}
}
}
class Runnable1 implements Runnable {
private volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
flag = true;
if (flag) {
System.out.println("flag=" + flag);
}
}
}
CAS算法概述
CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器 操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并 发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的 值,否则不会执行任何操作。
原子变量
类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可 将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。
类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对 相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操 作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方 面也引人注目,这对于普通数组来说是不受支持的。
核心方法:boolean compareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference AtomicIntegerArray 、AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDL9BVGG-1600069656748)(/Users/haitao/Pictures/TyporaPic/17_juc/image-20200914094727007.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9g9c50P-1600069656749)(/Users/haitao/Pictures/TyporaPic/17_juc/image-20200914101256005.png)]
/*
* 一、i++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
* int i = 10;
* i = i++; //10
*
* int temp = i;
* i = i + 1;
* i = temp;
*
* 二、原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。
* 1. volatile 保证内存可见性(可以看成实时想主存中读取数据)
* 2. CAS(Compare-And-Swap) 算法保证数据变量的原子性
* CAS 算法是硬件对于并发操作的支持(计算机底层的支持 )
* CAS 包含了三个操作数:计算机底层保证了 比较和替换的原子性
* ①内存值 V (先读取内存值)
* ②预估值 A (再次获取内存值)
* ③更新值 B
* 当且仅当 V == A 时, V = B; 否则,不会执行任何操作,此时不会失去cpu的执行权,所以我们可以写代码判断一下,不要浪费这一次拿到cpu执行权的机会(所以效率比synchronized高)
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
// private int serialNumber = 0;
// private volatile int serialNumber = 0;
private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber() {
// return serialNumber++;
return serialNumber.getAndIncrement();
}
}
模拟CAS算法
/*
* 模拟 CAS 算法
*/
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//获取内存值
public synchronized int get(){
return value;
}
//比较
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
if(oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//设置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKQtEjNS-1600069656751)(/Users/haitao/Pictures/TyporaPic/17_juc/image-20200914102910437.png)]
/*
* CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
* 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
*/
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable{
// private static List list = Collections.synchronizedList(new ArrayList());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static{
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
/*
* CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
*/
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(50);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 50; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
/*
* 一、创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
*
* 二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
*/
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
//2.接收线程运算后的结果
try {
Integer sum = result.get(); //FutureTask 可用于 闭锁,因为调用get()方法,只有线程执行完才能解锁使程序向下执行
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用 法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的 功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关 联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版 本中的不同。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
想要实现按序交替就肯定涉及到线程的通信
class testOrderDemo {
public static void main(String[] args) {
OrderDemo od = new OrderDemo();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 10; i++) {
od.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 10; i++) {
od.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 10; i++) {
od.loopC(i);
}
}
}, "C").start();
}
}
class OrderDemo {
private int num = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalNum) {
// 第几次循环
lock.lock();
try {
if (num != 1) {
// 等待,
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "第几轮:" + totalNum);
}
// 打印完成唤醒别的线程
num = 2;
condition2.signal();
} finally {
lock.unlock();
}
}
public void loopB(int totalNum) {
// 第几次循环
lock.lock();
try {
if (num != 2) {
// 等待,
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "第几轮" + totalNum);
}
// 打印完成唤醒别的线程
num = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void loopC(int totalNum) {
// 第几次循环
lock.lock();
try {
if (num != 3) {
// 等待,
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "第几轮" + totalNum);
}
System.out.println("-------------");
// 打印完成唤醒别的线程
num = 1;
condition1.signal();
} finally {
lock.unlock();
}
}
}
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLoack().lock();
lock.readLoack().unlock();
lock().writeLock().lock();
lock().writeLock().unlock();
/*
* 1. ReadWriteLock : 读写锁
*
* 写写/读写 需要“互斥”
* 读读 不需要互斥
*
*/
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockDemo rw = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random() * 101));
}
}, "Write:").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
}).start();
}
}
}
class ReadWriteLockDemo{
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
//读
public void get(){
lock.readLock().lock(); //上锁
try{
System.out.println(Thread.currentThread().getName() + " : " + number);
}finally{
lock.readLock().unlock(); //释放锁
}
}
//写
public void set(int number){
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName());
this.number = number;
}finally{
lock.writeLock().unlock();
}
}
}
弄懂同步监视器是谁,就能搞定这些问题
/*
* 题目:判断打印的 "one" or "two" ?
*
* 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two
* 2. 新增 Thread.sleep() 给 getOne() ,打印? //one two
* 3. 新增普通方法 getThree() , 打印? //three one two
* 4. 两个普通同步方法,两个 Number 对象,打印? //two one
* 5. 修改 getOne() 为静态同步方法,打印? //two one
* 6. 修改两个方法均为静态同步方法,一个 Number 对象? //one two
* 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one
* 8. 两个静态同步方法,两个 Number 对象? //one two
*
* 线程八锁的关键:
* ①非静态方法的锁默认为 this, 静态方法的锁为 对应的 Class 实例
* ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// number.getTwo();
number2.getTwo();
}
}).start();
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne(){
//Number.class
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo(){
//this
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收) Executors.newFixedThreadPool(int)(固定大小线程池) Executors.newSingleThreadExecutor()(单个后台线程)
它们均为大多数使用场景预定义了设置。
/*
* 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
*
* 二、线程池的体系结构:
* java.util.concurrent.Executor : 负责线程的使用与调度的根接口
* |--**ExecutorService 子接口: 线程池的主要接口
* |--ThreadPoolExecutor 线程池的实现类
* |--ScheduledExecutorService 子接口:负责线程的调度
* |--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService
*
* 三、工具类 : Executors
* ExecutorService newFixedThreadPool() : 创建固定大小的线程池
* ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
* ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
*
* ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
*/
public class TestThreadPool {
public static void main(String[] args) throws Exception {
//1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
list.add(future);
}
pool.shutdown();
for (Future<Integer> future : list) {
System.out.println(future.get());
}
/*ThreadPoolDemo tpd = new ThreadPoolDemo();
//2. 为线程池中的线程分配任务
for (int i = 0; i < 10; i++) {
pool.submit(tpd);
}
//3. 关闭线程池
pool.shutdown();*/
}
// new Thread(tpd).start();
// new Thread(tpd).start();
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
就是可以设置线程的延时启动
一个 ExecutorService,可安排在给定的延迟后运行或定 期执行的命令。
/*
* ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
*/
public class TestScheduledThreadPool {
public static void main(String[] args) throws Exception {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
Future<Integer> result = pool.schedule(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);//生成随机数
System.out.println(Thread.currentThread().getName() + " : " + num);
return num;
}
}, 1, TimeUnit.SECONDS);
System.out.println(result.get());
}
pool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oP1llGQ-1600069656753)(/Users/haitao/Pictures/TyporaPic/17_juc/image-20200914153431418.png)]
采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加
到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队 列中。 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务 的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些 原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中, 如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理 该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了 线程的等待时间,提高了性能。
public class TestForkJoinPool {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
}
@Test
public void test1(){
Instant start = Instant.now();
long sum = 0L;
for (long i = 0L; i <= 50000000000L; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
}
//java8 新特性
@Test
public void test2(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0L, 50000000000L)
.parallel()
.reduce(0L, Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long>{
/**
*
*/
private static final long serialVersionUID = -259195479995561737L;
private long start;
private long end;
private static final long THURSHOLD = 10000L; //临界值
public ForkJoinSumCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THURSHOLD){
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
left.fork(); //进行拆分,同时压入线程队列
ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
right.fork(); //
return left.join() + right.join();
}
}
}