线程通信

24 篇文章 1 订阅

线程通信

应用场景:生产者和消费者问问题

• 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费

• 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止

• 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

解决线程通信的三个方法

wait() 表示线程一直等待

wait(long timeout) 线程等待指定毫秒参数的时间

wait(long timeout,int nanos) 线程等待指定毫秒数、微妙的时间

notify() 唤醒一个处于等待状态的线程

notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程先运行

完整的线程生命周期

阻塞状态有三种

  • 普通的阻塞sleep,join,Scanner input.next()
  • 同步阻塞(锁池队列) 没有获取同步监视器的线程的队列
  • 等待阻塞(阻塞队列) 被调用了wait()后释放锁,然后进行该队列

在这里插入图片描述

  • 区别1:sleep():线程会让出CPU进入阻塞状态,但不会释放对象锁;wait():线程会让出CPU进入阻塞状态,也会放弃对象锁,进入等待此对象的等待锁定池
  • 区别2:wait只能在同步控制方法或同步控制块里面使用,而sleep可以在任何地方使用
生产者与消费者案例实现(Synchronized)

同步方法下实现线程通信

/**
 * 线程通信B
 * 线程通信的细节  基于synchronized下的线程通信
 *  【A】线程通信必须在锁情况下进行
 *  【B】进行线程通信的多个线程,要使用同一个线程监视器(product),
 *      还必须要调用该同步监视器的wait()、notify()、notifyAll()
 *
 *   1.wait()  会一直等待下去  直到唤醒操作执行
 *    Thread.sleep();   执行的时候没有释放锁
 *    product.wait();   等待的时候是释放了锁资源
 *
 *   2.notify()、notifyAll() 唤醒
 *     notify():随机唤醒一个线程
 *     notifyAll():唤醒所有等待线程
 *
 *    基于synchronized实现线程通信问题:
 *         基于多个生产者和消费者的情况下,进行线程唤醒的时候没有办法确定唤醒哪一个生产者和消费者
 *         因为这个唤醒的过程是随机的,所以非常可能造成想要唤醒生产者的时候却唤醒了消费者
 */
public class TestB {
    public static void main(String[] args) {
        Product product = new Product();

        //开启生产者线程
        new Thread(new ProduceRunnable2(product)).start();

        //开启消费者线程
        new Thread(new ConsumeRunnable2(product)).start();
    }
}

class ProduceRunnable2   implements Runnable{
    //这样操作的目的就是保证生产者和消费者使用的同一件商品
    private Product product;

    public ProduceRunnable2(Product product) {
        this.product = product;
    }
    @Override
    public void run() {
        int num=0;
        while (true){
        synchronized (product){
            if (product.isFlag()){//如果库存为true,证明有库存
                try {
                    product.wait(); //使当前线程等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (num%2==0){
                product.setColor("白色");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setName("馒头");
            }else {
                product.setColor("黄色");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setName("玉米饼");
            }
            System.out.println("生产者生产了:"+product.getColor()+" "+product.getName());
            num++;

            //证明商品已经生产出来了
            product.setFlag(true);
            //通知消费者消费
            product.notify();
        }
        }
    }
}

class ConsumeRunnable2 implements Runnable{

    private Product product;

