JAVASE23天从入门到精通_Day20

锁对象的功能
锁对象 : 锁对象可以控制线程!保证线程执行的安全性!
    特点: 1. 被所有的线程对象共享 2. 锁对象必须是引用数据类型
    
锁对象的功能来自于Object:
	唤醒线程的方法:
		void notify(): 随机唤醒一个正在沉睡/等待状态的线程
		void notifyAll() : 唤醒所有正在沉睡/等待状态的线程
		
	让线程等待的方法:
		void wait() :让线程永久等待 -> 无限等待
		void wait(long timeout)  : 让线程在一定时间内等待 -> 限时等待
		
1. 多线程代码没有同步,没有锁 : 线程只需要抢到CPU执行权就可以执行
2. 多线程代码有同步,有锁 : 线程需要同时抢到CPU执行权和锁资源才能执行

wait和sleep的区别
	sleep : 持有锁进行休眠,只会释放CPU资源 -> 抱着锁睡!
    wait : 同时释放锁资源和CPU执行权    
线程的生命周期
新建 NEW : 至今尚未启动的线程处于这种状态。 -> new过了,但是没有调用start方法
运行 RUNNABLE : 正在 Java 虚拟机中执行的线程处于这种状态
死亡 TERMINATED : 已退出的线程处于这种状态
阻塞 BLOCKED : 线程没有锁资源或者没有CPU资源的状态 -> 醒着
限时等待 TIMED_WAITING : 线程属于昏迷状态(没有资格抢夺CPU资源或者锁资源),时间到自己醒
    被调用 wait(long timeout) 或者 sleep(long millis)
无限等待 WAITING :线程属于昏迷状态(没有资格抢夺CPU资源或者锁资源)
	被调用 void wait() 
等待唤醒案例
等待唤醒案例 : 线程的通讯,生产者和消费者案例

厨师 : 线程
顾客 : 线程
包子 : 可以控制线程(它的状态决定了线程的执行状态) -->,控制线程的对象,增加程序的趣味性
线程池
线程池 装线程的池子 

线程池对象的类型 : ThreadPoolExecutor 
线程池的创建方式一: 面向对象的方式
工具类: Executors
	创建线程池对象的方法:
		static ExecutorService newCachedThreadPool()  : 创建不指定最大线程数量的线程池对象(底层也是由最大线程数量的: 21个亿多)
        static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最大线程数量的线程池对象
        
    向线程池提交任务:
		 Future<?> submit(Runnable task) : 向线程池提交一个不带任务结果的线程任务
 		<T> Future<T> submit(Callable<T> task) : 向线程池提交一个带任务结果的线程任务 
 		<T> Future<T> submit(Runnable task, T result) 向线程池提交一个不带任务结果的线程任务,手动编写结果 (T result)
线程池的创建方式二: 面向过程的方式
ThreadPoolExecutor : 线程池对象的类

面向过程的方式 : 手动创建ThreadPoolExecutor对象
	ThreadPoolExecutor(int corePoolSize, //核心线程数量
                       int maximumPoolSize, //总线程数量
                       long keepAliveTime, //临时线程的存活时间值
                       TimeUnit unit, //临时线程的存活时间单位
                       BlockingQueue<Runnable> workQueue, //阻塞队列
                       ThreadFactory threadFactory, //线程工厂
                       RejectedExecutionHandler handler) //等待拒绝策略

在这里插入图片描述

TimeUnit unit
TimeUnit unit : 时间单位的枚举类

枚举类的使用 : 创建枚举对象的方式 -> 枚举的类名.对象名;
DAYS 
            
HOURS 
            
MICROSECONDS 
            
MILLISECONDS 
            
MINUTES 
            
NANOSECONDS 
            
SECONDS  

例如: TimeUnit.SECONDS -> 创建此类的对象
BlockingQueue workQueue
BlockingQueue<E> : 单列集合,继承了Collection<E> -> 自己也是接口

使用其具体的实现类:ArrayBlockingQueue<E>

构造方法:
	ArrayBlockingQueue(int capacity) 
        int capacity: 阻塞队列中最多的元素数量
        
增删改查四类功能: 参考Collection    

跟队列相关的方法:
	进队列 : void put(E e) 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。 
	出队列 : E take() 获取并移除此队列的头部,如果队列中已经没有元素了,如果继续take那就等。 
ThreadFactory threadFactory
ThreadFactory threadFactory 线程工厂 -> 人才市场

借助工具类型 : Executors
	static ThreadFactory defaultThreadFactory() 返回用于创建新线程的默认线程工厂。 
RejectedExecutionHandler handler
RejectedExecutionHandler handler : 等待拒绝策略

RejectedExecutionHandler 接口,不能创对象 --> 需要它的实现类对象

RejectedExecutionHandler 的实现类对象是 ThreadPoolExecutor 的静态内部类对象!
    
    静态内部类对象如果创建 : 
        class Outer{
            static class Inner{

            }
        }
	Outer.Inner inner = new Outer.Inner();
    
