JAVA笔记整理-线程二

一、线程死锁

线程死锁的产生

​ 线程同步可以帮助我们解决多个线程操作同一个资源而导致数据不安全的问题,但线程同步也有可能产生隐患,假如一个线程中出现多个锁对象时,可能出现锁使用不当,导致锁与锁之前相互等待对方释放资源,从而形成一种 “相互等待”的僵局,这就是线程死锁。 例如哲学家吃饭

​ 模拟线程死锁

public class DeadThread implements  Runnable {
    Object obj1 = new Object();
    Object obj2 = new Object();
    @Override
    public void run() {
        // 模拟线程死锁
        // 如果当前线程为线程A  先拿到obj1锁   ,等待obj2锁资源
        // 如果当前线程为线程B  先拿到obj2锁  ,等待obj1锁的资源
        if(Thread.currentThread().getName().equals("线程A")){
             synchronized (obj1){
                 System.out.println(Thread.currentThread().getName()+"拿到了obj1的锁");
                 try {
                     Thread.sleep(500);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println("我在等待obj2锁。。。。。");
                 synchronized (obj2){
                     System.out.println("我已经拿到了obj2锁。。。。");
                 }
             }
        }else{
            //线程B
            synchronized (obj2){
                System.out.println(Thread.currentThread().getName()+"拿到了obj2的锁");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我在等待obj1锁。。。。");
                synchronized (obj1){
                    System.out.println("我已经拿到了obj1锁,我和开心");
                }
            }
        }

    }
}

    public static void main(String[] args) {
        DeadThread dead = new DeadThread();
        Thread th1 = new Thread(dead,"线程A");
        Thread th2 = new Thread(dead,"线程B");
        th1.start();
        th2.start();
    }

之所以会发生死锁,因为对象锁直接没有良好的“沟通”,导致互相获取对方的锁 ,进入等待中 ,可以通过线程类的几个方法 解决线程之间通信问题

二、线程通信的几个方法

​ wait() : 让当前线程处于等待中,会释放对象锁 ,但是不会自动唤醒,需要其他线程唤醒

​ notify() : 唤醒等待中的一个线程

​ notifyAll: 唤醒所有等待中的线程

他们都属性Object的方法,需要相同的对象 ,使用时 通过Object的对象调用

注意: 以上方法的调用必须满足两个条件: a、他们必须在同步代码块中执行, b、调用该方法的对象是锁对象

案例1:模拟3个人,张飞、李逵和刘备,来买电影票,售票员只有一张5元的钱,电影票5元钱一张。

  • 张飞拿20元一张的人民币排在李逵和刘备的前面,李逵和刘备各拿了一张5元的人民币买票。

    public class TicketThread implements  Runnable{
        //公共资源:
        int fiveCount=1;
        int twentyCount =0;
    
    
        @Override
        public void run() {
            //开始买票  如果是张飞,他是20元面值,其他都是5元
            if(Thread.currentThread().getName().equals("张飞")){
                //20元面值买票
                takeTicket(20);
            }else{
                // 5元面值买票
                takeTicket(5);
            }
    
        }
    
