Java 线程

214 阅读10分钟

线程

线程(Thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

需要通过java.lang.Thread类来实现。

Thread类的特征

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
  • 通过该Thread对象的start()方法来调用这个线程

创建和启动

两种创建方式,另一种实现Callable接口,只需了解

  1. 继承Thread类(局限于Java的单继承限制,不推荐使用

    1. 定义子类继承Thread
    2. 子类重写Thread类的run()方法
    3. 创建Thread子类对象,即创建了线程对象
    4. 调用线程对象的start()方法:启动线程,调用run()方法
    public class Test{
      public static void main(String[] args){
        Thread t = new TestThread();
        t.start();//调用了run方法,run方法中的代码会执行,但main方法也会继续,所以输出可能会互相穿插
        //并且可能每次运行的结果都不同,看性能变化
        System.out.println("--------");
        System.out.println("--------");
        System.out.println("--------");
      }
    }
    
    class TestThread extends Thread{
      @Override
      public void run(){
        //此处放需要多线程运行的代码
        for(int i = 0; i < 10; i++){
          System.out.println("执行了"+i);
        }
      }
    }
    

image.png

  1. 实现Runnable接口**(推荐使用)**

    1. 定义子类,实现Runnable接口
    2. 子类重写Runnable接口中的run()方法
    3. 通过Thread有参构造函数创建线程对象
    4. Runnable接口的子类对象作为实际参数传给Thread类的构造方法中
    5. 调用Thread类的start()方法:开启线程,调用Runnable子类接口的run()方法
    public class Test{
      psv main(){
        Thread t = new Thread(new TestRunnable());
        Thread t2 = new Thread(new TestRunnable(),"thread-1");//第二个参数可以设置线程名称
        t.setPriority(1);
        t2.setPriority(10);
        //这种创建方法,可以让多个线程处理同一个TestRunnable对象
        t.start();
      }
    }
    class TestRunnable implements Runnable{
      @Override
      public void run(){
        //此处放需要多线程运行的代码
        for(int i = 0; i < 10; i++){
          Thread.sleep(1000);//一秒循环一次
          if( i%2 == 0){
            Thread.yield();//线程让步
          }
          System.out.println( Thread.currentThread().getName() + "线程执行了" + i);
          //线程名称可以这么使用
        }
      }
    }
    

两种创建方式的区别

  • 继承 Thread:线程代码存放 Thread子类run方法中。重写run方法
  • 实现 Runnable:线程代码存在接口的子类的run方法。实现run方法
  • 一般使用实现接口的方法创建

实现方法的好处

  1. 避免了单继承的局限性
  2. 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

Callable接口创建(了解)

package com.volcano.thread;

import java.util.concurrent.*;

public class TestCallable {
    public static void main(String[] args) {
        test1();
        test2();
    }
    static void test2(){
        System.out.println("测试方式2");
        CallableThread thread = new CallableThread();
        ExecutorService service= Executors.newFixedThreadPool(1);
        Future result = service.submit(thread);
        try {
            Integer num = (Integer) result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdownNow();
    }
    static void test1(){
        System.out.println("测试方式1");
        FutureTask futureTask = new FutureTask(new CallableThread());
        new Thread(futureTask).start();
        try {
            Integer integer = (Integer) futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class CallableThread implements Callable{
    //重写call方法,必须要有返回值
    @Override
    public Integer call(){
        System.out.println("CallableThread");
        return 11;
    }
}

静态代理模式

package com.volcano.thread;
//静态代理模式
//真实对象和代理对象要实现同一个接口
//真实对象只用专注于自己的事情(结婚)
//代理对象帮真实对象做更多的事情(准备工作)
public class StaticProxy {
    public static void main(String[] args) {
        Person candashuai = new Person("灿大帅");
        new WeddingCompany(candashuai).HappyMarry();
    }
}
interface Marry{
    void HappyMarry();
}
class Person implements Marry{
    public Person(String name) {
        this.name = name;
    }

    public String name;
    @Override
    public void HappyMarry() {
        //真实对象专注结婚
        System.out.println(name+"快乐地结婚了");
    }
}
class WeddingCompany implements Marry{
    Person target;

    public WeddingCompany(Person target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        //代理对象,婚庆公司帮你准备
        Ready();
        target.HappyMarry();
        End();
    }
    public void Ready(){
        System.out.println("已经为"+target.name+"准备好了,可以结婚了");
    }
    public void End(){
        System.out.println(target.name+"结婚完毕,收拾现场,结尾款");
    }
}
已经为灿大帅准备好了,可以结婚了
灿大帅快乐地结婚了
灿大帅结婚完毕,收拾现场,结尾款

Lambda表达式

  • λ希腊字母表中排序第十一位的字母,英文名称lambda
  • 避免匿名内部类定义过多
  • 其实质属于函数式编程的概念
  • 去掉无意义代码,留下核心逻辑

Functional Interface

  • 理解Functional Interface(函数式接口)是学习Java8 Lambda表达式的关键所在

  • 函数式接口的定义:

    • 任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口

      public interface Runnable{
          public abstract void run();
      }
      
    • 对于函数式接口,我们可以通过lambda表达式创建该接口的对象

推导过程

package com.volcano.thread;
//Lambda表达式的演化过程,按序号顺序看
public class TestLambda {
    //2.静态内部类
    static class SomeGame2 implements Game{
        @Override
        public void play() {
            System.out.println("I Play SomeGame2");
        }
    }
    public static void main(String[] args) {
        Game game=new SomeGame1();
        game.play();

        game=new SomeGame2();
        game.play();

        //3.局部内部类
        class SomeGame3 implements Game {
            @Override
            public void play() {
                System.out.println("I Play SomeGame3");
            }
        }
        game=new SomeGame3();
        game.play();
        //4.匿名内部类
        game=new Game() {
            @Override
            public void play() {
                System.out.println("I Play SomeGame4");
            }
        };
        game.play();
        //5.lambda表达式
        game=()->{System.out.println("I Play SomeGame5");};
        //若方法中只有一句代码,可以简化去掉大括号,如下
        //若()中有参数,可省略类型,要么全部去掉,要么都留着
        //若是只有一个参数,()也可以去掉,无参必须有括号
        //game=()->System.out.println("I Play SomeGame5");
        game.play();
    }
}
//0.定义一个函数式接口
interface Game{
    void play();
}
//1.类实现接口
class SomeGame1 implements Game{
    @Override
    public void play() {
        System.out.println("I Play SomeGame1");
    }
}
I Play SomeGame1
I Play SomeGame2
I Play SomeGame3
I Play SomeGame4
I Play SomeGame5

多线程的核心概念

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
  4. 多线程会带来额外的开销,如CPU调度时间,并发控制开销
  5. 程序运行时,即使没有主动创建的线程,后台也会有多个线程,如主线程main(),gc线程
  6. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
  8. 多线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为干预

Thread类的相关方法

startrun已讲)

  • String getName()

    • 获取线程的名称,若创建时,没有指定,则系统默认添加为Thread-num形式
  • void setName(String name)

    • 设置线程的名称
  • static currentThread()

    • 静态方法,获取当前线程

线程的优先级

就是哪个线程有较大个概率被执行。**(只是概率,脸黑还是没用)**

优先级是用数字`1-10`表示,数字越大优先级越高,如果没有设置默认优先级是`5`
  • int getPriority()

    • 获取优先级
  • void setPriority(int priority)

    • 设置优先级
  • static void yield()

    • 线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程。
  • void join()

    • 当某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到加入的join线程执行完为止。低优先级线程被join也能执行
    public class Test{
      public static void main(){
        Thread t = new Thread(new TestRunnable());
        System.out.println("1");
        t.join();//阻塞了main方法,会先把t线程执行完毕再继续
        System.out.println("2");
      }
    }
    
  • static void sleep(int millis)

    • 单位为毫秒
    • 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
    • 抛出InterruptedException异常
  • void stop()

    • 强制结束线程
  • boolean isAlive()

    • 获取线程是否存活

线程停止

  • 不推荐使用JDK提供的stop(),destroy()方法【已废弃】
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量
package com.volcano.thread;

public class StopThread implements Runnable {
    //建立一个标志位
    boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("I am running "+i++);
        }
    }
    public void stop(){
        flag=false;
    }

    public static void main(String[] args) {
        StopThread thread = new StopThread();
        new Thread(thread).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main is running "+i);
            if(i>900){
                //通过改变标志符停止线程
                thread.stop();
                System.out.println("线程停止");
                break;
            }
        }
    }
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常 InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
    • 网络延迟:方法问题的发生性
    • 倒计时:倒计时或者刷新时间
  • 每个对象都有一个锁,sleep不会释放锁

程序礼让

  • 礼让线程,让当前正在执行的线程停止,但不阻塞
  • 让线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情
package com.volcano.thread;

public class TestYield implements Runnable {
    public static void main(String[] args) {
        TestYield testYield = new TestYield();
        new Thread(testYield,"a").start();
        new Thread(testYield,"b").start();
    }

    @Override
    public void run() {
        //正常情况为 aabb
        System.out.println(Thread.currentThread().getName()+"执行了");
        //礼让成功 会交叉出现
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"停止了");
    }
}

线程强制执行(插队)

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
package com.volcano.thread;

public class TestJoin 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 {
        TestJoin vip = new TestJoin();
        Thread thread = new Thread(vip);

        for (int i = 0; i < 500; i++) {
            if(i==200){
                thread.start();
                thread.join();
            }
            System.out.println("平民排队中~"+i);
        }
    }
}
#结果太长,减缩版
平民排队中~×200
VIP登场!统统闪开!*1000
平民排队中~×300

线程状态观测

  • Thread.State
    • NEW,线程尚未启动
    • RUNNABLE,线程在Java虚拟机中执行
    • BLOCKED,线程被阻塞等待监视器锁定
    • WATTING,线程正在等待另一个线程执行特定动作
    • TIMED_WAITING,线程正在等待另一个线程执行动作达到指定等待时间
    • TERMINATED,线程已退出
  • 一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态
package com.volcano.thread;

public class TestThreadState implements Runnable{
    public static void main(String[] args) {
        TestThreadState state = new TestThreadState();
        Thread thread = new Thread(state);
        System.out.println(thread.getState());
        thread.start();
        System.out.println(thread.getState());
        while(thread.getState()!=Thread.State.TERMINATED){
            System.out.println(thread.getState());
        }
        System.out.println(thread.getState());
    }

    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(1);
        }
        System.out.println("end");
    }
}
#截取部分结果
NEW
RUNNABLE
RUNNABLE
...
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
...
1
end
BLOCKED
TERMINATED

