JUC线程框架深度解析 — 01、JUC基础使用
JUC线程框架深度解析 — 02、线程同步锁
JUC线程框架深度解析 — 03、线程同步辅助工具类
JUC线程框架深度解析 — 04、并发集合支持类
JUC线程框架深度解析 — 05、阻塞队列
JUC线程框架深度解析 — 06、DelayQueue延迟队列
JUC线程框架深度解析 — 07、线程池
JUC线程框架深度解析 — 08、ForkJoinTask架构
一、JUC开发包简介
【 java.util.concurrent开发包 】
➣ 传统线程编程模型之中为防止死锁等现象的出现(wait()、notify()、
synchronized)时往往会考虑性能、公平性、资源管理等问题,这样加重了程序开发人员的负担;
➣ Java5.0添加了一个新的java.util.concurrent开发包(简称JUC)。
利用此包进行的多线程编程将有效的减少竞争条件(race conditions)和死锁线程。
【 java.util.concurrent核心类 】
1)Executor:具有Runnable任务的执行者。
2)ExecutorService:一个线程池管理者,其实现类有多种,我会介绍一部分,我们能把Runnable,Callable提交到池中让其调度。
3)Semaphore:一个计数信号量。
4)ReentrantLock:一个可重入的互斥锁定Lock,功能类似synchronized,但要强大的多。
5)Future:是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等,还提供了cancel终止线程。
6)BlockingQueue:阻塞队列。
7)CompletionService:ExecutorService的扩展,可以获得线程执行结果的。
8)CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
9)CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。
10)Future:表示异步计算的结果。
11)ScheduldExecutorService:一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
二、TimeUnit工具类
在java.util.concurrent开发包里面提供有一个TimeUnit类, 时间单元类。该类是一个枚举类型,这也是juc开发包里面唯一的一个枚举类。
public enum TimeUnit extends Enum<TimeUnit>
这个类之中支持有:日、时、分、秒、毫秒、微妙、纳秒,这些是更加精准的控制单位。
范例:
如果现在要求你进行休眠控制,休眠2秒
• 休眠的处理只有Thread.sleep()方法。
public static void main(String[] args) throws Exception {
System.out.println("【START】" + System.currentTimeMillis());
Thread.sleep(2 * 1000); // 需要自己根据毫秒来进行计算
System.out.println("【END】" + System.currentTimeMillis());
}
那么这个时候对于以上的计算可以直接利用TimeUnit做准确处理。
范例:
休眠2秒
TimeUnit.SECONDS.sleep(2); // 休眠2秒
在TimeUnit里面最为重要的特点是可以方便的进行各种时间单位的转换,它提供了一个convert()方法。
范例:
实现转换处理
public long convert(long sourceDuration, TimeUnit sourceUnit) {
throw new AbstractMethodError();
}
———————
public static void main(String[] args) throws Exception {
long time = TimeUnit.MILLISECONDS
.convert(1, TimeUnit.HOURS) ;
System.out.println("一小时转为毫秒:" + time);
}
一小时转为毫秒:3600000
范例:
想知道3天后的日期
public static void main(String[] args) throws Exception {
long time = TimeUnit.MILLISECONDS.convert(3, TimeUnit.DAYS);
System.out.println("3天转为毫秒:" + time);
// 当前时间的毫秒数 + 三天后的毫秒数
long threeTime = System.currentTimeMillis() + time;
System.out.println("3天后的日期:" + new Date(threeTime));
System.out.println("3天后的日期:" + new SimpleDateFormat(
"yyyy-MM-dd").format(new Date(threeTime)));
}
3天转为毫秒:259200000
3天后的日期:Wed Feb 28 11:11:54 CST 2018
3天后的日期:2018-02-28
如果不使用这样的处理操作,则这个时间间隔的计算将会非常麻烦,需要各种乘法计算。
三、原子操作类
既然强调了并发访问,那么就必须考虑操作系统位数;32位操作系统还是64位操作系统,对于Long数据类型而言,是64位的,如果现在项目运行在32位上,则long这个数据类型会占据两个32位空间进行数据的保存。
如果现在每一个程序类里面都去使用long进行处理的时候都手工进行volatile配置是不是太麻烦了,为了解决这样的问题,在juc里面提供有一个atomic子包,这个自包里面保存的都是原子性的操作数据,也就是说这个包里所包含的数据类型前都使用volatile进行声明。
【 原子操作分类 】
➣ 原子操作,是指操作过程不会被中断,保证数据操作是以原子方式进行的;
• 基本类型:AtomicInteger, AtomicLong, AtomicBoolean.
• 数组类型:AtomicIntegerArray, AtomicLongArray.
• 引用类型:AtomicReference, AtomicStampedRerence.
• 对象的属性修改类型:AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater.
AtomicBoolean AtomicReference
AtomicInteger AtomicReferenceArray
AtomicIntegerArray AtomicReferenceFieldUpdater
AtomicIntegerFieldUpdater AtomicStampedReference
AtomicLong DoubleAccumulator
AtomicLongArray DoubleAdder
AtomicLongFieldUpdater LongAccumulator
AtomicMarkableReference LongAdder
范例:
观察“AtomicLong”类型
实际上使用 AtomicLong 的时候里面包含有的就是”private volatile long value;”
public static void main(String[] args) throws Exception {
AtomicLong num = new AtomicLong(100) ; // 设置为原子性操作
System.out.println("数据自增:" + num.incrementAndGet()); // 101
System.out.println("数减自增:" + num.decrementAndGet()); // 100
}
范例:
实现基础数学运算
public static void main(String[] args) throws Exception {
AtomicLong num = new AtomicLong(100); // 设置为原子性操作
System.out.println(“加法操作:” + num.addAndGet(100)); // 200
System.out.println(“减法操作:” + num.addAndGet(-10)); // 190
}
毕竟这种操作不是原始的基本数据类型,它的操作时刻需要保证数据在多线程访问下的并发安全性,对于原子性的处理操作,以上并不是它的重点,只是它的操作形式,这里面最为重要的一个方法:
CAS方法:public final boolean compareAndSet(long expect, long update);
范例:
观察CAS的方法使用
public static void main(String[] args) throws Exception {
AtomicLong num = new AtomicLong(100) ; // 设置为原子性操作
// 如果现在要进行修改的内容是100,即:原始的原子类型里面为100,则使用333替换num的内容
// 比较的值等于100,返回true
System.out.println(num.compareAndSet(100, 333));
System.out.println(num); // 结果:333
}
使用CAS方法进行内容修改的时候一定要设置一个原始的比较内容,如果内容相同才可以修改,如果现在操作的是数组也有与之对应的程序类:
设置数组的长度:public AtomicLongArray(int length),动态开辟;
设置具体的数组内容:public AtomicLongArray(long[] array),静态开辟;
范例:
进行数组操作
public static void main(String[] args) throws Exception {
AtomicLongArray array = new AtomicLongArray(new long[]{1, 2, 3});
array.set(0, 99); // 原子性的数组必须使用set修改内容
System.out.println(array); // 输出结果:[99, 2, 3]
}
范例:
使用原子性进行对象的描述
import java.util.concurrent.atomic.AtomicReference;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
AtomicReference<Member> ref = new AtomicReference<Member>();
Member memA = new Member("张三", 20);
Member memB = new Member("李四", 30);
ref.set(memA);
// 对象引用变更只得依靠地址比较“==”
ref.compareAndSet(memA, memB);
System.out.println(ref); // 结果:name = 李四、age = 30
}
}
class Member {
private String name;
private int age;
public Member(String name, int age) {
this.name = name; this.age = age;
}
@Override
public String toString() {
return "name = " + this.name + "、age = " + this.age;
}
}
以上集中类型严格来讲都算是常用的几种处理形式,但有一个问题,即:你可能本身类中定义的类型不是AtomicLong类型,那么可以用AtomicLongFieldUpdater类完成处理。
获得对象: public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName);
范例:
使用AtomicLongFieldUpdater更新器
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
Book book = new Book(100000001,"Java开发实战") ;
book.setBid(200000003); // 修改bid
System.out.println(book);
}
}
class Book {
private volatile long bid ; // 必须追加volatile关键字
private String title ;
public Book(long bid,String title) {
this.bid = bid ;
this.title = title ;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setBid(long bid) {
AtomicLongFieldUpdater updater = AtomicLongFieldUpdater.newUpdater(
super.getClass(), "bid") ;
// 使用cas方法进行内容的修改
updater.compareAndSet(this, this.bid, bid) ;
}
@Override
public String toString() {
return "图书编号:" + this.bid + "、名称:" + this.title;
}
} //输出结果:图书编号:200000003、名称:Java开发实战
四、ThreadFactory线程工厂类
在默认情况下如果要想创建一个线程类对象,大部分情况我们的选择,直接通过子类为父接口进行实例化,利用一个Runnable子类为Runnable接口实例化,或者直接利用Lambda表达式进行处理,不过在多线程运行机制里面考虑到线程对象创建的合理性,专门提供有一个ThreadFactory程序类,这是个工厂类,观察ThreadFactory类的定义:
public interface ThreadFactory {
public Thread newThread(Runnable r);
}
范例:
使用ThreadFactory程序类
import java.util.concurrent.ThreadFactory;
class SimpleThreadFactory implements ThreadFactory {
private static int count = 0 ; // 进行一个计数的操作
public Thread newThread(Runnable r) {
return new Thread(r, "线程 - " + count++);
}
}
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
Thread t = new SimpleThreadFactory().newThread(()->{
for (int x = 0 ; x < 10 ; x ++) {
System.out.println(Thread.currentThread().getName()
+ "、x = " + x);
}
}) ;
t.start();
}
}
以上输出的结果:
线程 – 0、x = 0
线程 – 0、x = 1
线程 – 0、x = 2
线程 – 0、x = 3
线程 – 0、x = 4
线程 – 0、x = 5
线程 – 0、x = 6
线程 – 0、x = 7
线程 – 0、x = 8
线程 – 0、x = 9
实际上对于接口对象一直强调:必须通过工厂类来获得实例化对象,所以ThreadFactory设计虽然看起来多余,但是复合标准话的设计,可是很多的开发者未必会这样去操作,在以后进行一些程序的内部结构分析上还是需要使用ThreadFactory。