JAVAEE-多线程(4)

目录

定时器

实现自己的Timer

线程池

常见的锁策略:

乐观锁和悲观锁 

读写锁和普通互斥锁

重量级锁和轻量锁

自旋锁和挂起等待锁

公平锁和非公平锁

可重入锁和不可重入锁

synchronized

CAS

CAS和ABA问题

锁粗化

 JUC

原子类

Semaphore

CountDownLatch

多线程使用队列和哈希表

ConcurrentHashMap


定时器

package threading;

import java.util.Timer;
import java.util.TimerTask;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-10
 * Time: 19:04
 */
public class L110 {
    public static void main(String[] args) {
        Timer timer =new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到,快起床");
            }
        },3000);
        System.out.println("开始计时");

        //第一个参数为安排的任务是啥(new timertask),第二个是多长时间之后来执行(3000)
        //timertask就是一个runnable,我们要做的即使继承timertask然后重写run方法,从而执行指定任务。
    }
}

然后输出结果就是,先打印开始计时,然后三秒后打印出时间到快起床

 

同时我们也可以用多组定时器来计时

package threading;

import java.util.Timer;
import java.util.TimerTask;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-10
 * Time: 19:04
 */
public class L110 {
    public static void main(String[] args) {
        Timer timer =new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到,快起床");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到2,快起床");
            }
        },4000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到3,快起床");
            }
        },5000);
        System.out.println("开始计时");

        //第一个参数为安排的任务是啥(new timertask),第二个是多长时间之后来执行(3000)单位为毫秒
        //timertask就是一个runnable,我们要做的即使继承timertask然后重写run方法,从而执行指定任务。
    }
}

这里的进程没有退出是因为timer内部需要有一组线程来执行注册的任务,而这里的线程是前台线程,会影响进程退出。 

实现自己的Timer

 

package threading;

import java.util.PriorityQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 9:34
 */
class  Mytask implements Comparable<Mytask>{
    private Runnable runnable;
    //要执行什么任务

    private long time;
    //什么时间执行,是一个时间戳

    public Mytask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time=System.currentTimeMillis() + delay;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(Mytask o) {
        return (int) (this.time -o.time);
    }
}
class Mytimer{
    private BlockingQueue<Mytask> queue = new PriorityBlockingQueue<>();
    //使用阻塞队列来执行多个任务,因为优先级队列不是线程安全的,使用不使用

    private Object locker = new Object();
    public Mytimer(){
        //创建一个扫描线程,让这个线程不停的来检查队首元素,如果时间到了就执行任务
        Thread t = new Thread(()->{
            while(true){
                try {
                    synchronized (locker){
                        //检查首元素
                        Mytask task = queue.take();
                        //因为不管是设置时间刚好到系统时间,还是
                        //设置时间已经小于系统时间,都需要执行任务

                        long curtime = System.currentTimeMillis();
                        if(curtime>= task.getTime()){
                            //如果系统时间大于或者等于设置时间,就执行任务
                            task.getRunnable().run();
                        }
                        else {
                            //还没到点
                            queue.put(task);
                            //没到点就进行等待

                            locker.wait(task.getTime() - curtime);

                        }
                    }

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
    public void schedule(Runnable runnable,long after) throws InterruptedException {
        Mytask mytask = new Mytask(runnable,after);
        queue.put(mytask);
        synchronized (locker){
            locker.notify();
        }
    }
}
public class L111 {
    public static void main(String[] args) throws InterruptedException {
        Mytimer timer = new Mytimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到 ");
            }
        },3000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到2 ");
            }
        },4000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到3 ");
            }
        },5000);
        System.out.println("开始计时");
    }
}

线程池

package threading;

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 10:37
 */
public class L1111 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        //创建线程池,并没有显示的new去创建,而是通过另外executors类的静态方法newCachedThreadPool来完成的(工厂模式的工厂方法创建的)
        pool.submit(new Runnable() {
            //使用submit方法把任务提交到线程池中,线程池里面就会有一些线程来完成这里的任务2
            @Override
            public void run() {
                System.out.println("任务");
            }
        });
    }
}

执行多个任的线程池:

package threading;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 10:51
 */
