多线程

1. 程序、进程与线程

  • 程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 进程:是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。
  • 线程:在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

2. 核心概念

  • 线程就是独立的执行路径。
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。
  • main()称之为主线程,为系统的入口,用于执行整个程序。
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器时与操作系统密切相关的,先后顺序是不能人为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

3. 线程运行

run(), start()

4. 线程创建的方式

  1. 继承Thread类

    1. start0(),本地方法:Java无权调用,会交给底层的c处理。
  2. 实现Runnable接口(函数式接口,可用lambda表达式)

    new Thread(()->{
    
    }).start();
    
  3. 实现Callable接口(可以有返回值)

5. 静态代理

new Thread(Runnable).start();

6. 线程的状态

  1. 新建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 死亡
img
Image
img
Image

线程状态Thread.State

线程状态。 线程可以处于以下状态之一:

  • NEW 尚未启动的线程处于此状态。
  • RUNNABLE 在Java虚拟机中执行的线程处于此状态。
  • BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
  • WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED 已退出的线程处于此状态。

线程如为终止状态,则不能再运行,否则报错。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("running");
        });

        System.out.println(thread.getState());

        thread.start();
        System.out.println(thread.getState());

        while (Thread.State.TERMINATED != thread.getState()) {
            Thread.sleep(100);
            System.out.println(thread.getState());
        }

        System.out.println(thread.getState());

    }
}

6.1. 常用的方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
sataic void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

6.2. 停止线程

  • 不推荐使用JDK提供的stop()、destory()方法。
  • 推荐线程自己停下来
    • 建议使用一个标志位进行终止变量当flag = flase,则终止线程运行。
public class TestStop implements Runnable{

    // 1.设置一个标识位
    private boolean flag = true;

    @Override
    public void run(){
        int i = 0;
        while(flag){
            System.out.println("run ..... Thread"+i++);
        }
    }

    // 2.设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {

        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i=0;i<1000;i++){
            System.out.println("main"+i);
            if (i==900){
                // 调用 stop方法切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程该停止了!!!");
            }
        }
    }
}

6.3. 线程休眠

  • sleep(时间) 指定当前线程阻塞的毫秒数。
  • sleep存在异常Interrupted Exception。
  • sleep时间达到后线程进入就绪状态。
  • sleep可以模拟网络延时、倒计时等。
  • 每一个对象都有一个锁,sleep不会释放锁。

线程休眠可以放大问题发生的随机性。

public class Test implements Runnable {

    // 票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            // 模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
        }
    }

    public static void main(String[] args) {
        Test ticket = new Test();
        new Thread(ticket, "小明").start();
        new Thread(ticket, "老师").start();
        new Thread(ticket, "黄牛").start();
    }
}

利用sleep()方法模拟倒计时

public class Test {

    public static void main(String[] args) throws InterruptedException {
        timeDown();
    }

    private static void timeDown() throws InterruptedException {
        int cnt = 10;
        while (true) {
            System.out.println(cnt--);
            Thread.sleep(1000);
            if (cnt < 0) {
                break;
            }
        }
    }
}

利用sleep方法显示当前时间

public class Test {
    public static void main(String[] args) throws InterruptedException {
        timeCount();
    }

    private static void timeCount() throws InterruptedException {
        int cnt = 10;
        while (cnt >= 0) {
            Date date = new Date(System.currentTimeMillis());
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(simpleDateFormat.format(date));
            cnt--;
            Thread.sleep(1000);
        }
    }
}

6.4. 线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞。
  • 将线程从运行状态转为就绪状态。
  • 让cpu重新调度,礼让不一定成功,即礼让的线程有可能会被重新调度。
public class Test {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield, "A").start();
        new Thread(myYield, "B").start();
    }
}

class MyYield implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "停止执行");
    }
}

6.5. 线程合并(插队join)

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
  • 可以想象成插队。
public class Test implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程vip来了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        //启动我们的线程
        Test testJoin = new Test();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                thread.join();//插队
            }
            System.out.println("main" + i);
        }
    }
}

