多线程02

目录

一、线程通信:

二、synchroized

线程同步方法

三、Lock

1、Condition接口

四、阻塞队列

1、生产者消费者模式

2、BlockingQueue接口

01、BlockingQueue增加了两个支持阻塞的方法

五、线程组

1、ThreadGroup类

 2、创建线程组

01、ThreadGroup的构造器

02、Thread构造器

 3、使用线程组

六、线程池

1、线程池机制:

2、创建线程池

3、使用线程池

01、 ExecutorService

02、ScheduledExecutorService

七、ForkJoinPool:

1、创建ForkJoinPool

2、使用ForkJoinPool

01、ForkJoinTask

八、ThreadLocal

九、线程安全的集合

1、包装不安全的集合

2、线程安全的集合

01、Concurrent开头的集合类

02、CopyOnWrite开头的集合类

 结束语


一、线程通信:

前提:线程通信的前提是要支持并发读写数据,所以线程通信必须要对共享资源加锁。
1 、在某些业务中,A线程负责修改数据,B线程负责使用数据
2、在数据修改前,B线程处于等待状态,直到A线程的通知
3、在数据修改后,A线程通过某种机制,通知B线程去使用数据

二、synchroized

* synchroized(obj) {...}//对某个对象加锁
* synchroized void fun() {...}对方法加锁

线程同步方法