class MyThreadPool{
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    public void  submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public  MyThreadPool(int m){
        for (int i = 0; i < m; i++) {
            Thread t = new Thread(()->{
               while(true){
                   try {
                       Runnable runnable = queue.take();
                       runnable.run();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            });
        }
    }
}

public class L1112 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int taskid = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务; "+ taskid + "当前线程:" + Thread.currentThread().getName());
                }
            });
        }
    }
}

 线程池,标准库提供了一个ThreadPoolExecutor类

corePoolSize
核心线程,空闲了也不会销毁

maximunPoolSize
非核心线程,空闲了就销毁了

//这个设定是为了防止线程池空闲的时候,非核心线程数目太多

keepAliveTime
运行非核心线程空闲的最大时间上限

unit
时间的单位

workQueue
手动给线程池传入一个任务队列

//本来线程池有自己的任务队列,如果我们不传会自己创建的
//这个时候把这个队列的任务拷贝到线程池就多余,只要使用线程池本身有的队列就可以

threadFactory
描述了线程是怎么创建的,工厂对象就负责创建线程,程序员还可以手动指定线程的创建策略

RejectedExecutionHandler handler
线程池的拒绝策略,线程池的任务队列已经满了(工作线程忙不过来了) 但是还在继续添加新任务该怎么办

详细介绍

RejectedExecutionHandler handler

RejectedExecutionHandler handler又有:

ThreadPoolExecutor.AbortPolicy
通过抛出异常来拒绝处理任务

ThreadPoolExecutor.CallRunsPolicy
拒绝任务,直接让任务由调用线程执行,如果调用线程执行不了则直接丢弃

ThreadPoolExecutor.DiscardOledsPolicy
放缓一个最先安排的任务,先处理最后安排的任务

ThreadPoolExecutor.DiscardPolicy
放缓一个最后安排的任务,先处理最先安排的任务

tip:

实际开发中,线程池的线程数目怎么确定,设定成几合适

这里不能给出一个具体的个数,因为主机cpu的配置不确定,程序的执行特点(也就是代码里面具体干了啥)不确定,不确定代码是cpu密集型(做大量的算术运算,逻辑及判断)还是io密集型(做了大量的读写网卡,读写硬盘)

因此需要我们实际去针对程序进行性能测试,给线程池设置成不同的数目,记录每种情况下程序的核心指标和系统负载情况,最终选择一个合适的配置。

常见的锁策略:

乐观锁和悲观锁 

乐观锁认为每次拿数据认为这个数据是未经修改的,因此不会加锁,可以用CAS算法来实现

悲观锁认为每次拿取数据这个数据都是被修改之后的,因此次次加锁,别人想取这个数据就会产生阻塞。

乐观锁干的事情少,悲观锁干的事情多(多在过程上看)

读写锁和普通互斥锁

普通的互斥锁,和sychronized就毫无区别,两个线程竞争一把锁的时候就会产生等待

读写锁分为加读锁和加写锁,

读锁和读锁不会有竞争,

读锁和写锁 以及 写锁和写锁之间有竞争

因为读的场景往往很多,写的场景往往恒少,因此读写锁相对于普通的互斥锁就少了很多锁竞争,优化了效率

重量级锁和轻量锁

多在结果上看,最终加锁解锁消耗的时间是多还是少

重量级锁就是加锁开销比较大 例如进入内核的加锁逻辑

轻量级锁就是加锁开销比较小 例如纯用户态的加锁逻辑

自旋锁和挂起等待锁

自旋锁是轻量级锁的一种典型实现

挂起等待锁是重量级锁的一种典型实现

自旋锁需要消耗大量的cpu反复询问当前锁是否就绪,但是获取锁很及时,所以消耗时间相对短

挂起等待锁获取锁没有那么及时,因此消耗时间更长

公平锁和非公平锁

公平锁是遵循先来后到,考虑锁竞争顺序,有序的竞争一个锁

非公平锁是不遵循先来后到,不考虑锁竞争顺序,全都可以站在同一起跑线取竞争一个锁

操作系统默认的锁的调度就是非公平的情况

想要实现公平锁,需要引入额外的数据结构来记录线程加锁的顺序,需要一定的额外开销

可重入锁和不可重入锁

可重入锁就是针对一个线程连续加锁两次不会死锁

不可重入锁就是针对一个线程连续加锁两次会死锁

synchronized