分类 : 
	static class ThreadPoolExecutor.AbortPolicy  : 默认方法和推荐方案 -> 直接拒绝并报错!
        //抛出的异常是 : RejectedExecutionException
		new ThreadPoolExecutor.AbortPolicy();
	static class ThreadPoolExecutor.DiscardPolicy  : 不推荐 -> 直接拒绝
		new ThreadPoolExecutor.DiscardPolicy();
	static class ThreadPoolExecutor.DiscardOldestPolicy :随机移除阻塞队列中的某个元素,把不能服务的等待最久的元素添加到阻塞队列中
		new ThreadPoolExecutor.DiscardOldestPolicy();
	static class ThreadPoolExecutor.CallerRunsPolicy : 找其他的线程帮助服务不能服务的线程任务
		new ThreadPoolExecutor.CallerRunsPolicy();
volatile关键字
案例:
//事物描述类
class Money {
    public static int money = 100000;
    //此变量被标记为不稳定的变量, 线程优先去内存中寻找值
    //public static volatile int money = 100000;
}

//线程1: 小红
class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(Money.money == 100000){

        }

        System.out.println("结婚基金已经不是十万了");
    }
}

//线程2: 小刚
class MyThread2 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Money.money = 90000;
    }
}

volatile 关键字 : 不稳定的,易变的
	被其修饰的变量,会强制要求线程对象去内存中找真实值,而不是去线程栈中找临时变量值!!
    
解决方案1 :Money变量前面加volatile关键字 
解决方案2 : 上锁,同步操作!
    上锁的特点: 一个线程操作共享数据的时候,不让其他的线程对象去使用. 线程对象就会有足够的时间去内存中找共享数据的真实值;(优先去内存中找)

public class VolatileDemo {
    public static void main(String[] args) {
        //创建线程对象
        MyThread1 小红 = new MyThread1();
        MyThread2 小刚 = new MyThread2();

        小红.start();
        小刚.start();
    }
}
class Money {
    //新new的锁对象
    public static Object lock = new Object();
    //共享数据
    public static int money = 100000;
}

//小红
class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(true){
            //上锁: 线程访问共享数据的时候优先访问内存中的共享数据而不是临时变量区
            synchronized (Money.lock){
                if(Money.money != 100000){
                    System.out.println("结婚基金已经不是十万了");
                    break;
                }
            }
        }
    }
}

//小刚
class MyThread2 extends Thread {
    @Override
    public void run() {
        synchronized (Money.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			//原子性操作
            Money.money = 90000;
        }
    }
}
原子性和非原子性
原子性操作 : 不可以分割的操作 -> 原子性操作
	//一句代码要么不做,要做就一定是做完的
	例如 : 赋值操作  int a = 10;

非原子性操作 : 可以分割的操作
	//一句代码可能需要多个步骤完成,每个步骤之间有"时间"间隔
	例如: 自增自减 a++; -> 1. a + 1 2. a = a+1;
	例如: 拓展的赋值运算符 a += 10; 1. a + 10 2. a = a + 10;

volatile 解决不了非原子性操作的线程安全问题! --> volatile只能解决原子性操作的线程安全问题
volatile关键字不能解决非原子性操作的安全问题
同步代码块解决非原子性操作的线程安全问题
package com.atguigu.d_atom;

public class AtomDemo1 {
    public static void main(String[] args) {
        Target1 target = new Target1();
        for (int i = 0; i < 100; i++) {
            new Thread(target).start();
        }
    }
}


class Target1 implements Runnable{
    private int count = 0; //送冰淇淋的数量
    private Object obj = new Object();

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            //解决方案1: 对原子性的操作进行上锁
            synchronized (obj){
                count++;//非原子性的操作
                System.out.println("已经送了" + count + "个冰淇淋");
            }
        }
    }
}
原子性类_AtomicXxxxx
概述:Java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。(CAS算法 + 自旋)
    因为变量(共享数据)的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。
  
本次我们只讲解使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:  
  1. AtomicBoolean:原子更新布尔类型
  2. AtomicInteger:原子更新整型
  3. AtomicLong:原子更新长整型
  
AtomicInteger原理 : 自旋锁  + CAS 算法

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

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

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

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

在这里插入图片描述

AtomicInteger:原子更新整型
//构造方法:
public AtomicInteger():初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):初始化一个指定值的原子型Integer

  
//成员方法:
int get(): 获取值
int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。 b=a++; //b是返回值
int incrementAndGet():以原子方式将当前值加1,注意,这里返回的是自增后的值。 b=++a;//b是返回值
int addAndGet(int data):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。  //a += data;
int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值。
通过具备原子性的类解决非原子性操作的线程安全问题
package com.atguigu.d_atom;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomDemo2 {
    public static void main(String[] args) {
        Target2 target = new Target2();
        for (int i = 0; i < 100; i++) {
            new Thread(target).start();
        }
    }
}
//使用原子性的类来解决非原子性操作的线程安全问题
class Target2 implements Runnable{
    //默认值是0
    private AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            //count++;//非原子性的操作