image.png

线程的优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
    • getPriority()
    • setPriority(int num)
  • 只是概率高了,而不是100%
package com.volcano.thread;

public class TestPriority implements Runnable{
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getState()+"-->"+Thread.currentThread().getPriority());
        TestPriority testPriority = new TestPriority();
        Thread t0 = new Thread(testPriority);
        Thread t1 = new Thread(testPriority);
        Thread t2 = new Thread(testPriority);
        Thread t3 = new Thread(testPriority);
        t0.setPriority(1);
        t1.setPriority(4);
        t2.setPriority(7);
        t3.setPriority(10);
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getState()+"-->"+Thread.currentThread().getPriority());
    }
}

守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待
package com.volcano.thread;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Human human = new Human();
        Thread tGod = new Thread(god);
        //默认为false表示用户线程
        tGod.setDaemon(true);
        Thread tHuman = new Thread(human);
        tGod.start();
        tHuman.start();
    }
}
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑你");
        }
    }
}
class Human implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("活到第"+i+"天");
        }
    }
}
上帝保佑你
..
活到第0天
..
上帝保佑你
..
活到第36499天
#即使用户线程结束了,上帝线程依然在执行,直到虚拟机结束
上帝保佑你
...

线程的同步

抢票模拟问题

package com.volcano.thread;
//线程的并发问题
public class TestThread2 implements Runnable{
    int ticketCount=10;
    @Override
    public void run() {
        while (true){
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticketCount<=0){
                break;
            }
        }
    }
    public static void main(String[] args) {
        TestThread2 thread2 = new TestThread2();
        new Thread(thread2,"小明").start();
        new Thread(thread2,"小红").start();
        new Thread(thread2,"黄牛党").start();
    }
}

