JUC与线程池

JUC 概述

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步IO和轻量级任务框架;还提供了用于多线程上下文中的 Collection实现等。

volatile
volatile:易变的,不稳定的

在并发编程中的三个特性:

  • 互斥性(原子性)

  • 内存可见性

  • 指令重排序

     指令重排序:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令单线程环境里面确保最终执行结果和代码顺序的结果一致处理器在进行重排序时,必须要考虑指令之间的数据依赖性多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
    

volatile 关键字: 当多个线程进行操作共享数据时,可以保证内存中的数据是可见的;相较于 synchronized 是一种较为轻量级的同步策略;不具备“互斥性”;不能保证变量的“原子性”.

接下来谈谈synchronized和volatile的区别:

  • synchronized可以实现互斥性和内存可见性,不能禁止指令重排序
  • volatile可以实现内存可见性,禁止指令重排序,不能保证原子性(互斥性)。

然后是static和volatile的区别:

  • static指的是类的静态成员,实例间共享
  • volatile跟Java的内存模型有关,线程执行时会将变量从主内存加载到线程工作内存,建立一个副本,在某个时刻写回。valatile指的每次都读取主内存的值,有更新则立即写回主内存。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

“既然static保证了唯一性”:static保证唯一性,指的是static修饰的静态成员变量是唯一的,多个实例共享这唯一一个成员。
“那么他对多个线程来说都是可见的啊”:这里,static其实跟线程没太大关系,应该说对多个对象实例是可见的。你说对多个线程可见,虽然没什么毛病,因为静态变量全局可见嘛,但是把这个理解转到线程的上线文中是困惑的起因。
“volatile保证了线程之间的可见性”:因为线程看到volatile变量会去读取主内存最新的值,而不是自个一直在那跟内部的变量副本玩,所以保证了valatile变量在各个线程间的可见性。
“那么修改的时候只要是原子操作,那么就会保证它的唯一性了吧”:此时你说的“唯一性”,指的是各个线程都能读取到唯一的最新的主内存变量,消除了线程工作内存加载变量副本可能带来的线程之间的“不唯一性”。这里“唯一性”的含义跟第一句说的“唯一性”是不一样的。

造成理解困惑最大的原因在于,这两个场景略有类似,以致混淆了:
场景1:各个类的实例共享唯一一个类静态变量
场景2:各个线程共同读取唯一的最新的主内存变量的值,只保证可见性,它不足以保证数据的同步性。

案例演示
class MyThread extends Thread{
//标记
public volatile	boolean flag=false; @Override
public void run() {
System.out.println("子线程开始执行了	");
while(true) {
if(flag) {
break;
}
}
System.out.println("子线程结束了	");
}
}
public static void main(String[] args) throws Exception { 
MyThread myThread=new MyThread(); myThread.start();
System.out.println("请输入任意字符结束子线程"); System.in.read();
myThread.flag=true;//把变量改成true
System.out.println(myThread.flag); System.out.println("主线程结束了	");
}
案例演示:懒汉式单例
/**
*懒汉式
*(1)私有化构造方法
*(2)类内部创建对象
*(3)添加公开的方法,返回这个对象
*
*/
public class SingleTon { 
private SingleTon() {

}
private volatile static SingleTon instance; public static SingleTon getInstance() {
if(instance==null) {	//双重检查 double check synchronized (SingleTon.class) {
if(instance==null) {
instance=new SingleTon();
//实例化过程
//(1) 堆中开辟空间
//(2) 调用构造方法初始化对象
//(3) 把对象的地址赋值给变量
}
}
}
return instance;
}

单例其他写法

静态内部类写法

package com.bigdata.juc;
/**
*单例静态内部类写法
*(1)私有化构造方法
*(2)创建静态内部类 , 在静态内部类中创建常量
*(3)添加公开的方法,返回这个对象
*
*好处:(1)节省内存空间
*(2)解决了线程安全问题
*
*/
public class SingleTon2 { 
private SingleTon2() {
}
private static class SingleTon2Holder{
private static final SingleTon2 INSTANCE=new SingleTon2();
}
public static SingleTon2 getInstance() { return SingleTon2Holder.INSTANCE;
}
}
i++的原子性问题
  1. i++的操作实际上分为三个步骤: “读-改-写”; i++可拆分为:
    int temp1=i;
    int temp2=temp+1; i=temp2;
    提示:使用 javap -c Demo.class 可查看字节码
  2. 原子性: 就是"i++"的"读-改-写"是不可分割的三个步骤;
  3. 原子变量: JDK1.5 以后, java.util.concurrent.atomic包下,提供了常用的原子变量; 原子变量中的值,使用volatile 修饰,保证了内存可见性;
  4. CAS(Compare-And-Swap) 算法保证数据的原子性;
