多线程之线程同步与协助

多线程

1.线程同步

1.1线程同步:多个线程操作同一个资源

  • 并发: 同一个对象被多个线程同时操作

    • 现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队一个个来。

    • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步.线程同步其实就是一种等待机制,多个需要同时访问!此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

  • 形成线程安全的条件:队列+锁锁机制【synchronized】

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可

  • 锁机制存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起。
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

1.2三大不安全案例

1.21 不安全买票代码演示

package threads.demo03;
//不安全的买票
//线程不安全,有负数


public class UnsafeBuyTicket {
    public static void main(String[] args) {

        BuyTicket bt = new BuyTicket();

        new Thread(bt,"牛逼的黑客").start();
        new Thread(bt,"可恶的黄牛").start();
        new Thread(bt,"傻傻的我").start();


    }
}


class BuyTicket implements Runnable{

    //票
    private int tikcetNum=10;
    boolean flag=true; //外部停止方式
    @Override
    public void run() {
        //买票
             while (flag){
                 try {
                     buytick();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

    }

    void buytick() throws InterruptedException {
        //判断是否有票
         if(tikcetNum<=0){
             flag=false;
             return;
         }
         //模拟延时
         Thread.sleep(100);
         //买票
        System.out.println(Thread.currentThread().getName()+"买到了第"+tikcetNum--+"张票");
    }


}

运行结果

牛逼的黑客买到了第10张票
可恶的黄牛买到了第9张票
傻傻的我买到了第10张票
傻傻的我买到了第8张票
牛逼的黑客买到了第8张票
可恶的黄牛买到了第8张票
傻傻的我买到了第7张票
可恶的黄牛买到了第6张票
牛逼的黑客买到了第5张票
可恶的黄牛买到了第4张票
牛逼的黑客买到了第3张票
傻傻的我买到了第2张票
牛逼的黑客买到了第1张票
可恶的黄牛买到了第0张票
傻傻的我买到了第-1张票

1.22 不安全的取钱代码演示

package threads.demo03;
//不安全的取钱
//两个人去银行取钱,同一账户
public class UnSafeBank {
    public static void main(String[] args) {

        // 账户
        Acount acount = new Acount(200, "结婚基金");

        Drawing medrawing = new Drawing(acount, 100, "我");
        Drawing wifedrawing = new Drawing(acount, 200, "老婆");

        medrawing.start();
        wifedrawing.start();



    }

}



//账户
class Acount{
    int money;// 余额
    String acountname;// 卡名

    public Acount(int money, String acountname) {
        this.money = money;
        this.acountname = acountname;
    }
}

//银行:模拟取款

class Drawing extends Thread{

    private Acount acount;// 账户
    int nowMoney;// 还剩多少钱
    int drawMoney;// 取了多少钱

    public Drawing(Acount acount, int drawMoney,String name) {
        super(name);
        this.acount = acount;
        this.drawMoney = drawMoney;
    }


    //取钱操作
    @Override
    public void run() {
        // 判断有没有钱
        if(acount.money-drawMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }

        // sleep 放大问题的发生性,让另一个线程在计算之前进入判断
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 卡内余额 = 余额 - 你取的钱
        acount.money=acount.money-drawMoney;

        // 你手里的钱
        nowMoney=drawMoney+nowMoney;
        System.out.println(acount.acountname+"账户余额为:"+acount.money);

        // 此时 Thread.currentThread().getName() = this.getName()
        System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney);


    }
}

运行结果

结婚基金账户余额为:-100
结婚基金账户余额为:-100
我手里的钱:100
老婆手里的钱:200


1.23 线程不安全的集合代码演示

package threads.demo03;

import java.util.ArrayList;
//线程不安全的集合
public class UnSafeList {
    public static void main(String[] args) {

        final ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    strings.add(Thread.currentThread().getName());
                }
            }).start();

        }


        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {

        }

        System.out.println(strings.size());

    }
}


运行结果

9999

1.3同步方法及同步块

  • 同步方法

    • 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法: synchronized 和 synchronized 块

    • 同步方法: public synchronized void method(int args ){}

  • synchronized 方法控制 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行。

  • 缺陷:若将一个大的方法申明为 synchronized 将会影响效率。方法里面需要修改的内容才需要锁,锁的太多,浪费资源

  • 同步块 synchroized(Obj){}
  • Obj 称为:同步监视器

    • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需制定同步监视器,因为同步方法的同步监视器是 this,就是这个对象本身,或是 class;
  • 同步监视器的执行过程

    • 1.第一个线程访问,锁定同步监视器,执行其中代码
    • 2.第二个线程访问,发现同步监视器被锁定,无法访问
    • 3.第一个线程访问完毕,解锁同步监视器
    • 4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问