结果:

小红抢到了第10张票
黄牛党抢到了第9张票
小明抢到了第10张票
小红抢到了第7张票
小明抢到了第8张票
黄牛党抢到了第8张票
小红抢到了第6张票
黄牛党抢到了第6张票
小明抢到了第6张票
小明抢到了第3张票
黄牛党抢到了第5张票
小红抢到了第4张票
小红抢到了第2张票
小明抢到了第0张票
黄牛党抢到了第1张票

线程同步

解决上面的问题,需要队列和锁,队列保证顺序,锁保证安全。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

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

同步方法

显然有人抢到同一张票,或者直接抢到不存在的0票,面临这种问题,需要在处理问题的方法中添加synchronized关键字

//在普通方法上加同步锁 synchronized,锁的是个对象,不是某一个方法
//不同的对象就是不同的锁,普通方法加 synchronized,线程使用不同的此方法的对象,还有共享资源的问题
//在静态方法上添加 synchronized,则锁的是所有对象
package com.volcano.thread;
//线程的并发问题
public class TestThread2 implements Runnable{
    int ticketCount=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
            try {
                buy();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized解决问题,锁的都是this这个对象
    private synchronized void buy(){
        if (ticketCount<=0){
            stop();
            return;
        }
        System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
    }
    private void stop(){
        flag=false;
    }
    public static void main(String[] args) {
        TestThread2 thread2 = new TestThread2();
        new Thread(thread2,"小明").start();
        new Thread(thread2,"小红").start();
        new Thread(thread2,"黄牛党").start();
    }
}
小明抢到了第10张票
小红抢到了第9张票
黄牛党抢到了第8张票
小明抢到了第7张票
黄牛党抢到了第6张票
小红抢到了第5张票
小红抢到了第4张票
小明抢到了第3张票
黄牛党抢到了第2张票
小明抢到了第1张票

同步块