案例演示
class AtomicDemo implements Runnable{

private int num=0; @Override
public void run() { 
try {
Thread.sleep(200);
} catch (InterruptedException e) {
 e.printStackTrace();
}
System.out.println(++num);
}
}
public static void main(String[] args) {

AtomicDemo atomicDemo=new AtomicDemo(); for(int i=0;i<10;i++) {
new Thread(atomicDemo).start();
}
}
使用原子变量
class AtomicThread implements Runnable{
//private int num=0;
private AtomicInteger atomicinteger=new AtomicInteger(0);
@Override
public void run() { 
System.out.println(atomicinteger.getAndIncrement());//i++
}
}
public static void main(String[] args) {
        AtomicThread atomicThread = new AtomicThread();
        for (int i = 0; i < 10; i++) {
            new Thread(atomicThread).start();
        }
}
CAS 算法
  1. CAS(Compare-And-Swap) 算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问;
  2. CAS 是一种无锁的非阻塞算法(属于乐观锁)的实现;
  3. CAS 包含了三个操作数:
    进行比较的旧预估值:
    A 需要读写的内存值:
    V 将写入的更新值: B
    当且仅当 A == V 时, V = B, 否则,将不做任何操作,并且这个比较交换过程属于原子操作;
模拟CAS算法
public class CompareAndSwapDemo {
public static void main(String[] args) {
CompareAndSwap compareAndSwap = new CompareAndSwap(); for (int i = 0; i < 10; i++) {
new Thread(new Runnable() { 
@Override
public void run() {


int expect = compareAndSwap.get();
boolean b = compareAndSwap.compareAndSwap(expect, new Random().nextInt(101));
System.out.println(b);
}
}).start();
}
}
}

class CompareAndSwap { 
private int value;

/**
*获取值
*
*@return
*/
public synchronized int get() { 
return value;
}


public synchronized boolean compareAndSwap(int expect, int newValue) { 
if (this.value == expect) {
this.value = newValue; return true;
}
return false;
}
}

public class CASDemo {
    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(2020);
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());

        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());

    }
}
线程池
	为什么我们要用线程池?
  1. 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率
  2. 线程并发数量过多,抢占系统资源从而导致阻塞
  3. 对线程进行一些简单的管理

在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类,学习Java中的线程池,就可以直接学习他了对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置

ThreadPoolExecutor提供了四个构造函数:
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue)

//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)

//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)

//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

下面来解释下各个参数

  • int corePoolSize:该线程池中核心线程数最大值
    核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
    如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。
  • int maximumPoolSize: 该线程池中线程总数最大值
    线程总数 = 核心线程数 + 非核心线程数。
  • long keepAliveTime:该线程池中非核心线程闲置超时时长
    一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置
    allowCoreThreadTimeOut = true,则会作用于核心线程。
  • TimeUnit unit:keepAliveTime的单位
    TimeUnit是一个枚举类型,其包括:
    NANOSECONDS : 1微毫秒 = 1微秒 / 1000
    MICROSECONDS : 1微秒 = 1毫秒 / 1000
    MILLISECONDS : 1 毫 秒 = 1 秒 /1000
    SECONDS : 秒
    MINUTES : 分
    HOURS : 小时
    DAYS : 天
  • BlockingQueue workQueue:该线程池中的任务队列:维护着等待执行的Runnable对象
    当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执 行任务。
向ThreadPoolExecutor添加任务

我们怎么知道new一个ThreadPoolExecutor,大概知道各个参数是干嘛的,可是我new完了,怎么向线程池提交一 个要执行的任务啊?

  1. ThreadPoolExecutor.execute(Runnable command)
    通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加一个任务。
ThreadPoolExecutor的策略
  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  2. 线程数量达到了corePools,则将任务移入队列等待
  3. 队列已满,新建线程(非核心线程)执行任务
  4. 队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常
常见四种线程池

如果你不想自己写一个线程池,Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置
ThreadPoolExecutor的参数实现的。

  1. 可缓存线程池CachedThreadPool()
    创建方法:
    ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();

用法

   ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            });
        }
  1. FixedThreadPool 定长线程池

用法

     ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
  1. SingleThreadPool
    创建方法:
    ExecutorService mSingleThreadPool = Executors.newSingleThreadPool();

用法


        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
  1. ScheduledThreadPool

用法

      ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable() {

            @Override
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);

        //表示延迟3秒执行。
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);
        //表示延迟1秒后每3秒执行一次。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值