Java学习Day14------线程通信、线程池、原子性、锁

线程通信

(1)概述(通过图介绍)
在这里插入图片描述
(2)案例

//奶箱类(锁对象)
//1. 成员变量: 奶箱存入的奶的次数int milk, 奶箱的状态 boolean state = true/false;
//2. 构造方法: 无
//3. 成员方法: 存奶操作 void put(int milk)  取奶操作 void get()
public class Box {
    //成员变量
    private int milk;
    //刚开始的时候,奶箱里面没有奶
    private boolean state = false;
    //存奶的操作,synchronized 请问锁对象是谁? this -> Box 的对象
    public synchronized void put(int milk){
        //1. 判断状态,是否等待
        if (state == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //2. 存奶,输出结果
        this.milk = milk;
        System.out.println("生产者存奶,次数:"+milk);
        //3. 状态修改, 因为生产者已经存奶,状态修改为 true
        state = true;
        //4. 唤醒所有的线程,准备抢!
        this.notifyAll();
    }
    //取奶的操作 synchronized 请问锁对象是谁? this -> Box 的对象
    public synchronized void get(){
        //1. 判断是否需要进入等待状态
        if (state == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //2. 取奶
        System.out.println("消费者取奶,次数:"+milk);
        //3. 修改状态,奶已经被取走了,状态是 false
        state = false;
        //4. 唤醒所有线程,准备抢!
        this.notifyAll();
    }
}

//生产者
public class Producer implements Runnable{
    //需要传递共享锁对象! Box 对象唯一
    private Box box;
    //构造方法
    public Producer(Box box) {
        this.box = box;
    }
    @Override
    public void run() {
        //生产者生成 30次的奶
        for (int i = 1; i < 31; i++) {
            //调用方法
            box.put(i);
        }
    }
}
//消费者
public class Customer implements Runnable{
    //需要传递共享锁对象! Box 对象唯一
    private Box box;
    public Customer(Box box) {
        this.box = box;
    }
    @Override
    public void run() {
        //死循环不断的取奶
        while(true){
            box.get();
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        // (1). 创建唯一的奶箱对象(唯一的锁对象)
        Box box = new Box();
        // (2). 将唯一的锁对象,分配给生产者和消费者
        Thread t1 = new Thread(new Producer(box));
        Thread t2 = new Thread(new Customer(box));
        // (3). 启动线程
        t1.start();
        t2.start();
    }
}

(3)阻塞队列
  a) 创建对象

ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);   //参数:容量大小

  b) 存放数据

queue.put("Hello");

  c) 获取数据

String s = queue.take();

  d) 容量限制
    容量 >= put 数目 >= take 数目

线程生命周期

六种生命状态:
  (1)NEW 新建
  (2)RUNNABLE 可运行
  (3)BLOCKED 阻塞
  (4)WAITING 等待
  (5)TIMED_WAITING 计时等待
  (6)TERMINATED 销毁

线程池

(1)默认线程池
  a) 步骤:
    aa) 创建线程池对象
    bb) 提交线程任务
    cc) 销毁线程池
  b) 案例

//创建默认的线程池
public class Test01 {
    public static void main(String[] args) {
        // (1). 创建线程池对象
        ExecutorService service = Executors.newCachedThreadPool();
        // (2). 提交线程任务
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        // (3). 销毁线程池
        service.shutdown();
    }
}

(2)创建制定上限的线程池
  a) 步骤:
    aa) 创建线程池对象(参数:线程池的线程最大值)
    bb) 提交线程任务
    cc) 销毁线程池
  b) 案例

//创建指定上限线程数目的线程池
public class Test02 {
    public static void main(String[] args) {
        // (1). 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(3);
        // (2). 提交线程任务
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        service.submit(()-> System.out.println(Thread.currentThread().getName()+"正在执行任务"));
        // (3). 销毁线程池
        service.shutdown();
    }
}

(3)自定义线程池
  a) 步骤:
    aa) 创建线程池对象
    bb) 提交线程任务
    cc) 销毁线程池
  b) 案例