  • synchronized(obj){}
    • obj称之为同步监视器,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
  • 同步监视器的执行过程
    • 第一个线程访问,锁定个同步监视器,执行其中代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 第一个次访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
public void qukuan(int money){
	synchronized(this){
    //表示当前的对象的代码块被加了synchronized同步锁
    //用this锁代码块是代表当前的对象,如果在其他方法中也有代码块使用的都是同一个同步锁
    操作金额
  }
}
public void qukuan2(int money,Account a){
	synchronized(a){
    //实现不同对象用不同的锁
    操作金额
  }
}
package com.volcano.thread;

public class TestSynBlock{
    public static void main(String[] args) {
        Station station = new Station();
        new Thread(station,"小明").start();
        new Thread(station,"小红").start();
        new Thread(station,"黄牛党").start();
    }
}
class Station implements Runnable{
    int ticketCount=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
            buy();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void buy(){
        synchronized (this){
            if(ticketCount<=0){
                stop();
                return;
            }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
        }
    }
    private void stop(){
        flag=false;
    }
}

Lock

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

import java.util.concurrent.locks.ReentrantLock;

public class TestThread2 implements Runnable{
    int ticketCount=500;
    boolean flag=true;
    //另锁私有并且为不可修改
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (flag){
            try {
                //建议放到try/catch中
                lock.lock();
                buy();
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //解锁放到finally中
                lock.unlock();
            }
        }
    }
    private void buy(){
        if (ticketCount<=0){
            stop();
            return;
        }
        System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
    }
    private void stop(){
        flag=false;
    }
    public static void main(String[] args) {
        TestThread2 thread2 = new TestThread2();
        new Thread(thread2,"小明---").start();
        new Thread(thread2,"小红--").start();
        new Thread(thread2,"黄牛党").start();
    }
}

synchronized和Lock的区别

  • Lock是显式锁(手动开启和关闭,别忘记关闭),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩张性(提供更多的子类)
  • 优先使用顺序:
    • **Lock **> 同步代码块 >同步方法

线程的死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

问题的模拟

package com.volcano.thread;
//模拟小红和小兰二人撕逼,互相要对方的口红和镜子
public class TestDeadLock {
    public static void main(String[] args) {
        SiB xh = new SiB("小红",0);
        SiB xl = new SiB("小兰",1);
        new Thread(xh).start();
        new Thread(xl).start();
    }
}
class LipStick{

}
class Mirror{

}
class SiB implements Runnable{
    //保证只有一个口红和镜子
    static LipStick lipStick = new LipStick();
    static Mirror mirror = new Mirror();
    String name;
    int choice;

    public SiB(String name, int choice) {
        this.name = name;
        this.choice = choice;
    }

    @Override
    public void run() {
        if(choice==0){
            synchronized (lipStick){
                System.out.println(name+"得到了口红的锁");
                /*1.会死锁*/
                synchronized (mirror){
                    System.out.println(name+"得到了镜子的锁");
                }
                
                System.out.println(name+"释放了口红的锁");
            }
            //2.不会死锁,开启后注释掉1的部分
//            synchronized (mirror){
//                System.out.println(name+"得到了镜子的锁");
//            }
        }else{
            synchronized (mirror){
                System.out.println(name+"得到了镜子的锁");
                /*1.会死锁*/
                synchronized (lipStick){
                    System.out.println(name+"得到了口红的锁");
                }

                System.out.println(name+"释放了镜子的锁");
            }
            /*2.不会死锁,开启后注释掉1的部分
                synchronized (lipStick){
                System.out.println(name+"得到了口红的锁");
            }*/
        }

    }

}

