JAVA多线程Ⅱ(多线程高级)

目录

九、线程状态

十、线程池

1.创建线程池的目的

2.线程池的基本代码实现

method1:创建默认的线程池---Executors.newCachedThreadPool()

 method2:创建指定上限的线程池---Executors.newFixedThreadPool()

3.自己创建线程池---new ThreadPoolExecutor();

 4.自己创建线程池的参数详解

①任务的拒绝策略流程:

②任务拒绝策略的几种方式

 ③关于shutdown()和shutdownNow()的区别

十一、volatile问题

1.线程之间数据副本不更新问题

2.原子性---送花案例

十二、AtomicInteger类 ---原子性

 1.构造方法

2.成员方法

3.送花案例优化---底层CAS算法

4.AtomicInteger原理---自旋锁+CAS算法

5.悲观锁&乐观锁

十三、并发工具类 

1.HashMap<>  & Hashtable<>

2.CountDownLatch---三个孩子吃饺子案例

3.Semaphore---访问特定资源线程数


九、线程状态

线程从创建到消亡的流程:

在虚拟机中线程有六种状态:(查看API中 Thread.State)

分别为:新建状态、就绪状态、阻塞状态、等待状态、计时状态、结束状态

十、线程池

1.创建线程池的目的

我们之前的线程创建的弊端:①用到的时候都要创建,②使用完之后,线程就消失了。故此我们提供一个解决方案,创建一个容器(线程池),当我们需要执行第一个任务,那我们就创建一个线程Thread1:

此时当我们的任务1执行完,Thread1会退回到线程池,而且不会消亡。当有任务2,接着拿出来用,当有多个任务同时存在,那么线程池会自动创建新的线程

2.线程池的基本代码实现

method1:创建默认的线程池---Executors.newCachedThreadPool()

分为三个步骤:

步骤1:创建一个线程池,此时池子中为空--->  创建Executors中的静态方法

步骤2:有任务需要执行时,创建线程对象--->submit()

步骤3:所有任务全部执行完毕,关闭池子--->shutdown()

    /*
    体验线程池
    */
    public static void main(String[] args) throws InterruptedException {
        //1.创建一个默认的线程池对象,默认为空,最大容量为int类型的最大值
        ExecutorService exp = Executors.newCachedThreadPool();
        //其中Executors可以帮我们创建线程池对象
        //而ExecutorService可以帮我们控制线程池

        //2.添加线程任务
        exp.submit(()->{
            System.out.println(Thread.currentThread().getName()+"执行了代码1");
        });

        //特殊情况1:当两个任务间有时间间隔-->那么最后俩执行代码,都会有线程一去执行
        //因为线程反应时间足够
        Thread.sleep(1);

        //特殊情况2:没有时间间隔-->由于来不及反应,则会新创建一个线程执行新的代码

        exp.submit(()->{
            System.out.println(Thread.currentThread().getName()+"执行了代码2");
        });

        //3.关闭线程池
        exp.shutdown();
    }

注意:当两个任务中间存在sleep()时间间隔,那么最后输出

当两个任务中间不存在时间间隔,那么最后输出

 method2:创建指定上限的线程池---Executors.newFixedThreadPool()

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolDemo3 {
    /*
    创建指定上限的线程池
     */
    public static void main(String[] args) {
        //1.这里面的参数表示手动指定的线程池上限,然而池子创建默认的上限为0
        ExecutorService exp2 = Executors.newFixedThreadPool(2);

        //如何去查看为添加任务(未自动创建线程)的池子上限
        //先将池子强转为ThreadPoolExecutor
        ThreadPoolExecutor pool = (ThreadPoolExecutor) exp2;

        //2.最后调用getPoolSize(),可以查看线程池上限
        System.out.println(pool.getPoolSize());//0
        //因为还没添加任务,池子未自动生成线程

        //3.添加任务,查看池子现有的线程数
        exp2.submit(()->{
            System.out.println(Thread.currentThread().getName()+"线程,执行了任务1");
        });
        exp2.submit(()->{
            System.out.println(Thread.currentThread().getName()+"线程,执行了任务2");
        });
        //添加任务的特殊情况:当任务数>池子中线程数
        exp2.submit(()->{
            System.out.println(Thread.currentThread().getName()+"线程,执行了任务3");
        });
        exp2.submit(()->{
            System.out.println(Thread.currentThread().getName()+"线程,执行了任务4");
        });
        //4.输出现在线程池的线程数
        System.out.println(pool.getPoolSize());//2
        /*
        因为这个时候,由于出现了任务,线程池自动创建了线程,这里=2是因为,我们设置
        的线程池上限为2,当出现任务过多,他们则会交替进行执行
         */
        //5.关闭线程池
        exp2.shutdown();
    }
}

