JUC线程框架深度解析 — 01、JUC基础使用

  • Post author:
  • Post category:其他




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。



版权声明:本文为androidsj原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。