//创建线程池的对象(共计7个参数)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
        2,        //参数1: 核心线程数目,不能小于0
        5,       //参数2: 最大线程数目, 大于0, 最大数量>=核心线程数量
        2,       //参数3: 空闲线程最大存活时间, 不能小于0
        TimeUnit.SECONDS,    //参数4: 时间单位
        new ArrayBlockingQueue<>(10),    //参数5: 任务队列,不能为null
        Executors.defaultThreadFactory(),        //参数6: 创建线程工厂,不能为null
        new ThreadPoolExecutor.AbortPolicy()     //参数7: 任务的拒绝策略, 不能为null
);
//提交线程任务
pool.submit(()-> System.out.println(Thread.currentThread().getName()+"执行了"));
pool.submit(()-> System.out.println(Thread.currentThread().getName()+"执行了"));
pool.submit(()-> System.out.println(Thread.currentThread().getName()+"执行了"));

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

原子性

概述:一组操作当中, 要么同时成功, 要么同时失败。
(1)volatile 关键字
  a) 概述:强制要求,线程每次使用变量,都重新获取最新值
  b) volatile 能不能保证原子性:因为 volatile 关键字主要是用于获取最新数据。对于多个线程而言, 抢到了 共享数据之后,如果都没有进行 重新赋值回去的操作,共享数据当中还是最新的值。 现在多个线程同时对共享数据赋值,那么将无法保证原子性。
  c) 案例

//银行类
public class Bank {
    //定义静态共享数据
    public static int money = 10;
    //public static volatile int money = 10;
}
//男孩线程
public class Boy extends Thread{
    @Override
    public void run() {
        try {
            //先让男孩睡10毫秒
            Thread.sleep(10);
            //修改银行存款
            Bank.money = 9;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//女孩线程
public class Girl extends Thread{
    @Override
    public void run() {
        //循环判断是否是10万,是10万就继续
        while(Bank.money == 10){
        }
        System.out.println("结婚基金已经不是10万啦");
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Boy boy = new Boy();
        boy.start();
        Girl girl = new Girl();
        girl.start();
    }
}

(2)原子性案例演示(提出问题)

//线程类
public class XianCheng implements Runnable{
    //定义成员变量
    private int count;
    public XianCheng(int count) {
        this.count = count;
    }
    @Override
    public void run() {
        //循环打印输出
        for (int i = 0; i < 100; i++) {
            System.out.println("输出count的数据值:"+count);
            count++;  //在这里实际上是3个步骤
        }
    }
    //1. 获取共享数据交给 单独的子线程任务栈
    //2. 在子线程任务栈当中,自增
    //3. 将自增之后的结果,赋值给共享数据
}

//测试类
public class Test {
    public static void main(String[] args) {
        //开启100个线程
        XianCheng xc = new XianCheng(1);
        //循环100次,使用共享数据 xc
        for (int i = 0; i < 100; i++) {
            Thread tt = new Thread(xc);
            tt.start();
        }
    }
}

  问题分析:自增操作,不是原子的,分为了三个步骤,所以在执行这三个步骤的时候,CPU资源很可能被其他线程抢占,如果其他线程访问了该资源,就很可能出现错误
在这里插入图片描述

(3)原子类 AtomicInteger
  a) 构造方法

方法备注
public AtomicInteger()初始化值为0的原子类型 Integer
public AtomicInteger(int initialValue)给出一个具体初始值的,原子类型Integer

  b) 常用方法

方法备注
int get()获取值
int getAndIncrement()将当前的值,自增加1. 返回的是 之前的数据值
int incrementAndGet()将当前的值,自增加1. 返回的是 自增之后的数据值
int addAndGet(int data)将当前参数当中的值, 与构造方法当中的值, 相加,返回的相加之后的和
int getAndSet(int value)先获取原来构造方法当中的值, 然后将参数当中的值设置进去, 最终返回的是之前构造方法的值