注意:这里输出语句无规则,是因为最后输出线程池线程数的语句在main线程中,它也要和别的线程抢夺cpu执行权。所以造成了无序

3.自己创建线程池---new ThreadPoolExecutor();

在我们自己创建线程池之前,我们跟一下默认线程池Executors.newCachedThreadPool(),和创建指定上限的线程池Executors.newFixedThreadPool(),源码如下图:

 我们发现,两个指向了同一个源码,而且里面return new ThreadPoolExecutor();然后我们去API文档中查询该类,发现ThreadPoolExecutor该类中,全为带参构造。我们可以根据参数类型去手动创建一个自定义线程池

但是前提我要明白线程池中需要的哪些部分,

import java.util.concurrent.*;

public class ThreadPoolDemo2 {
    /*
    体验手动new 一个线程池
    需要的参数有:
    1.核心线程数  ! < 0
    2.最大线程数  ! <= 0,最大线程数 = 核心线程数 + 临时线程数
    3.空闲线程最大存活时间  ! < 0
    4.时间单位----在TimeUnit类中找
    5.任务队列  ! = null
    6.创建线程工厂  ! = null
    7.任务的拒绝策略  ! = null
     */
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,  //线程池中的核心线程数量
                5,  //线程池中最大线程数量(最大线程数=核心线程数+临时线程数)
                2,  //临时线程如果没有了任务,最大存货时间
                TimeUnit.SECONDS,  //存活的时间单位
                new ArrayBlockingQueue<>(10),  //阻塞队列:当线程不够用时,用来存储任务
                Executors.defaultThreadFactory(),  //线程工厂,使用Executors类中的defaultThreadFactory()也就是默认方式
                new ThreadPoolExecutor.AbortPolicy());  //任务被线程池拒绝之后的策略(这里用内部类去写,表明超过数量的任务的处理策略)

        //创建执行任务
        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"检测是否执行任务1");
        });
        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"检测是否执行任务2");
        });

        //最后关闭线程池
        pool.shutdown();
    }
}

 4.自己创建线程池的参数详解

①任务的拒绝策略流程:

首先我们要明白任务是怎么传入的

 当线程池设定上限 + 阻塞队列设定容量 <= 任务数n,那么就不会有拒绝策略,但是此时如果存在任务n+1,那么该任务会落入拒绝策略中。

②任务拒绝策略的几种方式

//拒绝策略中的四种方式
ThreadPoolExecutor.AbortPolicy;  丢弃任务并抛出RejectedExecutionException异常,也是默认的策略
ThreadPoolExecutor.DiscardPolicy;  丢弃任务,但是不抛出异常,这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy;  抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy;  调用任务中的run()方法绕过线程池直接运行

 下面以这定义的这个线程池为例:

//拒绝策略方式一:AbortPolicy()
ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                2,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());//在此处进行策略更改
        //测试拒绝策略
        for (int i = 1; i <= 5; i++) {
            int y = i;
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName()+"执行了"+y);
            });
        }
        pool.shutdown();
-----------------------------------------------------------------------------------
//拒绝策略方式二:DiscardPolicy()
//拒绝策略方式三:DiscardOldestPolicy()
//拒绝策略方式四:CallerRunsPolicy()

方式一输出:

 方式二输出:

 

方式三输出:

方式四输出: 

 ③关于shutdown()和shutdownNow()的区别

shutdown():当线程池中的任务和阻塞队列中的任务执行完,之后才会关闭线程池

shutdownNow():当线程池中的任务完成之后,不管阻塞队列中是否存在未执行任务,都会立刻将线程池关闭!

十一、volatile问题

1.线程之间数据副本不更新问题

问题原因:两个线程最初开始获得的是共享区域中的数据副本,如果一个线程更改了共享区域数据,但是另一个线程却获取不到最新的。

public class VolatieDemo1 {
    public static void main(String[] args) {
        /*
        1.我们发现输出框没有东西输出,并且不会停止
        虽然男孩更改了money的值,但是女孩线程没有得到最新的值
        ---更改方法:在共享区域中,加入volatile修饰
            这样最后输出的:钱变少了
         */
        Girl girl = new Girl();
        girl.setName("女孩线程:");
        girl.start();

        Boy boy = new Boy();
        boy.setName("男孩线程:");
        boy.start();
    }
}
//定义一个女孩
class Girl extends Thread{
    @Override
    public void run() {
        while(Money.money == 10_0000){
        }
        System.out.println("钱变少了");
    }
}
//定义一个男孩
class Boy extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(10);
            Money.money = 9_0000;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//定义一个共享数据类,钱类