    public ConsumeRunnable2(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
     while (true){
         synchronized (product) {
             if (!product.isFlag()){//如果当前库存中没有商品
                 try {
                     product.wait();//没有商品就等待生产者生产
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             System.out.println("消费者消费了:" + product.getColor() + " " + product.getName());
             //消费完成了
             product.setFlag(false);
             //通知生产者生产
             product.notify();
         }
     }
    }
}

Lock锁下实现线程通信

/**
 *
 * 基于Lock锁下实现线程通信
 *  【1】基于lock锁的线程通信也必须在锁情况下实现
 *  【2】使用同一个lock创建出等待队列
 *      Condition produceContion=lock.newCondition();
 *  【3】await(); 	signal();  	signalAll();
 */
public class TestA {
    public static void main(String[] args) {
        Product product = new Product();

        //创建锁对象
        Lock lock = new ReentrantLock();

        //创建等待的队列
        Condition produceCondition = lock.newCondition();
        Condition consumeCondition = lock.newCondition();

        new Thread(new ProduceRunnable(product,lock,produceCondition,consumeCondition)).start();
        new Thread(new ConsumeRunnable(product,lock,produceCondition,consumeCondition)).start();


    }
}

class ProduceRunnable implements Runnable {
    private Product product;
    private Lock lock;
    private Condition produceCondition;
    private Condition consumeCondition;


    public ProduceRunnable(Product product, Lock lock, Condition produceCondition, Condition consumeCondition) {
        this.product = product;
        this.lock = lock;
        this.produceCondition = produceCondition;
        this.consumeCondition = consumeCondition;
    }

    @Override
    public void run() {
        int num = 0;
        while (true) {
            lock.lock();
            if (product.isFlag()) {
                //如果当前if可以进入  证明当前有库存,使当前线程等待  生产者等待
                try {
                    produceCondition.await();   //把当前线程放到生产者队列中等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (num % 2 == 0) {
                product.setColor("白色");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setName("馒头");
            } else {
                product.setColor("黄色");
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setName("玉米饼");
            }
            System.out.println("生产者生产了:" + product.getColor() + " " + product.getName());
            num++;

            //生产商品结束
            product.setFlag(true);
            //通知消费者队列进行消费
            consumeCondition.signal();//随机唤醒一个消费者等待线程
            consumeCondition.signalAll();//唤醒消费者队列所有等待线程

            lock.unlock();
        }
    }
}

class ConsumeRunnable implements Runnable {
    //这样操作的目的就是保证生产者和消费者使用的是同一件商品
    private Product product;
    private Lock lock;
    private Condition produceCondition;
    private Condition consumeCondition;


    public ConsumeRunnable(Product product, Lock lock, Condition produceCondition, Condition consumeCondition) {
        this.product = product;
        this.lock = lock;
        this.produceCondition = produceCondition;
        this.consumeCondition = consumeCondition;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();

            if (!product.isFlag()) {
                try {
                    consumeCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者消费了:" + product.getColor() + " " + product.getName());
            //消费结束
            product.setFlag(false);
            //去生产者队列中通知生产者生产
            produceCondition.signal();
            lock.unlock();
        }
    }
}

Condition

Condition是在Java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify()、使用Conditionde await()、signal()这种方式实现线程间协作更加安全和高效。

它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。一个Condition包含一个等待队列,一个Lock可以产生多个Condition,所以可以有多个等待队列。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。

调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock()之间才可以使用

  • Condition中的await()对应Object的wait();
  • Condition中的signal()对应Object的notify();
  • Condition中的signalAll()对应Object的notifyAll()。

signal()

唤醒一个等待线程

如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从await返回之前,该线程必须重新获取锁。

signalAll()

唤醒所有等待线程

如果所有的线程都在等待此条件,则唤醒所有线程。在从await()返回之前,每个线程都必须重新获取锁。

LockSupport锁下线程通信

public class Product {

     private  String  color;// 白色    黄色
     private  String   name;// 馒头   玉米饼

     private  boolean  flag=false;//库存信号 默认的是没有库存

     private ProduceRunnable  produceRunnable;
     private  ConsumeRunnable consumeRunnable;

    public ProduceRunnable getProduceRunnable() {
        return produceRunnable;
    }

    public void setProduceRunnable(ProduceRunnable produceRunnable) {
        this.produceRunnable = produceRunnable;
    }

    public ConsumeRunnable getConsumeRunnable() {
        return consumeRunnable;
    }

    public void setConsumeRunnable(ConsumeRunnable consumeRunnable) {
        this.consumeRunnable = consumeRunnable;
    }
    public Product(){}

    public Product(String color, String name) {
        this.color = color;
        this.name = name;
    }
    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product{" +
                "color='" + color + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 *
 * 基于LockSupport锁下实现线程通信:
 *
 *   不需要加锁
 *     park() 等待
 *     unpark(Thread  th)  唤醒
 *
 *
 */
public class Test01 {

    public static void main(String[] args) {
        Product product=new Product();

        ProduceRunnable produceRunnable = new ProduceRunnable();
        ConsumeRunnable consumeRunnable = new ConsumeRunnable();
        product.setProduceRunnable(produceRunnable);
        product.setConsumeRunnable(consumeRunnable);
        produceRunnable.setProduct(product);
        consumeRunnable.setProduct(product);
        //开启生产者线程
        produceRunnable.start();
        //开启消费者线程
       consumeRunnable.start();


    }
}

//生产者类
class  ProduceRunnable  extends Thread {
    private Product product;

    public void setProduct(Product product) {
        this.product = product;
    }
    @Override
    public void run() {

          int  num=0;

          while (true){

                 if(product.isFlag()){
                     //证明当前有库存,使当前线程处于等待状态
                     LockSupport.park();
                 }
                  if(num%2==0){
                      product.setColor("白色");
                      try {
                          Thread.sleep(5);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      product.setName("馒头");
                  }else{
                      product.setColor("黄色");
                      try {
                          Thread.sleep(5);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      product.setName("玉米饼");
                  }

                  System.out.println("生产者生产了:"+product.getColor()+" "+product.getName());
                  num++;

                  //生产结束
                 product.setFlag(true);
                 //唤醒消费者线程
                LockSupport.unpark(product.getConsumeRunnable());
          }
    }
}

//消费者线程
class ConsumeRunnable  extends  Thread{
    //这样操作的目的就是保证生产者和消费者使用的同一件商品
     private Product product;

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {

        while (true){
                if(!product.isFlag()){
                    LockSupport.park();
                }
                System.out.println("消费者消费了:"+product.getColor()+" "+product.getName());

                product.setFlag(false);
                //唤醒生产者生产
                LockSupport.unpark(product.getProduceRunnable());
        }
    }
}

三种线程通信方式对比

  1. 对于Object中wait和notify:必须在锁中使用,这里指synchronized,并且wait方法要先于notify,否则wait方法会一致阻塞该线程
  2. Lock中lock.newCondition:condition.await()和condition.signal():必须在锁中使用,这里指lock.lock()之后,并且await方法要先于signal,否则await方法会一直阻塞该线程。
  3. LockSupport优势,优势1:不需要再锁中进行;优势二:park方法可以不先于unpark方法

线程池

什么是线程池?

  • 创建好销毁对象是非常耗费时间的
  • 创建对象:需要分配内存等资源
  • 小慧对象:虽然不需要程序员操心,但是垃圾回收器会在后台一直跟踪并销毁
  • 对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:创建好多个线程,放入线程池中,使用时直接获取引用,不使用时放回池中。可以避免频繁创建销毁、实现重复利用
  • 技术案例:线程池、数据库连接池
  • JDK1.5起,提供了内置线程池

线程池的好处

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 提高线程的可管理性:避免线程无限制创建、从而消耗系统资源,降低系统稳定性,甚至内存溢出或者CPU耗尽

线程池的应用场合

  • 需要大量线程,并且完成任务的时间短
  • 对性能要求苛刻
  • 接收突发性的大量请求

使用线程池执行大量的Runnable命令

public class TestThreadPool1 {
    public static void main(String[] args) {
        //创建一个线程池
        //创建一个线程池,池中只有1个线程,保证线程一直存在
       //ExecutorService pool = Executors.newSingleThreadExecutor();
        //创建一个线程池,池中有固定数量的线程
        //ExecutorService pool = Executors.newFixedThreadPool(10);
        //创建一个线程池,池中线程的数量可以动态变化
        ExecutorService pool = Executors.newCachedThreadPool(); 
        //使用线程池执行大量的Runnable命令
        for(int i=0;i<20;i++){
            final int n = i;
            //指定Runnable命令
            Runnable command = new Runnable(){
                public void run() {
                    System.out.println("开始执行"+n);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("执行结束"+n);
                }                
            };
            //不需要new Thread(command);
            //使用线程池执行命令
            pool.execute(command);
        }
        //关闭线程池
        pool.shutdown();
    }
}
//class MyRunnable implements Runnable{
// public void run() {
//    System.out.println("开始执行");
//    System.out.println("执行结束");
// }
//}

使用线程池执行大量的Callable任务

public class TestThreadPool2 {
    public static void main(String[] args) 
            throws InterruptedException, ExecutionException {
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //使用线程池
        List<Future> list = new ArrayList<Future>();
        for(int i=0;i<20000;i++){
            //指定一个Callable任务
            Callable<Integer> task = new MyCallable();
            //将任务交给线程池
            Future<Integer> future = pool.submit(task);            
            //将Future加入到List
            list.add(future);
        }
        System.out.println("ok?");
        //遍历并输出多个结果
        for(Future f:list){
            System.out.println(f.get());
        }        
        System.out.println("OK!");
        //关闭线程池
        pool.shutdown();
    }
}
class MyCallable implements Callable<Integer>{
    public Integer call() throws Exception {
        Thread.sleep(2000);        
        return new Random().nextInt(10);
    }    
}

线程池API总结

  • Executor:线程池顶级接口,只有一个方法

  • ExecutorService:真正的线程池接口

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    • Futuresubmit(Callable task):执行任务,有返回值,一般用来执行Callable
    • void shutdown():关闭线程池
  • AbstractExecutorService:基本实现了ExecutorService的所有方法

线程池

  • l ThreadPoolExecutor:默认的线程池实现类
  • ScheduledThreadPoolExecutor:实现周期性任务调度的线程池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

ForkJoin框架

Fork/Join框架是JAVA7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果得到大任务结果的框架

forkjoin

工作窃取算法

一个大任务拆分成多个小任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列中,并且每个队列都有单独的线程来执行队列里的任务,线程和队列一一对应。

但是会出现这样一种情况:A线程处理完了自己队列的任务,B线程的队列里还有很多任务要处理。

A是一个很热情的线程,想过去帮忙,但是如果两个线程访问同一个队列,会产生竞争,所以A想了一个办法,从双端队列的尾部拿任务执行。而B线程永远是从双端队列的头部拿任务执行。

工作窃取算法

注意:线程池中的每个线程都有自己的工作队列(PS,这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务),当自己队列中的任务都完成以后,会从其它线程的工作队列中偷一个任务执行,这样可以充分利用资源。

优点

利用了线程进行并行计算,减少了线程间的竞争

缺点

如果双端队列中只有一个任务时,线程间会存在竞争。

窃取算法消耗了更多的系统资源,如会创建多个线程和多个双端队列。

ForkJoinTask

使用该框架,需要创建一个ForkJoin任务,它提供在任务中执行fork和join操作的机制。一般情况下,我们并不需要直接继承ForkJoinTask类,只需要继承它的子类,它的子类有两个:

  • RecursiveAction:用于没有返回结果的任务。

  • RecursiveTask:用于有返回结果的任务。

  • fork()方法:将任务放入队列并安排异步执行

  • ²join()方法:等待计算完成并返回计算结果。

ForkJoinPool:

  • 任务ForkJoinTask需要通过ForkJoinPool来执行。

ForkJoinWorkerThread:

  • ForkJoinPool线程池中的一个执行任务的线程。

  • //使用ForkJoin框架计算 1+2+3......+n = ?
    
    public class SumTask extends RecursiveTask<Long> {
        private int start;
        private int end;
        private final int step = 2000000;//最小拆分成几个数相加
        public SumTask(int start, int end) {
            this.start = start;
            this.end = end;
        }
        @Override
        protected Long compute() {
            long sum = 0;
            if(end - start <= step ){
                //小于5个数,直接求和
                for (int i = start; i <=end; i++) {
                    sum+=i;
                }
            }else{
              //大于5个数,分解任务
                int mid = (end + start)/2;
                SumTask leftTask = new SumTask(start,mid);
                SumTask rightTask = new SumTask(mid+1,end);
                //执行子任务
                leftTask.fork();
                rightTask.fork();
                //子任务,执行完,得到执行结果
                long leftSum = leftTask.join();
                long rightSum = rightTask.join();
                sum = leftSum+rightSum;
            }
            return sum;
        }
        public static void main(String[] args) 
    throws ExecutionException, InterruptedException {
         //如果多核CPU,其实是一个一直使用,其他闲置;怎么办,多线程解决;
        //但是涉及到任务的拆分与合并等众多细节,不要紧,
    //现在使用ForkJoin框架,可以较轻松解决;
            long  start = System.currentTimeMillis();
            long sum = 0;
            for(int i=0;i<=1000000000;i++){
                sum +=i;
            }
            System.out.println(sum);
            long end = System.currentTimeMillis();
            System.out.println("for:"+(end - start));
            //使用ForkJoin框架解决
            //创建一个线程池
            ForkJoinPool pool = new ForkJoinPool();
            //定义一个任务
            SumTask sumTask = new SumTask(1,1000000000);
            //将任务交给线程池
             start = System.currentTimeMillis();
            Future<Long> future = pool.submit(sumTask);
            //得到结果并输出
            Long result = future.get();
            System.out.println(result);
            end = System.currentTimeMillis();
            System.out.println("pool:"+(end - start));
        }
    }
    

    可以看出,使用了 ForkJoinPool 的实现逻辑全部集中在了 compute() 这个函数里,仅用了很少行就实现了完整的计算过程。特别是,在这段代码里没有显式地“把任务分配给线程”,只是分解了任务,而把具体的任务到线程的映射交给了 ForkJoinPool 来完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~Amory

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值