使线程同步,要调用Object类中的方法
    * void wait() 调用该方法的线程进入等待状态,并释放此对象的锁
    * void wait(long timeMillis) 同上,但设置了等到时间timeMillis
    * void notfy() 通知一个在此对象上等待的线程,使其从wait()返回
    * void notifyAll() 通知所有在对象上等待的线程,使其从wait()返回
    public static void main(String[] args){

        Product product = new Product(0);//默认商品为0

        for (int i = 0; i < 50;i++){
            //初始化50个用户,即50个线程去抢商品
            new Thread(new BuyTask(product),"用户" + i).start();

        }
        try {
            Thread.sleep(3000);//倒计时3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new SellTask(product),"卖家").start();
    }

    //商品类
    private static class Product{
        private int amount;//商品数量

        public Product (int amount){
            this.amount = amount;
        }

        public int getAmount(){
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

    //商家

    private static class SellTask implements Runnable{

        private Product product;

        public SellTask(Product product){//构造器初始化商品
            this.product = product;
        }
        @Override
        public void run() {
            //对商品加锁
            synchronized (product){
                String name = Thread.currentThread().getName();//获取线程名
                product.setAmount(10);//修改商品
                System.out.println(name + "上架了10件商品,剩余库存为" + product.getAmount());
                //唤醒等待用户的锁
                product.notifyAll();//通知所有的用户

            }


        }
    }

    //用户

    //购买商品逻辑
    private static class BuyTask implements Runnable{

        private Product product;

        public BuyTask(Product product){//构造器初始化商品
            this.product = product;
        }
        @Override
        public void run() {
            synchronized (product){//对商品加锁锁定
                String name = Thread.currentThread().getName();//获取线程名
                while (product.getAmount() == 0){//条件不满足时候等待,即商品为0
                    try {
                        System.out.println(name + "用户等待抢购....");
                        product.wait();//使当前线程等待,直到有其他线程唤醒执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //条件满足抢购时候
                product.setAmount(product.getAmount()-1);
                System.out.println(name + "用户抢到了商品,库存剩余 "+ product.getAmount());
            }

        }
    }

三、Lock

* Condition condition = lock.newCondition();

1、Condition接口

* void await() 调用该方法的线程进入等待状态。
* void await (long time, TimeUnit unit) 同上,但最长等待time时间,unit为时间单位。
* void signal() 唤醒一个在此Condition上等待的线程。
* void singnalAll() 唤醒所有在此Condition上等待的线程。
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args){

        Product product = new Product(0);//默认商品为0

        for (int i = 0; i < 50;i++){
            //初始化50个用户,即50个线程去抢商品
            new Thread(new BuyTask(product),"用户" + i).start();

        }
        try {
            Thread.sleep(3000);//倒计时3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new SellTask(product),"卖家").start();
    }

    //商品类
    private static class Product{
        private int amount;//商品数量

        public Product (int amount){
            this.amount = amount;
        }

        public int getAmount(){
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

    //商家

    private static class SellTask implements Runnable{

        private Product product;

        public SellTask(Product product){//构造器初始化商品
            this.product = product;
        }
        @Override
        public void run() {
            lock.lock();
            try {
                //对商品加锁
                String name = Thread.currentThread().getName();//获取线程名
                product.setAmount(10);//修改商品
                System.out.println(name + "上架了10件商品,剩余库存为" + product.getAmount());
                //唤醒等待用户的锁
                condition.signalAll();//通知所有的用户

            }finally {
                lock.unlock();
            }




        }
    }

    //用户

    //购买商品逻辑
    private static class BuyTask implements Runnable{

        private Product product;

        public BuyTask(Product product){//构造器初始化商品
            this.product = product;
        }
        @Override
        public void run() {
            lock.lock();
            try {
                //对商品加锁锁定
                String name = Thread.currentThread().getName();//获取线程名
                while (product.getAmount() == 0){//条件不满足时候等待,即商品为0
                    try {
                        System.out.println(name + "用户等待抢购....");
                        condition.await();//使当前线程等待,直到有其他线程唤醒执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //条件满足抢购时候
                product.setAmount(product.getAmount()-1);
                System.out.println(name + "用户抢到了商品,库存剩余 "+ product.getAmount());
            }finally {
                lock.unlock();//释放锁
            }

        }
    }

四、阻塞队列

1、生产者消费者模式

1、某个模块负责产生数据、某个模块负责处理数据
2、产生数据的模块被称为生产者,处理数据的模块称为消费者
3、需要有一个缓冲区位于生产者和消费者之间,作为沟通的桥梁
4、生产者只负责把数据放入缓冲区、而消费者从缓冲区中取出数据

2、BlockingQueue接口

* BlockingQueue接口是java出台用于解决生产者消费者模式问题的接口,阻塞队列
BlockingQueue是Queue的子接口。主要作用是作为线程通信的工具。

01、BlockingQueue增加了两个支持阻塞的方法

1、void put(E e): 尝试把元素e放在队列中,如果该队列的元素已满,则阻塞该线程
2、void take() : 尝试从队列的头部取出元素,如果该队列的元素已空,则阻塞该线程
    public static void main(String[] args) {

        BlockingQueue<Long> queue = new ArrayBlockingQueue<>(10);//此队列可以装入10个数
        //生产者
        new Thread(new Producer(queue),"生产者1").start();
        new Thread(new Producer(queue),"生产者2").start();
        new Thread(new Producer(queue),"生产者3").start();
        new Thread(new Producer(queue),"生产者4").start();
        new Thread(new Producer(queue),"生产者5").start();
        /**
         * 消费大于生产
         */
        //消费者
        new Thread(new Customer(queue),"消费者1").start();
        new Thread(new Customer(queue),"消费者2").start();
        new Thread(new Customer(queue),"消费者3").start();




    }

    //生产者
    private static class Producer implements Runnable{

        private BlockingQueue<Long> queue;//缓冲区

        public Producer(BlockingQueue<Long> queue){//构造器
            this.queue = queue;
        }
        @Override
        public void run() {
            //一直生产
            while (true){
                try {
                    Thread.sleep(new Random().nextInt(1000));//休眠1000ms内随机值
                    queue.put(System.currentTimeMillis());
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "生产了一条数据,剩余 " + queue.size());

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

    //消费者
    private static class Customer implements Runnable{
        private BlockingQueue<Long> queue;//缓冲区

        public Customer(BlockingQueue<Long> queue){//构造器
            this.queue = queue;
        }
        @Override
        public void run() {
            while (true){
                try {
                    Thread.sleep(new Random().nextInt(1000));//休眠1000ms内随机值
                    //缓冲区中取出数据
                    queue.take();
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "消费了一条数据,剩余 " + queue.size());

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

五、线程组

1、ThreadGroup类

* ThreadGroup类代表线程组,他可以包含一批线程,并对这些线程进行统一的管理

1、每个线程否有对应的线程组,若程序未显示指定的线程的线程组,则该线程属于默认的线程组
2、默认情况下,子线程与他的父线程属于同一个线程组,而main线程归则属于main线程组
3、一旦线程加入某个线程组,则该线程将一直处属于这个线程组,中途不允许修改器线程组

 2、创建线程组

01、ThreadGroup的构造器

1、public ThreadHroup(String name)
2、public ThreadGroup(ThreadGroup parent , String name)

02、Thread构造器

1、public Thread(ThreadGroup group , String name)
2、public Thread(ThreadGroup group , Runnable target)
3、public Thread(ThreadGroup group , Runnable target , String name)

 3、使用线程组

* ThreadGroup中常用的处理线程的常用方法包括
        1、返回线程组的名称
        2、返回当前线程组的父线程组
        3、中断次现场组中的所有线程
        4、设置(返回)线程组的最高优先级   {set / get}
        5、设置(返回)线程组Wie后台线程组
    public static void main(String[] args)  {

        //主线程线程组
        ThreadGroup g = Thread.currentThread().getThreadGroup();

        System.out.println("线程组名:"+g.getName() + ",是否是后台线程:" + g.isDaemon() + ", 线程组活跃线程数目:" + g.activeCount());//线程组名、是否后台线程组、线程组活跃线程数目
        g.list();//列举线程组中所有的线程

        //子线程的线程组

        Thread thread  = new Thread(new ThreadTask());
        thread.start();
        System.out.println(thread);

        //自定义线程组
        g = new ThreadGroup("test");
        g.setDaemon(true);//声明为后台线程组
        thread  = new Thread(g , new ThreadTask());
        thread.start();
        System.out.println(thread);

        //销毁后台线程组
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(g.isDestroyed());

        //异常处理器

        //自定义异常处理器
        Thread.setDefaultUncaughtExceptionHandler(
                new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        //在此方法中会传入当先线程是谁,错误是什么
                        System.out.println(t + "->" + e);
                    }
                }
        );
        System.out.println(3 / 0);//默认方式:错误线程,在主线程中执行
    }

    //线程体
    private static class ThreadTask implements Runnable{

        @Override
        public void run() {
            System.out.println("当前线程的毫秒数:"+System.currentTimeMillis());//创建系统调用时间
        }
    }

六、线程池

* 启动线程的成本较高,通过线程池可以实现线程复用,从而提高性能。
* 线程池可以控制程序中的线程数量,避免超出系统的负荷,导致系统崩溃。

1、线程池机制:

1、创建线程池之后,他会自动创建一批空闲的线程。
2、程序将线程体传给线程池,他就会启动一个空闲的线程来执行该线程体;
3、当线程体执行结束之后,该线程并不会死亡,而是变回空闲状态继续使用

2、创建线程池

1、ExecutorService接口代表线程池
2、ScheduledExecutorService是其子接口,代表可执行定时任务的线程池
3、Executors是一个工厂类,该类中包含了若干个静态方法,用来创建线程池:
    (1)ExecutorService newFixedThreadPool(int nThreads) //新建线程数目固定的线程池
    (2)ScheduledExecutorService newScheduledThreadPool(int corePoolSize)  //新建有定时任务的线程池

3、使用线程池

01、 ExecutorService

Future<?> submit(Runnable task);        //return null
<T> Future<T> submit(Runnable task,T result);       //return result
<T> Future<T> submit(Callable<T> task);         //return call()

02、ScheduledExecutorService

ScheduledFuture<?> schedule (Runnable command,long delay,TimeUnit unit);
... ... ...(部分方法省略)
    //业务中线程池一般为公有
    //普通线程池
    private static ExecutorService threadPool = Executors.newFixedThreadPool(3);//创建三个线程
    //执行定时任务线程池
    private static ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

    public static void main(String[] args) {

        //普通线程池
        Runnable threadTask = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行一个任务");
            }
        };

        for (int i = 0; i < 10; i++){
            //调用线程池执行十个任务
            threadPool.submit(threadTask);
        }

        //定时线程池
        Runnable scheduledTask = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " ->执行一个定时任务");
            }
        };
        for (int i = 0; i < 5; i++){
            //调用线程池执行5个定时任务,间隔为3秒
            scheduledPool.scheduleAtFixedRate(scheduledTask,5,3, TimeUnit.SECONDS);
        }

    }

七、ForkJoinPool:

* Fork/Join是一种思想,目的在充分利用多核资源,用于执行并行任务;
* ForkJoinPool是ExecutorService的实现类,是上述思想的实现
* 做法是:将一个大的任务分割成若干小任务,最终汇总成每个小任务结果,从而得到大任务结果

1、创建ForkJoinPool

构造器
        ForkJoinPool(int parallelism)根据指定任务的并行级别。创建一个ForkJoinPool
        ForkJoinPool()根据计算机的核心数目,创建一个ForkJoinPool

2、使用ForkJoinPool

常用方法:public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task){...}

01、ForkJoinTask

1、代表一个可以并行执行的任务,是Future的实现类、
2、RecursiveAction 、 RecursiveTask是他的子类
3、RecursiveAction代表没有返回值的任务,RecursiveTask有返回值的任务。

以计算前100项和为例

   public static void main(String[] args) throws ExecutionException, InterruptedException {
        int[] nums = new int [100];
        for (int i = 1; i <= 100; i++){
            nums[i-1] = i;
        }
        ForkJoinPool pool = new ForkJoinPool();
        Future<Integer> future = pool.submit(new FJTask(nums,0,nums.length-1));
        System.out.println(future.get());
    }
    //并行计算出结果
    private static class FJTask extends RecursiveTask<Integer>{

        private static final int THRESHOLD = 10;//定义一个阈值,代表将数据拆分,传入数据如果大于10,就拆分,小于十进行计算
        private int[] nums;
        private int start ,end;//定义处理数组的起始点和终止点

        public FJTask(int[] nums,int start,int end){//使用构造器
            this.nums = nums;
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            System.out.printf("%-25s\t%2d,%2d\n",Thread.currentThread().getName(),start,end);
            //计算逻辑
            Integer sum = 0;
            //数组片段小于阈值
            if(end - start < THRESHOLD){
                for (int i = start; i <= end; i++){
                    sum += nums[i];
                }
            }else {
                //拆分数据
                int middle = (start + end) / 2;
                //类似递归处理
                FJTask left = new FJTask(nums,start,middle);
                FJTask right = new FJTask(nums,middle+1,end);
                left.fork();
                right.fork();
                sum = left.join() + right.join();//sum等于左边合并结果+右边合并结果
            }
            return sum;
        }
    }

八、ThreadLocal

应用多见于服务器并发访问数据库
* ThreadLocal是一个工具类,可以将数据绑定到当前线程上,从而实现线程之间数据的隔离
    1、将数据绑定到当前线程之上
        public void set(T value){...}
    2、返回当前线程已绑定的数据
        public T get(){...}
    3、删除当前线程已绑定的数据
        public void remove(){...}
    /**
     * ThreadLocal:应用多见于服务器并发访问数据库
     * @param args
     */

    private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(new ThreadTask(100)).start();
        new Thread(new ThreadTask(200)).start();
        new Thread(new ThreadTask(300)).start();
    }

    public static void first(){
        System.out.println(Thread.currentThread().getName() + "成功调用first()方法");
        second();
    }
    public static void second(){
        System.out.println(Thread.currentThread().getName() + "成功调用second()方法");
        third();
    }
    public static void third(){
        System.out.println(Thread.currentThread().getName() + "成功调用third()方法");
        System.out.println(Thread.currentThread().getName() + "绑定了value " + threadLocal.get());
    }

    private static class ThreadTask implements Runnable{

        private Object value;
        public ThreadTask(Object value){
            this.value = value;
        }

        @Override
        public void run() {
            threadLocal.set(value);
            first();
        }
    }

九、线程安全的集合

1、包装不安全的集合

对于传统的集合类,可以通过Collection将其包装成线程安全的集合:
    1、public static <T> Set<T> synchronizedSet(Set<T> set){...}
    2、public static <T> List<T> synchronizedList(List<T> List){...}
    3、public static <K,V> Map<K,V> synchronizedMap(Map<K,V> map){...}

2、线程安全的集合

线程安全的集合位于java.util.concurrent包下

01、Concurrent开头的集合类

1、支持多线程的并发访问;
2、通过加锁处理、保证并发写入的安全性,而并发读物是无需增加锁的;

02、CopyOnWrite开头的集合类

1、并发读取时,直接读取集合本身,无需加锁
2、并发写入时,该集合会在底层赋值一份新的数组,然后对新数组进行写入操作。

 结束语

自己的笔记

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值