多线程05_线程协作

1 生产者消费者模式

  • 应用场景:

    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件:

    • 对于生产者,没有生产产品之前,要通知消费者等待;生产了产品之后,需要马上通知消费者消费
    • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
    • 在生产者消费者问题,仅有synchronized是不够的。synchronized可阻止并发更新同一个共享资源,实现了同步;但synchronized不能用来实现不同线程之间的消息传递(通信)
  • Java提供了几个方法解决线程之间的通信问题

    方法名作用
    wait()表示线程一直等待,直到其他线程通知,它与sleep()不同,会释放锁
    wait(long timeout)指定等待的毫秒数
    notify()唤醒一个处于等待状态的线程
    notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

    这些方法均是Object类的方法,都只能在同步方法或同步代码块中使用,否则会抛出IllegalMonitorStateException

2 管程法

public class TestPC {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();

        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

// 生产者
class Producer extends Thread {
    Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.push(new Product(i));
            System.out.println("生产了" + i + "件产品");
        }
    }
}

// 消费者
class Consumer extends Thread {
    Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第" + buffer.pop().id + "件产品");
        }
    }
}

// 产品
class Product {
    int id;     // 产品编号

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

// 缓冲区
class Buffer {
    // 缓冲区大小
    Product[] products = new Product[10];
    // 缓冲器计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push(Product product) {
        // 如果缓冲区满,需要等待消费者消费
        if (count == products.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果没有满,需要往缓冲区内加入产品
        products[count++] = product;

        // 通知消费者消费
        this.notifyAll();
    }

    // 消费者消费产品
    public synchronized Product pop() {
        // 判断能否消费
        if (count == 0) {
            // 等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Product result = products[--count];

        // 通知生产者生产
        this.notifyAll();

        return result;
    }
}

3 信号灯法

public class TestPC {
    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生产者——>演员
class Player extends Thread {
    TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                tv.play("电视剧" + i);
            } else {
                tv.play("广告" + i);
            }
        }
    }
}

// 消费者——>观众
class Watcher extends Thread {
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

// 产品——>节目
class TV {
    // 演员表演,观众等待    true
    // 观众观看,演员等待    false
    boolean flag = true;
    String name;        // 表演的节目名

    // 表演
    public synchronized void play(String name) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" + name);

        // 通知观众观看
        this.notifyAll();       // 通知唤醒
        this.name = name;
        this.flag = !this.flag;
    }

    // 观看
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + name);

        // 通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

4 线程池

  • 为了减少创建和销毁线程的次数,让每个线程可以多次使用,可根据系统情况调整执行的线程数量;为了防止消耗过多内存,可以使用线程池

  • Java中线程池的顶级接口是ExecutorExecutorServiceExecutor的子类,也是真正的线程池接口,它提供了提交任务关闭线程池等方法

  • ExecutorService常见子类为**ThreadPoolExecutor**

    • void execute(Runnable command):执行任务/命令,无返回值,一般用于执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用于执行Callable
    • void shutdown():关闭连接池
  • ThreadPoolExecutor拥有一些核心参数:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockongQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
           maximumPoolSize <= 0 ||
           maximumPoolSize < corePoolSize ||
           keepAliveTime < 0) {
            throw new IllegalArgumentException();
        }
        
        if (workQueue == null || threadFactory == null || handler == null) {
            throw new NullPointerException();
        }
        
        this.acc = System.getSecurityManager() == null ? 
            null : 
        	AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    
    • corePoolSize(线程池核心线程大小):线程池中会维护一个最小的线程数量,即使这些线程处于空闲状态也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize
    • maximumPoolSize(线程池最大线程数量):线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定
    • keepAliveTime(空闲线程存活时间):一个线程如果处于空闲状态,且当前线程数大于corePoolSize,那么在指定时间后该空闲线程就会被销毁
    • unit(空闲线程存活时间单位):keepAliveTime的计量单位
    • workQueue(工作队列):新任务被提交后,会先进入工作队列中,在任务调度时再从队列中取出任务
    • threadFactory(线程工厂):创建一个新线程时使用的工厂,可以用于设定线程名和是否为守护线程等
    • handler(拒绝策略):当工作队列中的任务已达到最大限制,且线程池中的线程数量也达到最大限制,此时如果有新任务提交,使用拒绝策略解决
  • JDK中提供了4种工作队列

    • ArrayBlockingQueue基于数组的有界阻塞队列,按FIFO排序。新任务进入时先放置到队列的队尾,**有界数组可以防止资源耗尽的问题。**当线程池中线程数量达到corePoolSize后,新任务放在该队列的队尾,等待被调度。如果队列已满则创建一个新线程。如果线程数量达到maximumPoolSize,则执行拒绝策略
    • LinkedBlockingQueue基于链表的无界阻塞队列(实际上最大容量为Integer.MAX),按FIFO排序。线程池中的线程数量达到corePoolSize后,新任务会一直存入该队列。因此使用该队列时,参数maxPoolSize其实是不起作用的
    • SynchronousQueue不缓存任务的阻塞队列,生产者放入一个任务必须要等到消费者取出这个任务。即新任务进入时不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程。如果线程数量达到maximumPoolSize,则执行拒绝策略
    • PriorityBlockingQueue具有优先级的无界阻塞队列,优先级通过参数Comparator实现
  • JDK提供了4种拒绝策略:

    • CallerRunsPolicy:该策略在调用者线程中直接执行被拒绝任务的run()方法,如果线程池已经shutdown(),则直接抛弃该任务
    • AbortPolicy:该策略直接抛弃任务,并抛出RejectedExecutionException异常
    • DiscardPolicy:该策略直接抛弃任务,什么都不做
    • DiscardOldestPolicy:该策略抛弃进入队列最早的任务,然后尝试将这次拒绝的任务放入队列
  • Java中定义了Executors工具类用来方便创建各种常用线程池

    • newSingleThreadExecutor()单线程线程池,只有一个线程在工作,所有任务按照顺序执行
    • newFixedThreadPool()定长线程池,最大线程数等于核心线程数
    • newCachedThreadPool()可缓存线程池,核心线程为0,最大线程为Integer.MAX_VALUE,线程处于空闲状态超过60s会被销毁,采用SynchronousQueue作为阻塞队列
    • newScheduledThreadPool()支持定时的定长线程池,核心线程数固定,最大线程为Integer.MAX_VALUE,使用DelayedQueue作为阻塞队列,实现延迟和定时的功能
  • public class TestPool {
        public static void main(String[] args) {
            // 创建服务,创建线程池
            ExecutorService service = Executors.newFixedThreadPool(10);
            
            // 执行
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            
            // 关闭连接
            service.shutdown();
        }
    }
    
    class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值