1.31 使用同步方法synchronized安全买票代码演示

package threads.demo03;
//安全的买票 同步方法   synchronized 默认锁的是this.
public class SynchronizedBuyTicket {
    public static void main(String[] args) {

        Buyticket buyT = new Buyticket();

        new Thread(buyT,"牛逼的黑客").start();
        new Thread(buyT,"可恶的黄牛").start();
        new Thread(buyT,"傻傻的我").start();


    }
}


class Buyticket implements Runnable{

    //票
    private int tikcetNum=10;
    boolean flag=true; //外部停止方式
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buytick();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //synchronized 同步方法,锁的是方法的调用者,对象本身  this
    synchronized void buytick() throws InterruptedException {
        //判断是否有票
        if(tikcetNum<=0){
            flag=false;
            return;
        }
        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"买到了第"+tikcetNum--+"张票");
    }
}


运行结果

牛逼的黑客买到了第10张票
牛逼的黑客买到了第9张票
牛逼的黑客买到了第8张票
牛逼的黑客买到了第7张票
可恶的黄牛买到了第6张票
可恶的黄牛买到了第5张票
傻傻的我买到了第4张票
傻傻的我买到了第3张票
可恶的黄牛买到了第2张票
牛逼的黑客买到了第1张票


1.32 使用同步块安全的取钱代码演示

package threads.demo03;
//安全的取钱  同步块
public class SynchronizedBank {
    public static void main(String[] args) {

        // 账户
        acount count = new acount(200, "结婚基金");

        Drawinga medrawing = new Drawinga(count, 100, "我");
        Drawinga wifedrawing = new Drawinga(count, 200, "老婆");

        medrawing.start();
        wifedrawing.start();



    }



}



//账户
    class acount{
        int money;// 余额
        String acountname;// 卡名

        public acount(int money, String acountname) {
            this.money = money;
            this.acountname = acountname;
        }
    }

//银行:模拟取款

class Drawinga extends Thread{

    private acount count;// 账户
    int nowMoney;// 还剩多少钱
    int drawMoney;// 取了多少钱

    public Drawinga(acount count, int drawMoney,String name) {
        super(name);
        this.count = count;
        this.drawMoney = drawMoney;
    }





    //取钱操作
    @Override
    public void run() {



        //synchronized 默认锁的是Drawinga的对象本身,既this ,但是改变的是账户余额,所以需要使用同步块
        //同步块可以锁任何的东西
        //什么增删改查,就锁什么。锁住的是:同步操作的对象,也就是共享的资源


        //锁的对象就是变化的量,需要增删改的对象
        synchronized (count){
            // 判断有没有钱
            if(count.money-drawMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }

            // sleep 放大问题的发生性,让另一个线程在计算之前进入判断
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 卡内余额 = 余额 - 你取的钱
            count.money=count.money-drawMoney;

            // 你手里的钱
            nowMoney=drawMoney+nowMoney;
            System.out.println(count.acountname+"账户余额为:"+count.money);

            // 此时 Thread.currentThread().getName() = this.getName()
            System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney);


         }
        }

}


运行结果

结婚基金账户余额为:100
我手里的钱:100
老婆钱不够,取不了


1.33 使用同步块安全的集合代码演示

package threads.demo03;

import java.util.ArrayList;
//安全类型的集合
public class SynchronizedList {
    public static void main(String[] args) {

        final ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {

                    synchronized (strings){
                        strings.add(Thread.currentThread().getName());
                    }

                }
            }).start();

        }

        //添加了同步块锁,但是如果不sleep,就会导致线程没执行完就打印了,主线程跑的太快跑完停止了
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {

        }

        System.out.println(strings.size());

    }
}



运行结果

10000

1.34 测试JUC安全类型的集合代码演示

package threads.demo03;

import java.util.concurrent.CopyOnWriteArrayList;

//测试juc 安全类型的集合
public class TestJuc {
    public static void main(String[] args) {

        //juc 里面安全类型的集合
        final CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    strings.add(Thread.currentThread().getName());
                }
            }).start();

        }

        //为了让主线程休眠让其他线程跑完
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(strings.size());

    }
}



运行结果

10000