6.6. 线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1~10。
Modifier and Type Field and Description
static int MAX_PRIORITY线程可以拥有的最大优先级。
static int MIN_PRIORITY线程可以拥有的最小优先级。
static int NORM_PRIORITY分配给线程的默认优先级。
  • 使用以下方式改变或获取优先级
    • getPriority(), setPriority(int newPriority)

优先级的设定应在start()调度之前。

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。具体由CPU决定。

public class Test {

    public static void main(String[] args) {
        // 主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        // 先设置优先级,再启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();


        t5.setPriority(8);
        t5.start();

        t6.setPriority(7);
        t6.start();
    }
}

class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

6.7. 守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕。
  • 虚拟机不用等待守护线程执行完毕。
  • 常见的守护线程有:后台记录操作日志、监控内存、垃圾回收等待。
public class Test {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true); // 设置为守护线程
        thread.start();

        new Thread(you).start();

    }
}

class God implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("God bless you...");
        }
    }
}

class You implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("You passed a day");
        }
    }
}

7. 线程同步【重点】

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

7.1. 并发

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

并发问题

多个线程同时操作一个资源的情况下,线程不安全,数据紊乱。

并发的案例

  1. 购票

下列代码有产生负数,或者多个人抢到同张票的风险。

public class Demo5 implements Runnable {

    int ticketNum = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                break;
            }
            // 模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "---->拿到了第" + ticketNum-- + "张票");
        }
    }

    public static void main(String[] args) {
        Demo5 demo5 = new Demo5();

        new Thread(demo5, "线程1").start();
        new Thread(demo5, "线程2").start();
        new Thread(demo5, "线程3").start();
    }
}
/*
线程1---->拿到了第10张票
线程2---->拿到了第9张票
线程3---->拿到了第8张票
线程2---->拿到了第7张票
线程1---->拿到了第7张票
线程3---->拿到了第6张票
线程2---->拿到了第5张票
线程1---->拿到了第4张票
线程3---->拿到了第3张票
线程1---->拿到了第1张票
线程3---->拿到了第0张票
线程2---->拿到了第2张票
*/
  1. 取款

可能取款钱数超过账户钱数

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

        Account account = new Account("张三", 100);
        new Thread(new Drawing(account, 100), "张三").start();
        new Thread(new Drawing(account, 50), "李四").start();

    }
}

class Account {
    private String name;
    private int money;

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class Drawing implements Runnable {

    Account account;
    int drawMoney;

    public Drawing(Account account, int drawMoney) {
        this.account = account;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {
        if (account.getMoney() - drawMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "余额不足");
            return;
        }
        // 模拟休眠
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.setMoney(account.getMoney() - drawMoney);
        System.out.print(Thread.currentThread().getName() + "取出了" + drawMoney + "元 ");
//        System.out.println("当前账户余额" + account.getMoney() + "元");
    }
}
  1. ArrayList是线程不安全的

可能list大小不足10000

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
  • 由于同一进程的多个线程共享同一快存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程被挂起。
    • 在多线程的竞争下,加锁、释放锁会导致比较多的上下文切换调度延时,引起性能问题。
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

7.2. 线程同步的方法

  1. 前提:队列+锁

  2. synchronized

    1. 同步方法(弊端:锁太多了)(优先级最低)

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

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

        方法里面修改的内容才需要锁,锁的太多,浪费资源。

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

        缺陷:若将一个大的方法申明为synchronized将会影响效率。

    2. 同步代码块(常用)(优先级次等)

      • 同步块:synchronized(Obj){}
      • Obj称之为同步监视器
        • obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
        • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
      • 同步监视器的执行过程
        1. 第一个线程访问,锁定同步监视器,执行其中的代码。
        2. 第二个线程访问,发现同步监视器被锁定,无法访问。
        3. 第一个线程访问完毕,解锁同步监视器。
        4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

      上述三个案例改成线程安全:

      1. 买票

        public class Demo5 implements Runnable {
        
            int ticketNum = 10;
        
            @Override
            public void run() {
                buyTicket();
            }
        
              private synchronized void buyTicket(){
              while (true) {
                    if (ticketNum <= 0) {
                        break;
                    }
                    // 模拟延时
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "---->拿到了第" + ticketNum-- + "张票");
                }
            }
        