            count.incrementAndGet();

            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}
悲观锁和乐观锁
悲观锁 : 同步操作就是 悲观锁
	同步: 当一个线程操作的时候,把所有其他线程拦在外面,当前线程不操作完不允许其他线程干扰! -> 效率低
	
乐观锁 : CAS算法 + 自旋
	CAS算法 + 自旋 : 不对共享数据上锁,而是保留旧的预期值,每次修改内存值之前去拿最新的内存值和预期值进行比较
		相同 : 要修改的值赋值给内存值
		不相同 : 重新获取最新内存值给预期值,再次做修改
总结线程安全问题
多个线程操作共享数据,极有可能产生线程安全的问题!!
    
解决方案:
	1. 一定能解决的方案 : 同步操作-> 上锁 --> 悲观锁
	2. 如果是原子性操作的线程安全问题 : volatile 关键字
	3. 如果是非原子性操作的线程安全问题 :Atomic包下的原子性的类(CAS算法和自旋)
死锁

在这里插入图片描述

//准备2把锁
        Object objA = new Object();
        Object objB = new Object();

        //准备2个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    synchronized (objA){//小康遇到的第一把锁
                        synchronized (objB){//小康遇到的第二把锁
                            System.out.println("紫薇 不要走~");
                        }
                    }
                }
            }
        }).start();//小康


        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    synchronized (objB){//小薇遇到的第一把锁
                        synchronized (objA){//小薇遇到的第二把锁
                            System.out.println("尔康 我不想留~");
                        }
                    }
                }
            }
        }).start();//小薇
Hashtable和Vector
Vector 线程安全的元素可重复,元素有索引,元素存取有序的单列集合实现
	如何保证线程安全呢? --> 同步方法
	
Hashtable  线程安全的双列集合实现
	如何保证线程安全呢? --> 同步方法
ConcurrentHashMap<K,V>
ConcurrentHashMap<K,V> 替换了 Hashtable<K,V>, 原因是ConcurrentHashMap<K,V>效率更高!
    Hashtable同步操作是把整个底层的哈希表都锁起来,一旦有线程操作集合就不允许其他线程操作集合
    ConcurrentHashMap<K,V>同步操作是只锁住当前被线程操作的hash表单个索引位置,只要其他的线程不操作被锁起来的hash表位置,还是可以操作集合
    
ConcurrentHashMap<K,V> 底层结构在JDK7和JDK8中不同体现在Hash表数据结构的实现方式不同:
	1. JDK7 : 数组加链表
	2. JDK8 : 数组加链表加红黑树
	JDK7和JDK8的线程安全方式是一致的!!

在这里插入图片描述

CountDownLatch
CountDownLatch : 实现让一个线程执行的前提条件是,其他线程都执行完毕后再执行

CountDownLatch 实现原理: 计数器

构造方法:
	CountDownLatch(int count) 
        	//int count : 需要等待的线程数量
        
成员方法:
	void await() : 休眠方法 -> 等到计数器归零后自己醒来
	void countDown()  : 计数器-1的操作
      
      
案例:
	package com.atguigu.h_countdownlatch;

import java.util.concurrent.CountDownLatch;

public class Demo {
    public static void main(String[] args) {
        //创建一个计数器对象
        CountDownLatch latch = new CountDownLatch(3);
        //创建线程对象
        ChildThread1 t1 = new ChildThread1(latch,"大娃");
        ChildThread2 t2 = new ChildThread2(latch,"二娃");
        ChildThread3 t3 = new ChildThread3(latch,"三娃");
        MotherThread motherThread = new MotherThread(latch,"蛇精");

        motherThread.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

//孩子线程1
class ChildThread1 extends Thread {

    private CountDownLatch countDownLatch;

    public ChildThread1(CountDownLatch countDownLatch,String name) {
        super(name);
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}

//孩子线程2
class ChildThread2 extends Thread {

    private CountDownLatch countDownLatch;

    public ChildThread2(CountDownLatch countDownLatch,String name) {
        super(name);
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}

//孩子线程3
class ChildThread3 extends Thread {

    private CountDownLatch countDownLatch;

    public ChildThread3(CountDownLatch countDownLatch,String name) {
        super(name);
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}

//妈妈线程
class MotherThread extends Thread {
    private CountDownLatch countDownLatch;

    public MotherThread(CountDownLatch countDownLatch,String name) {
        super(name);
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("妈妈在收拾碗筷");
    }
}
Semaphore
Semaphore 通行证的类
	通行证 给线程对象颁发,获取到通行证的线程对象就可以执行,否则就不可以执行!
    
构造方法:
	Semaphore(int permits) : 
		int permits : 通行证数量
		
成员方法:
	void acquire()  : 获取通行证的方法
	void release()  : 释放通行证的方法

        super(name);
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("妈妈在收拾碗筷");
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值