class Money {
    //2.将这里加volatile修饰
    public static volatile int money = 10_0000;
}

 上述问题也可以用synchronized()同步代码块解决。就是在共享区域创建一个锁对象

class Money {
    //2.将这里加volatile修饰
    public static volatile int money = 10_0000;

    //方法二。创建锁对象
    public static Object lock = new Object();
}
------------------------------------------------
//定义一个女孩
class Girl extends Thread{
    @Override
    public void run() {
        while (true) {
            synchronized (Money.lock){
                if(Money.money != 10_0000){
                    System.out.println("钱变少了");
                    break;
                }
            }
        }
    }
}
//定义一个男孩
class Boy extends Thread{
    @Override
    public void run() {
        synchronized (Money.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Money.money = 9_0000;
        }
    }
}

2.原子性---送花案例

原子性:多个操作是一个不可分割的整体要么同时成功,要么同时失败

有个结论:count++不是一个原子性的操作,其在执行的过程中,有可能被其它线程打断!!!

解决方案就是加入synchronized()锁;

public class MyAtomThreadDemo1 {
    /*
    测试原子性:目标,每次输出都能保证排序正确,也就是获取最新的值
    结论:
    1.当线程中没有volatile修饰变量count的时候,会造成输出结果有相同数量的玫瑰花
    2.当我们在线程变量中加volatile修饰,输出不是每次最后都是10000,
    volatile只能保证线程每次在使用共享数据的时候都是最新的值,但是不能保证
    原子性!!!!
    3.当我们在创建的线程中加入synchronized()锁,就会发现最后输出结果是按照
    我们最初猜想的1----10000排序输出
     */
    public static void main(String[] args) {
        RunnableImpl1 runnableImpl1 = new RunnableImpl1();
        for (int i = 0; i < 100; i++) {
            new Thread(runnableImpl1).start();
        }
    }
}
//创建一个线程
class RunnableImpl1 implements Runnable{
    private volatile int count = 0;
    private Object lock = new Object();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (lock) {
                count++;
                System.out.println(
                        Thread.currentThread().getName()+
                                "线程,送出了"+count+"玫瑰花");
            }
        }
    }
}

当我们使用synchronized()锁解决了上述问题之后,代码还是有弊端,那就是运行效率不高,所以在JDK1.5之后,出现了一种用法简单、性能高效、线程还非常安全的类---AtomicInteger      

十二、AtomicInteger类 ---原子性

 1.构造方法

public AtomicInteger();-----初始化一个默认值为0的原子型Integer

public AtomicInteger(int  initialValue);-----初始化一个指定值的原子型Integer

2.成员方法

    /*
    原子性的常用方法
    int get();-------------获取原子对象的值
    int getAndIncrement();-以原子方式将当前值加1,并且返回自增前的值!
    int incrementAndGet();-以原子方式将当前值加1,返回自增后的值!
    int addAndGet();-------以原子方式将输入的数值与实例中的值相加,并返回
    int getAndSet();-------以原子方式设置为newValue的值,并返回
     */
    public static void main(String[] args) {
        AtomicInteger a1 = new AtomicInteger(10);
        int i = a1.get();//获取
        System.out.println(i);//10

        AtomicInteger a2 = new AtomicInteger(11);
        int andIncrement = a2.getAndIncrement();//+1
        System.out.println(andIncrement);//11
        System.out.println(a2);//12

        AtomicInteger a3 = new AtomicInteger(12);
        int i1 = a3.incrementAndGet();//+1
        System.out.println(i1);//13

        AtomicInteger a4 = new AtomicInteger(13);
        int i2 = a4.addAndGet(2);
        System.out.println(i2);//15

        AtomicInteger a5 = new AtomicInteger(14);
        int andSet = a5.getAndSet(3);
        System.out.println(andSet);//14
        System.out.println(a5);//最后原子对象a5的值为3

3.送花案例优化---底层CAS算法

import java.util.concurrent.atomic.AtomicInteger;

public class AtomDemo1 {
    /*
    用原子类去优化刚刚synchronized锁,
    这样就优化了我们刚刚送玫瑰花的问题
     */
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        for (int i = 0; i < 100; i++) {
            new Thread(test1).start();
        }
    }
}
class Test1 implements Runnable{
    //创建一个原子对象
    AtomicInteger atm = new AtomicInteger();//初始值为0

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //调用原子对象中的自增1并且返回的方法incrementAndGet()
            int i1 = atm.incrementAndGet();
            System.out.println(Thread.currentThread().getName()+"送了"+i1+"玫瑰花");
        }
    }
}

