入门到入土,Java学习 day20(多线程下)

void wait() 当前线程等待,直到被其他线程唤醒

void notify() 随机唤醒单个线程

void notifyAll() 唤醒所有线程

阻塞队列

在测试方法中创建带锁队列,然后在对象类中也创建队列但是不赋值,用构造方法将测试方法中的对象赋值

然后用put和take方法实现,这两个方法中自动上锁不需要再上锁,直接使用即可

线程状态

新增,就绪,阻塞,等待,计时等待,结束

线程池

之前的多线程用的时候就创建,用完就消除,浪费资源

核心原理

创建一个池子,里面是空的

提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

代码实现

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池

public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池

自定义线程池(有7个参数,核心线程数,线程池中最大线程的数量,空闲时间值,空闲时间单位,阻塞队列,创建线程的方式,拒绝解决方案)

当核心线程满了的时候,再提交任务就会排队

当核心线程满了,队伍满了,会创建临时线程

都满了,出发任务拒绝策略

ThreadPoolExecutor pool = new ThreadPoolExecutor(

corePoolSize:3,/核心线程数量,能小于0

maximumPoolSize:6,/最大线程数,不能小于0,最大数量>=核心线程数量

keepAliveTime:60,/空闲线程最大存活时间

TimeUnit.SECONDS,/时间单位

new ArrayBlockingQueue<>( capacity: 3),

Executors.defaultThreadFactory(),

new ThreadPoolExecutor. AbortPolicy()

);

任务拒绝策略

ThreadPoolExecutor.AbortPolicy 默认策略:丢弃任务并抛出RejectedExecutionException异常

ThreadPoolExecutor. DiscardPolicy 丢弃任务,但是不抛出异常这是不推荐的做法

ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务然后把当前任务加入队列中

ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行

线程池多大合适呢

CPU密集型,最大并行数+1

I/O密集型,最大并行数*期望CPU利用率*(总时间(CPU计算时间+等待时间)/CPU计算机时间)里面的时间用thread dump记录获取

最大并行数,一般看电脑处理器时多少线程的,然后还是调用Runtime.getRuntime.availableProcessors获取

练习

抢红包

假设:100块,分成了3个包,现在有5个人去抢。

其中,红包是共享数据。

5个人是5条线程。

public class MyThread extends Thread{

    //共享数据
    //100块,分成了3个包
    static double money = 100;
    static int count = 3;

    //最小的中奖金额
    static final double MIN = 0.01;

    @Override
    public void run() {
        //同步代码块
        synchronized (MyThread.class){
            if(count == 0){
                //判断,共享数据是否到了末尾(已经到末尾)
                System.out.println(getName() + "没有抢到红包!");
            }else{
                //判断,共享数据是否到了末尾(没有到末尾)
                //定义一个变量,表示中奖的金额
                double prize = 0;
                if(count == 1){
                    //表示此时是最后一个红包
                    //就无需随机,剩余所有的钱都是中奖金额
                    prize = money;
                }else{
                    //表示第一次,第二次(随机)
                    Random r = new Random();
                    //100 元   3个包
                    //第一个红包:99.98
                    //100 - (3-1) * 0.01
                    double bounds = money - (count - 1) * MIN;
                    prize = r.nextDouble(bounds);
                    if(prize &lt; MIN){
                        prize = MIN;
                    }
                }
                //从money当中,去掉当前中奖的金额
                money = money - prize;
                //红包的个数-1
                count--;
                //本次红包的信息进行打印
                System.out.println(getName() + "抢到了" + prize + "元");
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {

        //创建线程的对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        MyThread t5 = new MyThread();

        //给线程设置名字
        t1.setName("a");
        t2.setName("b");
        t3.setName("c");
        t4.setName("d");
        t5.setName("e");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

volatile解决

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题

1,堆内存是唯一的,每一个线程都有自己的线程栈。

2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。

3 ,在线程中,每一次使用是从变量的副本中获取的。

Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值

synchronized解决

1 ,线程获得锁

2 ,清空变量副本

3 ,拷贝共享变量最新的值到变量副本中

4 ,执行代码

5 ,将修改后变量副本中的值赋值给共享数据

6 ,释放锁

原子性

所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

volatile关键字不能保证原子性,要加锁

原子性_AtomicInteger

java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。

使用原子的方式更新基本类型:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

AtomicInteger原理 : 自旋锁 + CAS 算法

CAS算法:

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

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

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

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

悲观锁和乐观锁

synchronized和CAS的区别 :

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

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

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

如果别人修改过,那么我再次获取现在最新的值。

如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

并发工具类

Hashtable

Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

ConcurrentHashMap基本使用

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。

原理:

1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表

2,计算当前元素应存入的索引。

3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。

4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。

5,当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性

总结:

1 ,HashMap是线程不安全的。多线程环境下会有数据安全问题

2 ,Hashtable是线程安全的,但是会将整张表锁起来,效率低下

3,ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。

CountDownLatch类 :

public CountDownLatch(int count)参数传递线程数,表示等待线程数量

public void await()让线程等待

public void countDown()当前线程执行完毕

Semaphore

可以控制访问特定资源的线程数量。

1,需要有人管理这个通道

2,当有车进来了,发通行许可证

3,当车出去了,收回通行许可证

4,如果通行许可证发完了,那么其他车辆只能等着

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值