            public static void main(String[] args) {
                Demo5 demo5 = new Demo5();
        
                new Thread(demo5, "线程1").start();
                new Thread(demo5, "线程2").start();
                new Thread(demo5, "线程3").start();
            }
        
      2. 取款

        public class Test {
            public static void main(String[] args) {
        
                Account account = new Account("张三", 100);
                new Thread(new Drawing(account, 100), "张三").start();
                new Thread(new Drawing(account, 50), "李四").start();
        
            }
        }
        
        class Account {
            private String name;
            private int money;
        
            @Override
            public String toString() {
                return "Account{" +
                        "name='" + name + '\'' +
                        ", money=" + money +
                        '}';
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public int getMoney() {
                return money;
            }
        
            public void setMoney(int money) {
                this.money = money;
            }
        
            public Account(String name, int money) {
                this.name = name;
                this.money = money;
            }
        }
        
        class Drawing implements Runnable {
        
            Account account;
            int drawMoney;
        
            public Drawing(Account account, int drawMoney) {
                this.account = account;
                this.drawMoney = drawMoney;
            }
        
            @Override
            public void run() {
               synchronized(account) {
                  if (account.getMoney() - drawMoney < 0) {
                    System.out.println(Thread.currentThread().getName() + "余额不足");
                    return;
                }
                // 模拟休眠
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setMoney(account.getMoney() - drawMoney);
                System.out.print(Thread.currentThread().getName() + "取出了" + drawMoney + "元 ");
        //        System.out.println("当前账户余额" + account.getMoney() + "元");
               }
            }
        }
        
      3. list集合

        1. 加锁
        public class Test {
            public static void main(String[] args) {
                ArrayList<String> list = new ArrayList<>();
                for (int i = 0; i < 10000; i++) {
                    synchronized(list) {
                      new Thread(()->{
                        list.add(Thread.currentThread().getName());
                    }).start();
                    }
                }
        
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(list.size());
            }
        }
        
        1. 使用JUC下的CopyOnWriteArrayList,线程安全实现类
        public class Test {
            public static void main(String[] args) {
                CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
                for (int i = 0; i < 10000; i++) {
                    new Thread(()->{
                        list.add(Thread.currentThread().getName());
                    }).start();
                }
        
                System.out.println(list.size());
        
            }
        }
        

7.3. 死锁

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

  • 条件:

    1. 互斥:一个资源每次只能被一个进程使用。
    2. 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,只要想办法破其中的一个或多个条件,就可以避免死锁的发生。

死锁的案例

public class Test {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0, "g1");
        MakeUp g2 = new MakeUp(1, "g2");

        new Thread(g1).start();
        new Thread(g2).start();

    }
}

class Lipstick {

}

class Mirror {

}

class MakeUp implements Runnable {
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choose;
    String girlName;

    public MakeUp(int choose, String girlName) {
        this.choose = choose;
        this.girlName = girlName;
    }


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

    private void makeup() throws InterruptedException {
        if (choose == 0) {
            synchronized (lipstick) {
                System.out.println(girlName + "---->" + "Lipstick");
                Thread.sleep(100);
                synchronized (mirror) {
                    System.out.println(girlName + "---->" + "Mirror");
                }
            }
        } else {
            synchronized (mirror) {
                System.out.println(girlName + "---->" + "mirror");
                Thread.sleep(100);
                synchronized (lipstick) {
                    System.out.println(girlName + "---->" + "lipstick");
                }
            }
        }
    }
}

解决方法

private void makeup() throws InterruptedException {
  if (choose == 0) {
    synchronized (lipstick) {
      System.out.println(girlName + "---->" + "Lipstick");
      Thread.sleep(100);
    }
    synchronized (mirror) {
      System.out.println(girlName + "---->" + "Mirror");
    }
  } else {
    synchronized (mirror) {
      System.out.println(girlName + "---->" + "mirror");
      Thread.sleep(100);
    }
    synchronized (lipstick) {
      System.out.println(girlName + "---->" + "lipstick");
    }
  }
}

