1、Java JUC简介
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
2、volatile关键字--内存可见性
下面由一段程序引出volatile关键字和内存可见性问题
package com.juc;
/**
*
* 一、volatile关键字:
*
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true)
{
if(td.isFlag())
{
System.out.println("-----------------------");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
以上程序执行结果如下,那么就会发现,当线程td修改了flag的值为true、,但是主线程里面的while循环仍然没有退出,说明读取到的flag还是false,那么就相应的提出了内存可见性问题,当多个线程操作共享数据时,彼此时不可见的。
当我们程序运行,jvm会为执行任务每一个线程分配一个独立的缓存,用于提高效率
td线程负责修改数据:
td线程修改flag时,首先回从主存拷贝一份数据flag=flase到自己的独立的缓存空间里面,然后修改flag的为true,最后将flag的值写入内存
main线程负责读取数据
读取数据也是一样,读取主存的flag=false到自己独立的缓存空间里面,然后进行while循环,对flag的值进行判断。while调用相对底层代码,执行效率非常高,以至于主线程无法再次从主存里面读取数据
那么就有了内存可见性问题,当多个线程操作共享数据时,彼此时不可见的
那么以上问题如果可以让主线程每次都从主存里面读取数据,是不是就能退出循环呢,
那么这个问题可以使用synchronized 同步锁来解决这个问题,保证每次都会刷新主存,去主存里面读取数据
执行结果如下,说明每次都会从主存读取数据
但是使用了同步锁,效率大大的降低,多个线程进来的时候,还需要判断锁,当有线程使用的时候,还会造成阻塞问题,,那么这会就有了volatile关键字,
当多个线程进行操作共享数据时,可以保证内存中的数据可见。 可以理解为 他们进行操作直接在主存中进行,,每次读取和写都是在主存中进行。
那么在flag前面加上 volatile关键字的时候,执行结果如下
volatile关键字相较于synchronized是一种j较为轻量级的同步策略
volatile 不具备“互斥性” synchronized是互斥锁,当有一个线程获取到这个锁的时候,其他线程就会发生阻塞
volatile 不能保证变量的原子性,原子性意味着不可分割。
三、原子性操作解释
例如 i++; 这个操作,它不是一个原子性操作,在实际执行时需要三步操作“读-改-写”:
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* i++的原子性问题: i++的操作实际上分为三个步骤的:读-改-写
* int i = 10;
* i = i++;
* int temp = i;
* i = i+1;
* i = temp;
*
*/
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 volatile int serialNumber = 0;
//private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber() {
//return serialNumber.getAndIncrement();
return serialNumber++;
}
}
当线程1读取sn=0到自己的缓存空间中,进行的了加加操作,当没有写回到主存的时候,线程2也读取了sn=0;那么当线程1写回到主存打印出sn=1,当线程2也写回到主存也会打印出sn=1,
四、原子变量
1、类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的。
2、类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
3、AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
4、核心方法:boolean compareAndSet(expectedValue, updateValue)
5、java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference
AtomicIntegerArray 、AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference
6、原子变量的特征
1、volatile 保证内存可见性
2、CAS(compare-and-swap) 算法保证数据的原子性
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* i++的原子性问题: i++的操作实际上分为三个步骤的:读-改-写
* int i = 10;
* i = i++;
* int temp = i;
* i = i+1;
* i = temp;
*
*/
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 volatile int serialNumber = 0;
private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();
//return serialNumber++;
}
}
五、模拟CAS算法
CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:需要读写的内存值 V、进行比较的值 A、拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。
package com.juc;
/**
*
* 模拟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 exceptedValue = cas.get(); //获取预估值
boolean b = cas.compareAndSet(exceptedValue, (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 exceptedValue,int newValue)
{
int oldValue = value;
if(oldValue == exceptedValue)
{
this.value = newValue;
}
return oldValue;
}
//设置值
public synchronized boolean compareAndSet(int exceptedValue,int newValue)
{
return exceptedValue == compareAndSwap(exceptedValue, newValue);
}
}
六、ConcurrentHashMap 锁分段机制
1、Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
2、ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁。进而提高性能。
3、此包还提供了设计用于多线程上下文中的 Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
ConcurrentHashMap与HashTable相比较,都是线程安全的,但是HashTable是对整张哈希表进行上锁,相当于多个进行串行的访问哈希表,,但是ConcurrentHashMap采取了锁分段机制,如下图,concurrentLevel默认值为16,默认分为16个段,每个段都有自己独立的锁,每段都指向一个哈希表,那么这样的话,多个线程可以并行的访问哈希表,相较于hashtable的效率较高
package com.juc;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/*
* 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<String> list = Collections.synchronizedList(new ArrayList<String>()); 使用这行代码的时候,会出现修改值异常
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 闭锁
1、Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
2、CountDownLatch 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
3、闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
确保某个计算在其需要的所有资源都被初始化之后才继续执行;
确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
等待直到某个操作所有参与者都准备就绪再继续执行
package com.juc;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch:闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行
*/
public class TestCountDownLatch {
public static void main(String[] agrs) {
CountDownLatch latch = new CountDownLatch(5); // 5表示有5个线程,底层维护一个变量,当有一个线程结束后,变量进行减减操作。
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
latch.await(); // 等待 等待所有线程执行完成之后,才执行下面的语句
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start) + "ms");
}
}
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 接口
1、Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口
2、Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
3、Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁。
package com.juc;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 一、创建执行线程的方式三:实现Callable接口。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
* 二、执行Callable方式,需要FutureTask实现类的支持,用于接受运算结果。FutureTask是Future接口的实现类
*/
public class TestCallable {
public static void main(String[] args) {
ThreadDemo1 td = new ThreadDemo1();
// 1.执行Callable方式,需要FutureTask实现类的支持,用于接受运算结果
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
// 2.接收线程运算后的结果
try {
Integer sum = result.get(); // FutureTask可用于闭锁 只有当线程结束后才会获取值
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
九、Lock 同步锁
1、在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和 volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
2、ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
package com.juc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 一、用于解决多线程安全问题的方式:
*
* synchronized:隐式锁
* 1. 同步代码块
* 2. 同步方法
*
* jdk 1.5 后:
* 3. 同步锁 Lock
* 注意:是一个显示锁,需要通过 lock() 方法上锁,必须通过 unlock() 方法进行释放锁
*/
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
class Ticket implements Runnable{
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock(); //上锁
try{
if(tick > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " 完成售票,余票为:" + --tick);
}
}finally{
lock.unlock(); //释放锁
}
}
}
}
十、 Condition 控制线程通信
1、Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
2、在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
3、Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用其 newCondition() 方法。
生产者消费者案例来说明线程通信问题
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者消费者问题
* @author lenovo
*
*/
public class TestProductorAndConsumerForLock {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro,"生产者A").start();
new Thread(cus,"消费者B").start();
new Thread(pro,"生产者C").start();
new Thread(cus,"消费者D").start();
}
}
//店员
class Clerk{
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//进货
public void get()
{
lock.lock();
try {
while(product >= 1) //为了避免x虚假唤醒问题,应该总是使用在循环中
{
System.out.println("产品已满,无法添加");
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+(++product));
condition.signalAll();
} finally{
lock.unlock();
}
}
//卖货
public void sale() // product =0 ;循环次数:1
{
lock.lock();
try {
while(product <=0)
{
System.out.println("缺货");
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+ (--product));
condition.signalAll();
} finally {
lock.unlock();
}
}
}
//生产者
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk)
{
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable
{
private Clerk clerk;
public Consumer(Clerk clerk)
{
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
十一、线程按序交替
Lock和Condition结合应用以实现线程按序交替。
案例:
编写一个程序,开启 3 个线程,这三个线程的 ID 分别为A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。如:ABCABCABC…… 依次递归。
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {
public static void main(String[] agrs) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopA();
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopB();
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopC();
}
}
}, "C").start();
}
}
class AlternateDemo {
private int number = 1; // 当前正在执行的线程标记
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA() {
lock.lock();
try {
// 1.判断
if (number != 1) {
condition1.await();
}
// 2.打印
System.out.print(Thread.currentThread().getName());
// 3.唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB() {
lock.lock();
try {
if (number != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
number = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void loopC() {
lock.lock();
try {
if (number != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
number = 1;
condition1.signal();
} finally {
lock.unlock();
}
}
}
十二、ReadWriteLock 读写锁
1、ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
2、ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
package com.juc;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* 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();
}
}
}
十三、线程八锁
package com.juc;
/**
*
* 题目: 判断打印的“one” or “two”
*
* 1. 两个普通同步方法,两个线程,标准打印 打印出 one two
* 2. 新增Thread.sleep()方法给getOne(), 打印出 one two
* 3. 新增普通方法getThree(), 打印出 three one two
* 4. 两个普通的同步方法,l两个Number对象 打印 two one
* 5. 修改getOne()为静态方法 打印 two one
* 6. 修改两个方法都为静态同步方法,一个Number对象 打印 one two
* 7. 一个静态同步方法,一个非静态同步方法,两个Number对象 打印 two one
* 8 两个静态同步方法,两个Number对象 打印 one two
*
*
* 线程八锁的关键:
* 1、非静态方法的锁 默认为this ,静态方法的锁为 CLass实例
* 2、某一个时刻内,只能有一个线程持有锁 无论几个方法
*/
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(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo()
{
System.out.println("two");
}
public void getThree()
{
System.out.println("three");
}
}
总结:
1、非静态同步方法用的锁是——实例对象本身(this对象),也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以不需要等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
2、静态同步方法用的锁是——类对象本身(Class对象,只有一份),this对象和Class对象是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。
比如
Number number1 = new Number();
Number number2 = new Number();
这两个对象都是Number类的实例对象,
十四、线程池
1、第四种获取线程的方法:线程池,一个 ExecutorService,通常使用 Executors 工厂方法配置。
2、线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。
3、Executors 工厂方法 :
ExecutorService Executors.newCachedThreadPool()(缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。)
ExecutorService Executors.newFixedThreadPool(int)(创建固定大小的线程池)
ExecutorService Executors.newSingleThreadExecutor()(创建单个线程池。线程池中只有一个线程)
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
它们均为大多数使用场景提供了配置。
package com.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/*
* 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
*
* 二、线程池的体系结构:
* 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++);
}
}
}
线程调度
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
package com.juc;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
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); // 1 延迟时间 TimeUnit.SECONDS时间单位
System.out.println(result.get());
}
pool.shutdown();
}
}
十五、ForkJoinPool 分支/合并框架 工作窃取
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
Fork/Join 框架与线程池的区别
1、采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
2、相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
package com.juc;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
import org.junit.Test;
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();
}
}
}