小结:这个里面牵扯到了CAS算法,中的自旋操作

4.AtomicInteger原理---自旋锁+CAS算法

CAS算法:有3个操作数(内存值V,旧的预留值A,要修改的值B)

当旧的预留值A == 内存值,此时修改成功,将V改成B

当旧的预留值A != 内存值,此时修改失败,不做任何操作

 并重新获得现在的最新值(这个重新获取的动作就是自旋)

5.悲观锁&乐观锁

synchronized和CAS的区别:

相同点:在多线程的情况下,都可以保证共享数据的安全性

不同点:①synchronized总是从最坏的角度出发,认为每一次获取数据时,都有可能被修改,所以在每次操作共享数据前,都会上锁(悲观锁)

②CAS时从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁,只不过在修改共享数据的时候,会检查一下,被人有没有修改过这个数据(乐观锁)

十三、并发工具类 

当我们在多线程的情况下去操作集合,由于线程抢夺cpu的随机性,集合也可能产生错误

1.HashMap<>  & Hashtable<>

HashMap<>是线程不安全的(多线程环境下可能会出现问题)

Hashtable<>是安全的,但是效率低下(因为Hashtable底层采用的synchronized悲观锁来保证数据安全)

2.CountDownLatch---三个孩子吃饺子案例

使用场景:让某一条线程等待其它线程执行完毕后再执行

import java.util.concurrent.CountDownLatch;

public class CountDownlatchDemo {
    /*
    这里是关于CountDownLatch应用
    三个孩子吃饺子案例:妈妈要等待三个孩子都吃完饺子了才开始收拾碗筷
    里面用到CountDownLatch类的两个方法,
    方法一:await();等待
    方法二:countDown();通知
    原理:就是让CountDownLatch按照括号中的int参数进行自减,
    当自减为0后,然后唤醒某个线程
     */
    public static void main(String[] args) {
        //先创建CountDownLatch对象,里面的为等待的执行的线程数
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //这里的3代表为三个孩子

        //创建四个线程并开启,将上面的对象传入
        Monther monther = new Monther(countDownLatch);
        monther.start();

        Child1 child1 = new Child1(countDownLatch);
        child1.setName("小明");
        child1.start();

        Child2 child2 = new Child2(countDownLatch);
        child2.setName("小洪");
        child2.start();

        Child3 child3 = new Child3(countDownLatch);
        child3.setName("大胖");
        child3.start();
    }
}
//创建三个 孩子线程
class Child1 extends Thread{
    //传入要记得写构造方法
    private CountDownLatch countDownLatch;
    public Child1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //开始吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName()+"正在吃第"+i+"个饺子");
        }
        //吃完了说一声
        countDownLatch.countDown();
    }
}
class Child2 extends Thread{
    private CountDownLatch countDownLatch;
    public Child2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //开始吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName()+"正在吃第"+i+"个饺子");
        }
        //吃完了说一声
        countDownLatch.countDown();
    }
}
class Child3 extends Thread{
    private CountDownLatch countDownLatch;
    public Child3(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //开始吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName()+"正在吃第"+i+"个饺子");
        }
        //吃完了说一声
        countDownLatch.countDown();
    }
}
//创建一个妈妈类的线程
class Monther extends Thread{
    private CountDownLatch countDownLatch;
    public Monther(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.等待三个孩子吃饺子
        try {
            //当计数器=0,就唤醒在这里等待的线程
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //2.三个孩子吃完之后开始收拾碗筷
        System.out.println("妈妈正在收拾碗筷");
    }
}

3.Semaphore---访问特定资源线程数

使用场景:比如高速路关口发放指定数量通行证,每次只允许一定数量的线程(车)进入

import java.util.concurrent.Semaphore;

public class MySemaphoreDemo {
    public static void main(String[] args) {
        RunnableImpl_ rai = new RunnableImpl_();

        //创建100条线程(100辆车)
        for (int i = 0; i < 100; i++) {
            new Thread(rai).start();
        }
    }
}
//创建一个高速路关卡类
class RunnableImpl_ implements Runnable{
    //1.获得管理员对象,参数表示每次只允许 2 辆车进入高速路
    private Semaphore smp = new Semaphore(2);
    @Override
    public void run() {
        try {
            //2.获得通行证
            smp.acquire();
            //3.开始行驶
            System.out.println(Thread.currentThread().getName()+"开始行驶了");
            //设定形式时间
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+"下高速");
            //4.归还通行证
            smp.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 上面的输出结果:最多只有两条线程一起开始一起结束!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值