多线程学习笔记3

目录

线程状态:

 线程池

Executors创建线程池的方法:

 自定义线程池ThreadPoolExecutor 

线程池的拒绝策略

volatile关键字

synchronized同步代码块:

 原子性及原子操作类

AutomicInteger原理:自旋锁+CAS算法

incrementAndGet源码解析

synchronized和CAS的区别

CurrentHashMap

CountDownLatch:

Semaphore


线程状态:

虚拟机中线程的六种状态:

  • 新建状态(NEW)                                -------------> 创建线程对象
  • 就绪状态(RUNNABLE)                     -------------> start方法
  • 阻塞状态(BLOCKED)                        ------------->无法获得锁对象
  • 等待状态(WAITING)                          -------------> wait方法
  • 计时状态(TIMED_WAITING)             -------------> sleep方法
  • 结束状态(TERMINATED)                   -------------> 全部代码运行完毕

 在Thread类内部State枚举内部类中可以看到6中状态

 线程池

基本原理(更加的高效的使用多线程):

  1. 创建一个空的池子
  2. 有任务要执行时,才会创建线程对象,当任务执行完毕,线程对象归还给池子
  3. 所有的任务全部执行完毕,关闭连接池

创建一个池子用的是一个类Executors               -----------------------》 创建Executors中的静态方法

创建线程、执行、归还                                      ------------------------》submit方法

所有的任务全部执行完毕,关闭连接池             ------------------------》shutdown方法

Executors创建线程池的方法:

  • static ExecutorService newCachedThreadPool() 创建一个默认的线程池
     

public class MyTest {

    public static void main(String[] args) {
        //1、创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值。
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --可以帮助我哦们创建线程池对象
        //ExecutorService ---可以帮助我们控制线程池
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        try {
            //Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        executorService.shutdown();

    }

}
  • static newFixedThreadPool (int nThreads) 创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        //参数不是初始值 而是最大值
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"开始执行了");
        });
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"开始执行了");
        });
        executorService.shutdown();
    }
}

上面两个类的底层都是ThreadPoolExecutor实现的

 

 自定义线程池ThreadPoolExecutor 

所以自定义线程池对象需要创建ThreadPoolExecutor对象

  • 用员工,顾客 ---------类比---------->线程、任务

正式员工数量                                                ------->核心线程数

餐厅最大员工数                                            -------->线程池中最大线程的数量

临时员工空闲多长时间被辞退(值)            -------->空闲时间(值)

临时员工空闲多长时间被辞退(单位)        --------->空闲时间(单位)

排队的客户                                                  ---------->阻塞队列

从哪里招人                                                  ---------->创建线程的方式

当排队人数过多,超出顾客(拒绝服务)   ----------->要执行的任务过多时的解决方案

  • 创建线程池对象格式:

ThreadPoolExecutor threadPoolExecutor =  new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

参数一:核心线程数                        不能小于0

参数二:最大线程数量                      不能小于等于0,最大数量>=核心线程数量

参数三:空闲线程最大存活时间        不能小于0

参数四:时间单位                                时间单位 TimeUtil类中的

参数五:任务队列                               不能为null,任务在队列中等着,有空闲线程后获取并执行

参数六:创建线程工厂                          不能为null

参数七:任务的拒绝策略                        不能为null:当提交的任务>池中最大线程数量+队列容量

public class MyThreadPoolDemo3 {
    public static void main(String[] args) {
        //参数一:核心线程数  不能小于0
        //参数二:最大线程数量   不能小于等于0,最大数量>=核心线程数量
        //参数三:空闲线程最大存活时间    不能小于0
        //参数四:时间单位    时间单位 TimeUtil类中的
        //参数五:任务队列   不能为null
        //参数六:创建线程工厂  不能为null
        //参数七:任务的拒绝策略    不能为null
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,10,5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        threadPoolExecutor.submit(()->{
            System.out.println(Thread.currentThread().getName()+"执行了");
        });
        threadPoolExecutor.submit(()->{
            System.out.println(Thread.currentThread().getName()+"执行了");
        });
        threadPoolExecutor.shutdown();
    }
}

线程池的拒绝策略

  • ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。时默认的策略。
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但并不抛出异常,这是不推荐的做法
  • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务 然后把当前任务加入队列中
  • ThreadPoolExecutor.CallerRunsPolicy:调用run的方法,绕过线程池,直接执行

volatile关键字

作用:强制线程去看共享数据的最新值

public class Money {
    public static volatile int money=100000;

}

public class Thread1  extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Money.money=90000;
    }
}