1.4死锁

  • 什么是死锁

    • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情况。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
  • 死锁避免方法

  • 产生死锁的四个必要条件:

    • 1.互斥条件:一个资源每次只能被一个进程使用

    • 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

    • 3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

    • 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

  • 上述四个条件,只要破坏其任意一个就可避免死锁的发生。

1.41 死锁及解决死锁代码演示

package threads.demo04;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

//死锁:多个线程相互抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {

        Makeup bai = new Makeup(0, "白雪公主");
        Makeup hui = new Makeup(1, "灰姑娘");

        bai.start();
        hui.start();


    }
}

class Lipstick{

}

class Mirror{


}

class Makeup extends Thread {

    // 需要的资源只有一份,用static来保证只有一份
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();

    int choice;// 选择
    String girname;// 使用化妆品的人

    public Makeup(int choice, String girname) {
        this.choice = choice;
        this.girname = girname;
    }


    @Override
    public void run() {
        // 化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 化妆 互相持有对方的锁,就是需要拿到对方的资源
    public void makeup() throws InterruptedException {

        if (choice == 0) {
            // 获得镜子的锁
            synchronized (mirror) {
                System.out.println(this.girname + "获得镜子的锁");
                Thread.sleep(1000);
                /*
                synchronized (lipstick) {
                    System.out.println(this.girname + "获得口红的锁");
                }
                */
            }
            // 获得口红的锁
            synchronized (lipstick) {
                System.out.println(this.girname + "获得口红的锁");
            }
        } else {
            // 获得口红的锁
            synchronized (lipstick) {
                System.out.println(this.girname + "获得口红的锁");
                Thread.sleep(2000);
                /*
                synchronized (mirror) {
                    System.out.println(this.girname + "获得镜子的锁");
                }
                */

            }
            // 获得镜子的锁
            synchronized (mirror) {
                System.out.println(this.girname + "获得镜子的锁");
            }
        }

    }
}

运行结果

灰姑娘获得口红的锁
白雪公主获得镜子的锁
白雪公主获得口红的锁
灰姑娘获得镜子的锁


1.5Lock锁

  • 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用 Lock对象充当

  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象

  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock ,可以显示加锁、释放锁。

class Demo{
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        lock.lock();
        try{
            // 保证线程安全的代码
        }finall {
            lock.unlock();
            // 如果同步代码有异常,将 unlock() 写入 finally 块中
        }
    }
}
  • synchronized与Lock的对比
    • Lock是显示锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域会自动释放

    • Lock只有代码块锁,synchronized有代码块锁和方法锁

    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

    • 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应的资源)> 同步方法(在方法体之外)

1.51 测试lock锁代码演示

package threads.demo04;

import java.util.concurrent.locks.ReentrantLock;

//测试lock锁
public class LockTest {
    public static void main(String[] args) {

        LockTest2 lockTest2 = new LockTest2();

        new Thread(lockTest2).start();
        new Thread(lockTest2).start();
        new Thread(lockTest2).start();
    }
}


class LockTest2 implements Runnable{

    int tickNums=10;

     private final ReentrantLock lock= new ReentrantLock();//定义lock锁