7.4. Lock (优先级最高)

  • 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问资源之前应先获得Lock对象。
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

(ReentrantLock)

  1. lock()
  2. trylock()
  3. unlock()
public class Test {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();

    }
}

class BuyTicket implements Runnable {

    int ticketNum = 10;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            lock.lock();
            buyTicket();
        } finally {
            lock.unlock();
        }

    }

    private void buyTicket() {
        while (ticketNum >= 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(ticketNum--);
        }
    }
}

7.5. synchronized与Lock对比

  • Lock是显式锁,(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
  • 优先使用顺序;
    • Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体之外)

8. 线程通信

8.1. 生产者消费者模式

  • 应用场景:生产者和消费者问题
    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,由需要马上通知消费者消费。
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者消费者问题中,仅有synchronized是不够的。
    • synchronized可阻止并发更新同一个共享资源,实现了同步。
    • synchronized不能用来实现不同线程之间的消息传递(通信)。
方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁。
wait(long timeout) 指定等待的毫秒数。
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度。

注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。

解决方式

  1. 并发协作模型“生产者/消费者模式” --> 管程法

    • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)。
    • 消费者:负责处理数据的模块(kennel是方法、对象、线程、进程)。
    • 缓冲区:消费者并不能直接使用生产者的数据,他们之间有个“缓冲区”,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
    image-20210822001523514
    Image
public class TestPC {
    public static void main(String[] args) {
        Container container = new Container();
        new Producer(container).start();
        new Consumer1(container).start();

    }
}

class Producer extends Thread {
    private Container container;

    public Producer(Container container) {
        this.container = container;
    }


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

class Consumer1 extends Thread {
    private Container container;

    public Consumer1(Container container) {
        this.container = container;
    }

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

class Product {
    int id;

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

    public int getId() {
        return id;
    }

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

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                '}';
    }
}

class Container {
    Product[] products = new Product[10];
    int count = -1;

    // 生产
    public synchronized void push(Product product) {
        if (count == products.length - 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        products[++count] = product;
        this.notifyAll();
    }

    //消费
    public synchronized Product pop() {
        if (count == -1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Product product = products[count--];
        this.notifyAll();
        return product;
    }
}
  1. 并发协作模型“生产者/消费者模式” --> 信号灯法
public class TestPC2 {
    public static void main(String[] args) {
        Product1 product = new Product1();
        new Thread(new Producer1(product)).start();
        new Thread(new Consumer2(product)).start();

    }
}

class Producer1 implements Runnable {
    Product1 product;

    public Producer1(Product1 product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("生产了" + i + "件产品");
                product.push(i + "");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer2 implements Runnable {
    Product1 product;

    public Consumer2(Product1 product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("消费了" + product.pop() + "件产品");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Product1 {
    boolean needToProduce = true;
    String productName;

    public synchronized void push(String productName) throws InterruptedException {
        if (!needToProduce) {
            this.wait();
        }
        this.notifyAll();
        this.productName = productName;
        needToProduce = !needToProduce;
    }

    public synchronized String pop() throws InterruptedException {
        if (needToProduce) {
            this.wait();
        }
        this.notifyAll();
        needToProduce = !needToProduce;
        return productName;
    }
}

9. 线程池(Pool)

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中,可以避免频繁创建销毁,实现重复利用。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池大小。
      • maximumPoolSize:最大线程数。
      • keepAliveTime:线程没有任务时最多保持多长时间会终止。
  • JKD5.0起提供了线程池相关API:ExecutorService和Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable。
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable。
    • void shutdown()关闭连接池。
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.shutdown();
    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • ExecutorService的实现类之一ThreadPoolExecutor
Constructor and Description
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂和拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)创建一个新的 ThreadPoolExecutor与给定的初始参数和默认拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)创建一个新 ThreadPoolExecutor给定的初始参数。
ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 10, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  1. 池化技术
  2. 池的大小
  3. 最大连接数
  4. 保持时间
  5. ...
Copyright © rootwhois.cn 2021-2022 all right reserved,powered by GitbookFile Modify: 2023-03-05 10:55:52

results matching ""

    No results matching ""