public class Thread2 extends Thread{
    @Override
    public void run() {
        while (Money.money == 100000){

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

public class MyTest {
    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();

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

synchronized同步代码块:

作用:强制相处去看共享数据的最新值

  1. 线程获得锁
  2. 清空变量副本
  3. 拷贝共享变量最新的值到变量副本中
  4. 执行代码
  5. 将修改后变量副本中的值赋值给共享数据
  6. 释放锁
public class Money {
    public static int money=100000;
    public static final Object lock = new Object();
}


public class Thread1  extends Thread{
    @Override
    public void run() {
        synchronized(Money.lock){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Money.money=90000;
        }
    }
}

public class Thread2 extends Thread{
    @Override
    public void run() {
        synchronized (Money.lock){
            while (Money.money == 100000){

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


public class MyTest {
    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();

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

 原子性及原子操作类

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

volatile:只能保证线程每次在使用共享数据的时候是最新值。但是不能保证原子性。

AutomicInteger原子操作类。

构造方法1:AutomicInteger(); 初始值为0 

构造方法2:AtomicInteger(int value);用给定的初始值创建一个新的AtomicInteger

常用方法:

  • int get(); 获取值
  • int getAndIncrement();以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • int incrementAndGet();以原子方式将当前值加1,注意,这里返回的是自增后的值。
  • int addAndGet(int data);以原子方式将参数与对象中的值相加,并返回结果。
  • int getAndSet(int value);以原子方式设置为newValue的值,并返回旧值。
public class MyAtomThread implements Runnable {
    private  int count=0;
    AtomicInteger ac = new AtomicInteger();
    @Override
    public void run() {
            for (int i = 0; i < 100; i++) {
                count = ac.incrementAndGet();
                System.out.println("已经第了"+count+"次表白了");
            }

    }
}


public class MyTest {
    public static void main(String[] args) {
        MyAtomThread myAtomThread = new MyAtomThread();
        for (int i = 0; i < 1000; i++) {
            new Thread(myAtomThread).start();
        }
    }
}

AutomicInteger原理:自旋锁+CAS算法

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

  • 当旧的预期值A == 内存值  此时修改成功,将V改为B
  • 当旧的预期值A != 内存值  此时修改失败,不做任何操作
  • 并重新获取现在的最新值(这个重新获取的工作就是自旋)

incrementAndGet源码解析


//先自增,然后获取自增后的结果
public final int incrementAndGet() {
        //this指当前的atomicInteger(值)
        //
        //1 自增1次
        //+1 自增后的结果
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }





public final int getAndAddInt(Object var1, long var2, int var4) {
        //旧值
        int var5;
        //do-while 是自旋的过程
        do {
            //不断获取旧值
            var5 = this.getIntVolatile(var1, var2);
            //如果为true 结束自旋,如果为false继续自旋
            //比较内存中的值、旧值是否相等,如果相等就把修改后的值写到内存中,返回true 表示修改成功
            //                           如果不相等,无法把修改后的值写到内存,返回false,表示修改失败
            //如果修改失败,继续自旋
            //var1 = 内存值  ; var5 = 旧值  ; var5 + var4 = 修改后的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
            
        return var5;
    }

synchronized和CAS的区别

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

不同点:

  • synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每一次操作共享数据之前都会上锁。(悲观锁
  • cas是从乐观锁的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候会检查一下,别人有没有修改这个数据。如果别人修改过,那么我再次获取现在的最新值。如果别人没有修改过,那么我现在直接修改共享数据的值。(乐观锁

HashMap是线程不安全的,多线程情况下会存在线程不安全情况。

默认长度为16,加载因子为0.75,当数组长度达到12时,数组扩容为原来的2倍

Hashtable

  • 采用的是悲观锁synchronized的形式保证数据的安全性
  • 只要有线程访问,会将整张表锁起来,所以Hashtable的效率底下

CurrentHashMap

线程安全的,效率高

  • JDK1.7底层原理:

  1.  创建对象 :
    1. 默认创建一个长度为16,加载因子为0.75的大数组。这个大数组一旦创建无法扩容
    2. 还会创建一个长度为2的小数组,把地址值赋值给0索引处,其他索引位置的元素都是null
  2. 添加
    1. 如果为null,则按照模板创建小数组;创建完毕,会二次哈希,计算出在小数组中应存入的位置。直接存入
    2. 如果不为null,就会根据记录的地址值找到小数组。二次哈希,计算出在小数组中应存入的位置。
      1. 如果需要扩容,则将小数组扩容两倍
      2. 如果不需要扩容,就会判断小数组的这个位置有没有元素
        1. 如果没有元素,直接存入
        2. 如果有元素,会调用equals方法比较属性值
          1. 如果equals为true,则不存
          2. 如果为false,形成哈希桶结构

  • JDK1.8底层原理

底层结构:哈希表。(数组、链表、红黑树的结合体)

结合CAS机制+synchronized同步代码块形式保证线程安全。

  1. 如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。在第一次添加元素的时候创建哈希表
  2. 根据键计算当前元素应存入的索引
  3. 如果该索引位置为null,则利用cas算法,将本结点添加到数组中
  4. 如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
  5. 当链表的长度大于8时,自动转换成红黑树
  6. 以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。

CountDownLatch:

使用场景:让某一线程在等待其他线程执行完毕之后再执行

CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。

await():让线程等待,当计数器为0时,会唤醒等待的线程。

countDown():线程执行完毕时调用,会将计数器-1

Semaphore

Semaphore(int  permits):构造方法 最多允许permits个线程执行任务

acquire() :获取线程执行的许可证

release():释放许可证

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //创建一个信号管理员对象
        Semaphore sm = new Semaphore(2);
        try {
            //获取执行许可证,获取到了执行,获取不到阻塞
            sm.acquire();
            //获取许可证之后的执行逻辑
            System.out.println("获取到许可证,执行成功");

            //释放许可证
            sm.release();
            System.out.println("执行已完成,归还许可证");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}


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

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值