  c) 内存原理(自旋锁+CAS算法 CAS:内存值,旧的预期值A,要修改的值B)
    aa) 当旧的预期值A == 内存值 //此时修改成功, 将V改为B —> 如果旧值和内存值相等,表示当前的数据值,没有被其他线程操作过。
    bb) 当旧的预期值A != 内存值 //此时修改失败, 不做任何操作 --> 如果旧值和内存值不相等,表示当前的数据值,已经被其他线程操作过。【这里就需要重新获取(自旋)】
    cc) 并重新获取现在的最新值 (这个重新获取的动作,就是自旋
在这里插入图片描述

悲观锁和乐观锁

synchroized 和 CAS 的区别
(1)相同点:
  都可以在多线程的情况下, 保证共享数据的安全性,原子性。
(2)不同点:
  A. 悲观锁(synchroized) 总是从最坏角度思考, 所有的人都有可能改变原始数据,每次使用,都会上锁。 //不管有没有改变数据,统一上锁。
  B. 乐观锁(CAS) 从乐观的角度出发, 假设别人使用不会修改,不会上锁。 只有共享数据改变的情况下,会检查数据,进行修改。 //会对数据进行检查,是否有变化

并发工具类

(1)Hashtable
  a) Hashtable和HashMap的区别
    aa) 安全性:Hashtable安全性更高,HashMap安全性低
    bb) 效率:HashMap效率高,Hashtable效率低
    原因:Hashtable底层是同步锁,HashMap底层没有上锁
(2)ConcurrentHashMap
  a) 概述:既保证了安全性,也保证了访问的高效
  b) 底层原理(为什么能实现安全又高效)
    JDK1.7:底层是一个长度16的数组(不可以扩容),数组中每个元素存放了一个小数组地址。在线程访问的时候,只锁住1/16的小数组,也就是说,最多允许有16个线程同时访问
在这里插入图片描述
    JDK1.8:底层采用的是数组+链表+红黑树的结构存储。在线程操作的过程中,采用同步锁synchronized去锁住数组的一个元素(元素中包含的是链表或者红黑树),结合CAS算法(乐观锁)和悲观锁保证线程安全。(可以扩容,初始大小为16,后续扩容之后可以允许超过16个线程访问)
(3)CountDownLatch
  a) 概述:让其中的一条线程, 等待其他所有线程执行完毕之后,再去执行。(其他线程执行完毕之后,它再执行)
  b) 常用方法

方法备注
CountDownLatch(数字)构造方法。创建一个需要等待n条线程的示例(参数的数字表示需要等待的线程数)
await()等待方法
countDown()减少方法

  c) 案例

//下载线程
public class XiaZaiBa implements Runnable{
    private int start;
    private int end;
    private CountDownLatch latch;
    public XiaZaiBa(int start, int end, CountDownLatch latch) {
        this.start = start;
        this.end = end;
        this.latch = latch;
    }
    @Override
    public void run() {
        for (int i = start; i <= end; i++) {
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在下载:"+i);
        }
        //当整个线程下载完毕之后,需要减少。
        latch.countDown();
    }
}

//展示结果的线程
public class ShowResult implements Runnable {
    private CountDownLatch latch;
    public ShowResult(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("恭喜你,下载完毕,快去看看吧");
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        //需要创建 CountDownLatch 对象
        CountDownLatch latch = new CountDownLatch(3);
        //创建3个下载线程的对象
        XiaZaiBa xzb1 = new XiaZaiBa(1,30,latch);
        XiaZaiBa xzb2 = new XiaZaiBa(31,60,latch);
        XiaZaiBa xzb3 = new XiaZaiBa(61,100,latch);
        //创建最终的线程
        ShowResult show = new ShowResult(latch);
        // --------
        //启动线程
        Thread t1 = new Thread(xzb1);
        Thread t2 = new Thread(xzb2);
        Thread t3 = new Thread(xzb3);
        Thread t4 = new Thread(show);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

(4)Semaphore
  a) 概述:可以控制特定资源的线程访问数量。 (交通道路上面,控制一次有多少量车通过)
  b) 常用方法

方法备注
Semaphore(数字)构造方法,每次运行n个线程,参数中的数字代表运行的线程数量(最多允许发放几个令牌)
acquire()获取令牌
release()归还令牌

  c) 案例

//线程
public class MyThread implements Runnable{
    private Semaphore sema;
    public MyThread(Semaphore sema) {
        this.sema = sema;
    }
    @Override
    public void run() {
        try {
            //获取通行证
            sema.acquire();
            String name = Thread.currentThread().getName();
            System.out.println(name + "拿到了通行证");
            Thread.sleep(50);
            System.out.println(name + "归还通行证");
            //归还通行证
            sema.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        //最多允许3条线程
        Semaphore sema = new Semaphore(3);
        MyThread mt = new MyThread(sema);
        //开启多条线程执行
        for (int i = 1; i < 101; i++) {
            Thread t = new Thread(mt);
            t.setName("线程-"+i);
            t.start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值