小兰得到了镜子的锁
小红得到了口红的锁
#线程会死锁到这一步
#没有死锁的结果
小红得到了口红的锁
小兰得到了镜子的锁
小兰释放了镜子的锁
小红释放了口红的锁
小兰得到了口红的锁
小红得到了镜子的锁

产生死锁的四个必要条件

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

只要想办法破坏一个或多个的条件就可以避免死锁的发生

线程通信

生产者和消费者

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

解决方式

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

    • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)

    • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)

    • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”

    • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

image.png

package com.volcano.thread;
//消费者和生产者和鸡的故事
public class TestPC {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
//生产者
class Producer implements Runnable{
    Buffer buffer;

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.push(new Chicken(i));
            System.out.println("生产了id为"+i+"的鸡");
        }
    }
}
//消费者
class Consumer implements Runnable {
    Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了id为"+buffer.pop().id+"的鸡");;
        }
    }
}
//消费品:鸡
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class Buffer{
    Chicken[] chickens = new Chicken[10];
    int count=0;
    synchronized void push(Chicken chicken){
        //如果缓冲区的鸡满了就等
        if(count==chickens.length){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有空闲了,可以生产鸡了
        chickens[count]=chicken;
        count++;
        //通知消费者可以消费了
        this.notifyAll();
    }
    synchronized Chicken pop(){
        //没有鸡了等待
        if(count==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有鸡了可以消费
        Chicken chicken=chickens[--count];
        //通知生产者可以生产鸡了
        this.notifyAll();
        return chicken;
    }
}
生产了id为0的鸡
消费了id为0的鸡
生产了id为1的鸡
消费了id为1的鸡
...
消费了id为96的鸡
消费了id为99的鸡
消费了id为98的鸡
消费了id为97的鸡
  • 并发协作模型“生产者/消费者模式” —> 信号灯法

    package com.volcano.thread;
    
    public class TestFlag {
        public static void main(String[] args) {
            Road road = new Road(100);
            Car car = new Car(road);
            Traffic traffic = new Traffic(road);
            new Thread(car).start();
            new Thread(traffic).start();
        }
    }
    //车子
    class Car implements Runnable{
        public Car(Road road) {
            this.road = road;
        }
    
        Road road;
    
        @Override
        public void run() {
            getStatus();
        }
        synchronized void getStatus(){
            //100辆车的状态
            while (road.carNum>0){
                if(!road.flag){
                    road.stop();
                    System.out.println("剩下"+road.carNum+"辆车正在等待中···");
                }
                else{
                    road.go();
                    System.out.println("第"+road.carNum+"辆车正在通过~");
                    road.carNum--;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //交通信号灯
    class Traffic implements Runnable{
        Road road;
    
        public Traffic(Road road) {
            this.road = road;
        }
    
        @Override
        public void run() {
            //每两秒切换一次信号灯
            while(road.carNum>0){
                try {
                    changeFlag();
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        synchronized void changeFlag(){
            road.flag=!road.flag;
        }
    }
    //路
    class Road{
        boolean flag=false;
        int carNum;
    
        public Road(int carNum) {
            this.carNum = carNum;
        }
    
        synchronized void stop(){
            //绿灯则等待红灯
            if(flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("红灯停止");
            this.notifyAll();
        }
        synchronized void go(){
            //红灯则等待绿灯
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("绿灯通行");
            this.notifyAll();
        }
    }
    
    

线程通信的方法

  • wait()

    • 放弃CPU、同步资源,使别的线程可访问并修改共享资源,直到其他线程通知,和sleep不同,会释放锁
  • wait(timeout)

    • 指定等待毫秒
  • notify()

    • 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll()

    • 唤醒正在排队等待资源的所有线程结束等待。

    Java.lang.Object提供的这三个方法只有在 synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

线程池

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

使用线程池

  • JDK5起提供了线程池相关API:ExecutorService和Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnale
    • Futrue submit(Callable task):执行任务,有返回值,一般用来执行Callable
    • void shutdown():关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.volcano.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        //创建线程池,指定大小
        ExecutorService  service = Executors.newFixedThreadPool(10);
        //启动Runnable线程
        service.execute(new OneThread());
        service.execute(new OneThread());
        service.execute(new OneThread());
        //关闭连接
        service.shutdown();
    }
}
class OneThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2