        /**
         * 买票过程   给方法加同步  ,锁对象默认是 方法
         * @param money
         */
        public synchronized void  takeTicket(int money){
            if(money ==20){
                // 验证 当前公共资源 中是否有3张5元
                 while(fiveCount<3){
                     //等待
                     System.out.println(Thread.currentThread().getName()+"不能买到票,要继续等待");
                     try {
                         this.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 // 程序执行这里,说明 fiveCount >=3
                System.out.println(Thread.currentThread().getName()+"正在买票。。");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                fiveCount-=3;
                twentyCount++;
                System.out.println(Thread.currentThread().getName()+"已经买到了票。。");
    
            }else{
                System.out.println(Thread.currentThread().getName()+"正在买票。。");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                fiveCount++;
                System.out.println(Thread.currentThread().getName()+"已经买到了票。。");
                // 唤醒等待的线程
                this.notify();
            }
        }
    
    }
    
    
      public static void main(String[] args) {
             TicketThread ticketThread = new TicketThread();
             Thread th1 = new Thread(ticketThread,"张飞");
            Thread th2 = new Thread(ticketThread,"李逵");
            Thread th3 = new Thread(ticketThread,"刘备");
            //开启线程
            th1.start();
            th2.start();
            th3.start();
        }
    

三、线程的生产者和消费者模式

 多个线程同时运行时,会产生线程并发可使用同步操作确保数据的安全性,如果需要各线程之间交互,可是使用线程等待和唤醒模式,在这里常用的等待唤醒中经典的模式为“生产者和消费者模式” 

 生产者和消费者由两类线程组成:  若干个生产者线程 负责提交用户的请求,若干个消费者线程负责处理生成出来的任务。 他们操作一块共享内存区进行数据通信。

生成/消费的产品(数据): Mobile (手机编号)

生成者线程类: Provider : 无限制的生成手机

消费者线程类:Customer : 无限制的消费手机

​ 共享存储区: Storage ( push 、pop) 存储手机的对象数组

​ 测试类

public class Mobile {
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

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

存储区

package com.j2008.provider_customer;
/**
 *  存储区,它是生产者和消费者共享的空间 ( 生产者和消费者将该对象作为公有锁)
 */
public class Storage {

    // 定义存储手机的对象数据
    Mobile [] mobiles = new Mobile[10];
    int index=0;  // 个数

    static int n=1000;
    /**
     * 存放手机
     * @param mobile
     */
    public synchronized void push(Mobile mobile){
        //考虑容器上限已满,必须等待
        while(index == mobiles.length){
            System.out.println("容器已满,需等待");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //通知消费者取消费 ,将其他线程全部唤醒
        this.notifyAll();
        mobiles[index]=mobile;
        index++;

    }

    /**
     * 取出手机      1 2 3 4
     *              1 2 3
     *              index--;
     *             mobile[index] =null
     * @return 取出的手机对象
     */
    public synchronized Mobile pop(){
        Mobile m = null;

        // 判断index是否小于0
        while(index<=0){
            //等待
            System.out.println("容器中没有手机,需要等待");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        index--;
        m = mobiles[index];
        //将容器中的这个位置 设置为空
        mobiles[index]=null;
        // 通知生产者去生产
        this.notifyAll();
        return m;

    }

    public synchronized int getSize(){
        return index;
    }

}

生产者:

package com.j2008.provider_customer;

public class Provider implements  Runnable {
    //共享存储区
    Storage storage =null;
    public Provider(Storage storage){
        this.storage = storage;
    }


    @Override
    public void run() {
        //手机编号
        int n=1000;
        //一直生产
        while(true){
            Mobile m = new Mobile(n);
            storage.push(m);
            System.out.println(Thread.currentThread().getName()+
                    "生产了一部手机,其编号:"+m.getId()+" 其库存:"+storage.getSize());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            n++;
        }
    }
}

消费者

package com.j2008.provider_customer;

c class Customer implements  Runnable {

    Storage storage=null;
    public Customer(Storage storage){
        this.storage = storage;
    }

    @Override
    public void run() {
        while(true){
            Mobile mobile = storage.pop();
            System.out.println(Thread.currentThread().getName()+
                    "消费了一部手机,编号》》"+mobile.getId()+" 库存:"+storage.getSize());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类

package com.j2008.provider_customer;


public class TestProviderCustomer {
    public static void main(String[] args) {
         //创建公有的存储空间
        Storage storage = new Storage();
        Provider provider1 = new Provider(storage);
        Provider provider2 = new Provider(storage);
        Provider provider3 = new Provider(storage);

        Thread th1 = new Thread(provider1,"张飞");
        Thread th2 = new Thread(provider2,"刘备");
        Thread th3 = new Thread(provider3,"关羽");

        th1.start();
        th2.start();
        th3.start();

        //消费者上场
        Customer customer1 = new Customer(storage);
        Customer customer2 = new Customer(storage);
        Customer customer3 = new Customer(storage);

        Thread th4 = new Thread(customer1,"张飞的老婆");
        Thread th5 = new Thread(customer2,"刘备的老婆");
        Thread th6 = new Thread(customer3,"关羽的老婆");

        th4.start();
        th5.start();
        th6.start();


    }
}

测试结果

关羽生产了一部手机,其编号:1000 其库存:1
张飞的老婆消费了一部手机,编号》》1000 库存:0
张飞生产了一部手机,其编号:1000 其库存:1
刘备生产了一部手机,其编号:1000 其库存:2
刘备的老婆消费了一部手机,编号》》1000 库存:1
关羽的老婆消费了一部手机,编号》》1000 库存:0
容器中没有手机,需要等待
关羽生产了一部手机,其编号:1001 其库存:0
张飞的老婆消费了一部手机,编号》》1001 库存:0
张飞生产了一部手机,其编号:1001 其库存:2
刘备生产了一部手机,其编号:1001 其库存:2
关羽的老婆消费了一部手机,编号》》1001 库存:0
刘备的老婆消费了一部手机,编号》》1001 库存:0
容器中没有手机,需要等待
关羽生产了一部手机,其编号:1002 其库存:0
张飞的老婆消费了一部手机,编号》》1002 库存:0
刘备生产了一部手机,其编号:1002 其库存:2
张飞生产了一部手机,其编号:1002 其库存:2
刘备的老婆消费了一部手机,编号》》1002 库存:1
关羽的老婆消费了一部手机,编号》》1002 库存:0

四、线程池

1、定义

​ 用于创建和管理线程的容器就是线程池 (Thread Pool) ,在线程池中的线程执行完任务后不会立马进入销毁状态,而是重置到线程池中变为“空闲线程” 。 有利于避免频繁创建线程消耗资源,提供线程复用率,有限管理该线程。

2、使用线程池的原因:

​ 在多线程环境下,对于不断创建和销毁效率非常消耗系统资源,对于多线程之间的切换存在线程安全问题, 这是使用统一的管理类管理一些线程是比较好的解决办法

3、线程的运行机制:

  • ​ 在线程池模式下,任务是提交给线程池,由线程池根据当前空闲线程进行分配任务,如果没有空闲线程,由管理类创建线程或者进入任务等待队列中。
  • ​ 一个线程同时只能执行一个任务,但多个任务可以同时提交给这个线程池。

线程池的常用类 (ExecutedService)

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());

参数1: corePoolSize:核心线程数

参数2:maximumPoolSize :最大线程数

参数3: keepAliveTime : 线程活动时长

参数4: 对于参数3的单位

1、可缓存的线程池 newCacheThreadPool(n);如果线程池中没有空闲线程,则创建新线程并放入线程池中,无上限线程数,如果有空闲线程则直接使用该线程

 public static void main(String[] args) {
        // 创建可缓存线程
        ExecutorService service =Executors.newCachedThreadPool();
        // 创建10个线程
        int n=0;
        for(int i = 0 ;i < 10;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            service.execute(new Runnable() {
                @Override  //你们内部类
                public void run() {
                      //任务
                    System.out.println(Thread.currentThread().getName()+"---"+n);
                    try {

                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
            //由于以上的线程 每次执行完成只需要500毫秒 ,会释放线程对象到线程池中
            //  1000毫秒后创建的线程 就会复用上一次的线程对象


        }

        //关闭线程池
        service.shutdown();
    }
}

2、可重用的固定线程池: newFixedThreadPool(n) ,线程数量固定,如果没有空闲线程,则存放无界队列中等待

 public static void main(String[] args) {
         //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        // 连续创建10个线程, 由于只有3个线程,所有线程只能等待
        // 2秒后再执行3个
        for(int i =0;i<10;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"正在执行");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        // 结果: 每2秒执行3个线程 
    }

3、 固定长度的可执行定时任务的线程池 newScheduledThreadPool , 类似于定时器线程

   scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                     public void run() {
                         System.out.println("延迟1秒后每3秒执行一次");
                     }
                }, 1, 3, TimeUnit.SECONDS);

4、单线程的线程池 newSingleThreadExecutor : 线程池中只有一个线程数,所有的任务都通过该线程执行,它可以保证所有的任务是FIFO模式, 满足队列结构

posted @ 2020-11-10 20:48  落雨♡̶初晴  阅读(145)  评论(0)    收藏  举报