是乐观锁,也是悲观锁,是轻量级锁,也是重量级锁,乐观锁的部分是基于自旋锁实现的,悲观锁的部分是基于挂起等待锁实现的,是自适应的,初始使用是乐观锁,轻量级锁,自旋锁。

不是读写锁,是普通互斥锁,是非公平锁,是可重入锁

还是偏向锁,必要的时候就加锁,能不加就不加,偏向锁只是设置了一个状态

如果锁竞争激烈了,synchronized就会自动适应,切换成不同的锁

自适应是JVM在实现synchronized的时候给我们提供的自动优化的策略

synchronized效果就是加锁,两个线程针对同一个对象加锁的时候就会出现锁竞争,后来尝试加锁的线程就会阻塞等待,直到前一个线程释放锁

synchronized还会锁消除,由JVM自动判定,发现这个地方代码不需要加锁,就会自动把锁给去掉。比如有多个线程,但是多个线程不涉及修改一个变量,如果代码也写了synchronized这个时候JVM就会直接干掉这个加锁操作

锁消除也是一种编译器优化的行为

CAS

就是compare and swap

比较并交换,如果内存中某个值和cpu寄存器A中的值进行比较,如果两个值相同就把 另一个寄存器B中的值和内存值进行交换,(把内存的值放到寄存器B中,同时把寄存器B的值写给内存)如果不相同那么无事发生(不关心交换之后寄存器B的值,更关心交换之后内存的值)

上述是CPU的一条指令,原子完成的,线程是安全的

CAS只能在特定的场景用,加锁面更广

最常用的两个场景是,实现原子类和实现自旋锁

实现原子类就例如,count++在多线程环境下不安全,要安全就需要牺牲性能取加锁,我们就可以基于CAS操作来实现原子的,从而来保证线程的安全和高效

实现自旋锁就是纯用户态的轻量级锁

当发现锁被其他线程持有的时候,另外的线程不会挂起等待,而是会反复询问,看当前锁是否被释放。

CAS和ABA问题

在CAS中,进行比较的时候,会发现寄存器A和内存M的值相同

我们无法判断M是变了又变回来了还是没变

要怎么解决呢,我们只需要一个记录器来记录上述内存的变化就可以了

一是记录M的修改次数或者上次的修改时间

这个时候修改操作就比较版本号或者上次修改时间,如果发现版本号不一致就放弃操作

锁粗化

synchronized对应代码块包含的代码少,粒度就细,包含的代码多,粒度就粗

锁粗话就是把细粒度的加锁变成粗力度的加锁

在三个项目的开头都有加锁和解锁的情况下,只在开头去加锁和在末尾去解锁就是锁粗化,需要是同一个加锁对象才可以粗化到一起

 JUC

就是java.util.concurrent

主要介绍这个包内以下方法

Callable接口
类似于Runnable,Runnable描述的任务不带返回值,Callable描述的任务带返回值
package threading;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 15:01
 */
public class L11111 {
//    创建线程,通过线程来计算1+2+3+...+1000
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用一个Callable来定义一个任务
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 1; i < 1000 ; i++) {
                    sum+=i;
                }
                return sum;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //上面是存在目的就是为了让我们获取结果

        //创建线程执行上述任务
        //线程thread不能直接传callable 需要中间类futuretask
        Thread t = new Thread(futureTask);
        t.start();

        //获取线程计算结果
        //get方法会阻塞,直到call方法计算完毕,get才会返回。
        System.out.println(futureTask.get());

    }
}

 

ReentrantLock

主要是三个方法:
lock()

unlock()

tryLock    
试试看能不能加上锁,试成功了就加锁成功,失败了就放弃,还可以指定加锁的等待超时时间

ReentrantLock
可以实现公平锁,默认是非公平的,加入一个简单参数就可以成为公平锁

Condition类
可以指定唤醒那个线程,等待哪个线程
package threading;

import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 16:17
 */
public class L1113 {
    public static void main(String[] args) {
        ReentrantLock locker = new ReentrantLock(true);
        //里面的true可以实现公平锁
        //加锁
        locker.lock();

        //这两者之间如果有return或者异常就有可能导致unlock执行不到
        //大部分锁都是上面这样设定的
        //但是synchronized就没有这个问题
        //解锁
//        locker.unlock();
//
//        try{
//            locker.lock();
//        }finally {
//            {
//                locker.unlock();
//            }
//        }
    //这样写更加稳妥


    }
}