    @Override
    public void run() {


        while (true){
            lock.lock();//加锁
            try {
                if(tickNums>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(tickNums--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();//解锁
            }



        }


    }
}

运行结果

10
9
8
7
6
5
4
3
2
1


2. 线程通信

2.1 线程协作:生产者消费者模式

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

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

方法名 作用
  wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
  wait(long timeout) 指定等待的毫秒数
  notify() 唤醒一个处于等待状态的线程
  notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
  • 注意: 均是Object类,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegaIMoniorStateException

  • 解决方式1: 并发协作模型“生产者/消费者模式”—>管程法

    • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
    • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
    • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区
      • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
  • 解决方式 2: 并发协作模型“生产者/消费者模式”—>信号灯法

    • 使用一个 boolean flag 来控制是否操作,相当于缓冲区只有 1 的管程法

2.11 测试生产者消费者模型-->利用缓冲区解决:管程法代码演示

package threads.demo05;

//测试:生产者消费者模型————>利用缓存区解决:管程法

//生产者,消费者  缓存区  产品
public class TestPC {
    public static void main(String[] args) {

        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();

    }
}

//生产者
class Productor extends Thread{

    SynContainer container;
    public Productor(SynContainer container){
        this.container=container;
    }


    //生产
    @Override
    public void run() {

        for (int i = 0; i < 30; i++) {


            try {
                container.puch(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产了"+i+"只鸡");

        }
        
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container=container;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            try {
                System.out.println("消费了"+container.pop().chickenid+"只鸡");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }
    }
}

//产品
class Chicken{
    int chickenid;//产品编号

    public Chicken(int chickenid) {
        this.chickenid = chickenid;
    }
}
//缓冲区
class SynContainer{
    //需要一个容器大小
   Chicken[] chickens= new Chicken[10];
   //容器计数器
   int count=0;

   //生产者放入产品
   public synchronized void puch(Chicken chicken) throws InterruptedException {

       //如果容器满了就需要等待消费者消费
       if(count==chickens.length){
           // 通知消费者消费,生产等待
           this.wait();

       }

       // 如果没有满,需要丢入产品
       chickens[count]=chicken;
       count++;
       // 可以通知消费者消费了
       this.notify();

    }

    // 消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        // 判断能否消费
       if(count==0){
           // 等待生产者生产,消费者等待
           this.wait();
       }

        // 如果可以消费
        count--;
        Chicken chicken1 = chickens[count];

        // 吃完了,通知生产者生产
        this.notify();
        return chicken1;

    }




}

运行结果

生产了0只鸡
生产了1只鸡
生产了2只鸡
生产了3只鸡
生产了4只鸡
生产了5只鸡
生产了6只鸡
生产了7只鸡
生产了8只鸡
生产了9只鸡
生产了10只鸡
消费了9只鸡
消费了10只鸡
生产了11只鸡
消费了11只鸡
生产了12只鸡
消费了12只鸡
生产了13只鸡
消费了13只鸡
生产了14只鸡
消费了14只鸡
生产了15只鸡
消费了15只鸡
生产了16只鸡
消费了16只鸡
生产了17只鸡
消费了17只鸡
生产了18只鸡
消费了18只鸡
生产了19只鸡
消费了19只鸡
生产了20只鸡
消费了20只鸡
消费了21只鸡
生产了21只鸡
消费了8只鸡
生产了22只鸡
消费了22只鸡
生产了23只鸡
消费了23只鸡
生产了24只鸡
消费了24只鸡
生产了25只鸡
消费了25只鸡
生产了26只鸡
消费了26只鸡
生产了27只鸡
消费了27只鸡
生产了28只鸡
消费了28只鸡
生产了29只鸡
消费了29只鸡
消费了7只鸡
消费了6只鸡
消费了5只鸡
消费了4只鸡
消费了3只鸡
消费了2只鸡
消费了1只鸡
消费了0只鸡

2.12 测试生成者消费者问题2:信号灯法,标志位解决代码演示


package threads.demo05;
//测试生成者消费者问题2:信号灯法,标志位解决
public class TestPc2 {
    public static void main(String[] args) {

          Tv tv1 = new Tv();
          new Player(tv1).start();
          new Watcher(tv1).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){
               this.tv.play("节目一:快乐大本营");
            }else {
                this.tv.play("节目:中国好声音");
            }
            
        }
    }
}
//消费者-->观众


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{
    // 演员表演,观众等待 T
    // 观众观看,演员等待 F
    String voice;//表演的节目
    boolean flag=true;

    //表演
    public synchronized void play(String voice){
            if(!flag){

                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        System.out.println("演员表演了:"+voice);

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


    }


    //观看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("观众观看了:"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag=!this.flag;
        
    }



}


运行结果

演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音
演员表演了:节目一:快乐大本营
观众观看了:节目一:快乐大本营
演员表演了:节目二:中国好声音
观众观看了:节目二:中国好声音


2.2 使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • 使用线程池
  • JDK 5.0起提供了线程池相关 API:ExecutorService 和 Executors
  • ExecutorService:真正的线程池接口,常见子类 ThreadPoolExecutor
    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行 Runnavle
    • void shutdown() :关闭连接池
    • < T >Future< T >submit(Callable< T > task) :执行任务,有返回值,一般用来执行 Callable
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

2.21 测试线程池代码演示

package threads.demo05;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
    public static void main(String[] args) {

        //1.创建服务,创建线程池
        //newFixedThreadPool   参数为线程池大小
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        //执行
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());

        //2.关闭连接
        executorService.shutdown();


    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果

pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-5



更多参考

狂神说java-多线程详解

posted @ 2022-08-31 14:09  哼哼哈¥  阅读(59)  评论(0编辑  收藏  举报