原子类

底层是基于CAS实现的,java内部

AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference
package threading;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 16:29
 */
public class L1114 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
//                //相对于count++
//                count.incrementAndGet();
//                //相对于++count
//                count.getAndDecrement();
//                //相对于count--
//                count.decrementAndGet();
//                //相对于--count
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        //获取内部值
        System.out.println(count.get());
    }
}

原子类实现自增到十万

Semaphore

信号量

p操作 申请一个资源 可用资源数-1

v操作 释放一个资源 可用资源数+1

当计数为0的时候,继续p操作就会阻塞等到其他线程v操作了为止

锁可以说是一个特殊的信号量,信号量是一个更为广义的锁。

package threading;

import java.util.concurrent.Semaphore;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 16:40
 */
public class L1115 {
    public static void main(String[] args) throws InterruptedException {
        //构造的时候需要指定初始值,计数器的初始值,表示有几个可用资源
        //这个信号量就是把操作系统中的信号量给封装了一下
        Semaphore semaphore = new Semaphore(4);
        //p操作 申请资源 计数器+1
        semaphore.acquire();
        System.out.println("p操作");
        semaphore.acquire();
        System.out.println("p操作");
        semaphore.acquire();
        System.out.println("p操作");
        semaphore.acquire();
        System.out.println("p操作");
        //v操作,释放资源 计数器-1
        semaphore.release();

    }
}

CountDownLatch

用于解析多个线程完成任务

package threading;

import java.util.concurrent.CountDownLatch;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-11
 * Time: 16:48
 */
public class L1116 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i <10 ; i++) {
            Thread t = new Thread(()->{
                System.out.println("开始执行任务 " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("结束执行任务 "+ Thread.currentThread().getName());
                //撞线了
                countDownLatch.countDown();
            });
            t.start();
        }
        //所有选手撞线后才会解除这个阻塞
        countDownLatch.await();
        System.out.println("比赛结束");
    }
}

多线程使用队列和哈希表

多线程使用队列
ArrayBlockingQueue
基于数组实现的阻塞队列

LinkedBlockingQueue
基于链表实现的阻塞队列

PriorityBlockingQueue
基于堆实现的带优先级的阻塞队列

TransferQueue
最多只包含一个元素的阻塞队列

多线程使用哈希表
Hashtable

ConcurrentHashMap

ConcurrentHashMap


背后做了很多优化给加锁操作:

1 锁粒度的控制
HashTable是直接在方法上加synchronized 相当于是对this加锁 
也就是相当于是针对哈希表对象来加锁
一个哈希表只有一个锁
多个线程,无论这些线程都是如何操作这个哈希表都会产生锁冲突了

而ConcurrentHashMap不是一把锁,而是多把,给每个哈希桶都分配一把锁
只有两个线程访问同一个哈希桶的时候才会有锁冲突,如果不是一个哈希桶就不会产生锁冲突
又因为哈希桶个数很多 因此大大降低了哈希桶的锁冲突概率

2 只是给写操作加锁,读操作就不加锁了。

因此两个线程同时修改,才会有锁冲突,两个线程同时读或者一个线程读一个线程修改的话是不会有冲突的。也不需要担心会读到正在修改的数据。读操作中广泛的使用了volatile来保证读到的数据是及时的

3 充分的利用到了CAS的特性

比如维护元素个数,都是通过CAS实现而不是加锁 还有些地方直接使用了CAS实现的轻量级锁来实现。整体思路是能不加就不加。核心思路是尽一切可能去降低锁冲突的概率

4 对于扩容操作,进行了特殊的优化

HashTable的扩容是这样的

当put元素的时候,发现自己的负载因子已经超过了阈值,就需要触发扩容(申请更大的数组,把旧的数据搬运到新的数组上)。主要问题是如果元素个数很多,这个操作的开销就会很大。

而ConcurrentHashMap就是每次搬运一点,旧的和新的数组会同时存在,每次进行哈希表的操作就会把旧的内存上的元素搬运一部分到新的空间桑,直到最终搬运完成,然后再释放旧的空间。如果是查询元素则旧的和新的一起查,如果是插入元素则直接往新的上插入,如果是删除元素则直接删了不用搬运了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值