Java多线程详解

JAVA多线程


一、线程创建

1.继承Thread类

步骤: 继承Thread类–>重写run()方法–>调用start开启线程。

例一:

public class TestThread_01 extends Thread{
    public void run(){
        //run方法体
        for (int i = 0; i < 10; i++) {
            System.out.println("我是子线程");
            try {
                //线程休眠函数,休眠200ms
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //main主线程
    public static void main(String[] args) {
        //创建线程类对象
        TestThread_01 test = new TestThread_01();
        //调用start方法,开辟一个新的线程,新的线程自动调用run方法
        test.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("我是主线程");
            try {
                //线程休眠函数,休眠200ms
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

对于上述代码,按照步骤执行我们分析可推导:应该先输出十遍 ”我是子进程“ ,再输出十遍 ”我是父进程“,但真实的执行结果却是

在这里插入图片描述

并没有按照程序的先后顺序一步一步地执行,这是因为我们创建了一个子线程,这时该进程拥有两个子进程,CPU调度到哪个,哪个就会被执行。

例二:

//多线程下载网络图片

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.net.URL;

//通过继承 Thread 实现多线程

public class TestThread_02 extends Thread{
    private String url;//网络图片地址
    private String name;//保存文件路径

    public TestThread_02(String url, String name) {
        this.url = url;
        this.name = name;
    }

    public void run(){
        WebDownloader webdownloader = new WebDownloader();
        webdownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread_02 test1 = new TestThread_02("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.png");
        TestThread_02 test2 = new TestThread_02("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.png");
        TestThread_02 test3 = new TestThread_02("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.png");
        test1.start();
        test2.start();
        test3.start();
    }
}

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try{
            FileUtils.copyURLToFile(new URL(url),new File(name));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2、实现 Runnable 接口

步骤: 实现Runnable接口–>重写run方法–>创建Runnable接口的类实例–>将线程作为参数传入

例一:

package 多线程;

//通过实现Runnable接口实现多线程,重写run方法,执行线程需要丢入Runnable接口的实现类
public class TestThread_03 implements Runnable{
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.println("子线程");
        }
    }

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        TestThread_03 testThread_03 = new TestThread_03();

        //创建线程对象,通过线程对象开启我们的多线程
        Thread thread = new Thread(testThread_03);
        thread.start();

        for (int i = 0; i < 300; i++) {
            System.out.println("父线程");
        }
    }
}

在方法二与方法一中,我们都实现了多线程,但粗壮乃一些问题,例如当多个线程操作同一资源时,会出现重复操作。

例二:

//多个线程操作同一个资源
//买火车票的例子

//发现问题:多个线程操作同一资源时,线程不安全,数据紊乱,并发问题

public class TestThread_04 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();
            }
            //Thread.currentThread().getName()用于获取当前线程ID
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ ticketNums-- +"票");
        }
    }

    public static void main(String[] args) {
        TestThread_04 test = new TestThread_04();
        new Thread(test,"线程1").start();
        new Thread(test,"线程2").start();
        new Thread(test,"线程3").start();
    }
}

执行结果:
在这里插入图片描述
可以看到,总共10张票的情况下,线程一、二、三同时拿到了第十张票,这就属于数据紊乱(脏数据)

解决办法:通过增加flag标志位对公共资源进行上锁与解锁

例三:

//多个线程操作同一个资源
//买火车票案例改进

//通过增加flag标志位,对公共资源进行上锁与开锁

public class TestThread_04 implements Runnable{
    //火车票数
    private int ticketNums = 10;
    //设置标记位
    static boolean flag = true;

    @Override
    public void run() {
        while(true){
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 判断当前公共资源(火车票)是否被占用
            if(flag) {
                // 将公共资源上锁,其它线程使用时发现上锁,循环等待
                flag = false;
                //判断是否有票
                if(ticketNums <=0) {
                    // 将公共资源解锁并退出
                    flag = true;
                    break;
                }
                //Thread.currentThread().getName()用于获取当前线程ID
                System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
                //解锁公共资源
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        TestThread_04 test = new TestThread_04();
        new Thread(test,"线程1").start();
        new Thread(test,"线程2").start();
        new Thread(test,"线程3").start();
    }
}

执行结果:在这里插入图片描述
注意: 线程调度由CPU管理,任意两行代码之间,都可能出现线程切换,保证程序原子性在之后的线程锁中记载。

3、重写Callable方法(了解)

步骤:

1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);
5.提交执行: Future<Boolean> result1 = ser.submit(test1);
6.获取结果: boolean r1 = result1.get();
7.关闭服务:ser.shutdownNow();

例:

//多线程图片下载器

import java.util.concurrent.*;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.net.URL;

//通过继承 Thread 实现多线程

public class TestCallable implements Callable {
    private String url;//网络图片地址
    private String name;//保存文件路径

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call() throws Exception {
        //下载图片线程函数体
        Downloader downloader = new Downloader();
        downloader.downloader(url,name);
        System.out.println("下载了图片:"+name);
        return true;
    }

    public static void main(String[] args) {
        TestCallable test1 = new TestCallable("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.png");
        TestCallable test2 = new TestCallable("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.png");
        TestCallable test3 = new TestCallable("https://www.baidu.com/img/" +
                "PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.png");

        //创建执行服务                                参数为创建线程池数量
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行       执行线程方法返回值                        //参数为线程
        Future<Boolean> result1 = ser.submit(test1);
        Future<Boolean> result2 = ser.submit(test2);
        Future<Boolean> result3 = ser.submit(test3);

        //获取返回结果    需要异常处理
        try {
            boolean r1 = result1.get();
            boolean r2 = result2.get();
            boolean r3 = result3.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            //关闭服务  销毁创建的线程池
            ser.shutdownNow();
        }
    }
}

//下载器
class Downloader{
    //下载方法
    public void downloader(String url,String name){
        try{
            FileUtils.copyURLToFile(new URL(url),new File(name));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

该方法的优点在于可以从线程处获取返回值

二、静态代理模式

1、总结

1.真实对象与代理对象都要实现同一个接口
2.代理对象要代理真实角色

2、优点

1.代理对象可以做很多真实对象做不了的东西
2.真实对象可以专注做一件事

3、代码示例

public class StaticProxy {
    public static void main(String[] args) {
        //创建个人示例
        You you = new You(); //你要结婚
        //创建婚庆公司实例
        WeddingCompany weddingCompany = new WeddingCompany(you); //婚庆公司帮你结婚
        //婚庆公司代理个人完成婚礼整体步骤
        //个人作为参数加入到婚庆公司的计划中
        weddingCompany.happyMarry();
    }
}

// 创建结婚接口,用于规范
interface Marry{
    void happyMarry();
}

//创建结婚对象实现结婚接口
class You implements Marry{
    //ALT+INS自动实现接口函数,接口里的函数规范都是public的
    @Override
    public void happyMarry(){
        System.out.println("小明今天结婚");
    }
}

//创建婚庆公司实现结婚接口
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
    private Marry target;

    //Marry接口引用可以指向实现了他的所有类
    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        System.out.println("婚庆公司布置会场");
        this.target.happyMarry(); //这是真实对象
        System.out.println("清理场地,并结尾款");
    }
}

三、Lambda表达式

1、使用方法

为了减少匿名内部类的使用,让代码更加简洁对函数接口(只含有一个方法的接口)可以使用Lambda表达式创建实现该接口的临时类。

2、代码示例

public class TestLambda {

    public static void main(String[] args) {
        //普通方法
        Like like = new Like();
        like.lambda(2,1);

        //lambda表达式方法
        ILike like2 = (int a,float b)->{
            System.out.println("I like lambda2");
        };

        // 简化1.当函数体内只有一行代码时可省略花括号,否则只能用代码块
        like2 = (int a,float b)-> System.out.println("I like lambda2 "+ a);
        like2.lambda(2,1);

        // 简化2. 参数的类型可以省略
        like2 = (a,b)-> System.out.println("I like lambda3 "+ a);
        like2.lambda(2,1);

        /*
        简化3. 当只含有一个参数时,可以去除参数外面的小括号
        like2 = a,b-> System.out.println("I like lambda4 "+ a);
        like2.lambda(2,1);
        */
    }
}

// 1.定义一个函数接口(只包含一个方法的接口)

interface ILike{
    void lambda(int a,float b);
}

// 2.创建接口的实现类
class Like implements ILike{
    @Override
    public void lambda(int a,float b) {
        System.out.println("I like lambda  "+ a +"  " + b);
    }
}

四、Thread常用方法

1、线程停止

注意:

1.建议线程正常停止--->设置执行次数,避免出现死循环
2.建议使用标志位--->设置一个标志位
3.不要使用stop或者destroy等过时或JDK不建议使用的方法

例:

public class TestStop implements Runnable{

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

    @Override
    public void run() {
        int i =0;
        while (flag){
            System.out.println("执行"+Thread.currentThread().getName() +"第 " + ++i + " 次");
        }
    }

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

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        Thread thread = new Thread(testStop,"线程例子");
        thread.start();

        for(int i=0;i<1000;i++){
            System.out.println("main " + i);
            if(i==900){
                //调用stop方法停止线程
                testStop.stop();
                System.out.println("线程停止了");
            }
        }

    }
}

2、观测线程状态 (State)

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

        //观测线程状态
        Thread.State state = thread.getState();
        System.out.println(state);

        //观察启动后状态
        thread.start();
        state = thread.getState();
        System.out.println(state);

        //只要线程状态不是终止,就一直输出
        while (state != Thread.State.TERMINATED) {
            //更新状态
            state = thread.getState();
            System.out.println(state);
        }
    }
}

3、线程礼让

注意:

注意线程礼让,是让线程从运行状态转换为就绪状态,再由CPU进行线程调度,CPU可能还会调度该线程运行,所以线程礼让不一定会成功

例:

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        Thread thread1 = new Thread(myYield,"线程1");
        Thread thread2 = new Thread(myYield,"线程2");
        thread1.start();
        thread2.start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        //线程礼让
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程执行结束");
    }
}

4、线程优先级

注意:

设置优先级会增加其再CPU调度中的权重,但不一定先执行!!!
所以优先级高的线程不一定先执行,但有很大概率先执行。

例:

public class TestPriority {
    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread thread = new Thread(myPriority,"自定义线程");
        //设置现线程的优先级,注意:优先级只能为1-10,超出将报错
        thread.setPriority(10); //类内静态常量 最大值:Thread.MAX_PRIORITY 最小值:Thread.MIN_PRIORITY;
        //先设置优先级再启动线程
        thread.start();
        //输出主线程的优先级
        System.out.println(Thread.currentThread().getName()+"的优先级为--->"+Thread.currentThread().getPriority());
    }
}

class  MyPriority implements Runnable{
    @Override
    public void run() {
        // Thread.currentThread() 获取正在运行的线程对象 getPriority()获取线程优先级
        System.out.println(Thread.currentThread().getName()+"的优先级为--->"+Thread.currentThread().getPriority());
    }
}

5、线程合并(线程插队)

插队就是强制CPU中断当前线程,执行某个线程。

例:

public class TestJoin implements Runnable{
    @Override
    public void run() {
        //模拟延时
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //等待3秒后,依然执行插队的子线程,说明插队的线程具有原子性
        for (int i = 0; i < 5; i++) {
            System.out.println("VIP线程来了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);

        for(int i=0;i<10;i++){
            if(i==5) {
                thread.start();
                //抛出异常
                thread.join();
            }
            System.out.println("main线程"+Thread.currentThread().getName());
        }
    }
}

6、设置守护线程

虚拟机不用等待守护线程结束,当其他线程都结束时,虚拟机即关闭。

例:

public class TestDaemon {
    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("上帝保护着你");
        }
    }
}
//你
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你开心的活着");
        }
        System.out.println("========goodbye! world===========");
    }
}

五、线程同步

定义及实现:

①线程同步:队列+锁
②优缺点:可以增加安全性,但会降低性能
③实现:修饰符 synchronized,用于控制对“对象”的访问,每个对象对应一把锁,每个被synchronized修饰的方法都必须获得调用该方法的对象的锁才能执行, 方法一旦执行,就独占该锁。

1、synchronized方法或块

例一:买票问题

public class SafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        Thread thread1 = new Thread(buyTicket,"小明");
        Thread thread2 = new Thread(buyTicket,"小强");
        Thread thread3 = new Thread(buyTicket,"小亮");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class BuyTicket implements Runnable{

    //票数
    private int nums = 10;
    //设置标志位
    private boolean flag = true;

    @Override
    //买票
    public void run() {
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //加修饰符 synchronized ,变为同步方法,锁的是 this
    private synchronized void buy() throws InterruptedException {
        //判断是否有票
        if(nums<=0){
            flag = false;
            return;
        }
        //模拟延时,sleep不释放锁
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"买到了第"+ nums-- +"张票");
    }
}
}

例二:列表问题

import java.util.ArrayList;
import java.util.List;

public class SafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                //利用synchronized(obj){}块对公共资源list上锁
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                    //System.out.println(list.size());
                }
            }).start();
        }
        //若不加延时,可能出现主线程已经执行完毕,子线程还在执行,
        // 但子线程中没有输出语句,所以最终小于10000
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

例三:银行取款问题

// 两个人去银行取钱,账户
// 锁应该给要访问公共资源的代码块,在这个例子中,执行多线程的是银行,但公共资源为账户中的钱,
// 所以要用synchronized(obj){}块进行锁定

public class SafeBank {
    public static void main(String[] args) {
        //创建账户实例
        Account account = new Account(100,"结婚基金");

        //创建个人实例
        Drawing you = new Drawing(account,50,"你");
        Drawing yourGirlFriend = new Drawing(account,100,"女朋友");

        yourGirlFriend.start();
        you.start();
    }
}

// 账户
class Account{
    int money;
    String name;

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

//银行:模拟取款
class Drawing extends Thread{
    Account account; //账户
    int drawingMoney; //取款金额
    int nowMoney; //现在手里的现金

    //构造函数
    public Drawing(Account account, int drawingMoney, String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        // 对访问公共资源的代码块进行上锁
        synchronized (this.account){
            // 判断钱够不够
            if(this.account.money - this.drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"取钱余额不足");
                return;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额
            this.account.money = this.account.money - this.drawingMoney;
            //你手里的钱
            this.nowMoney = nowMoney + drawingMoney;
        }

        System.out.println(this.account.name+"中余额为"+this.account.money);
        //Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName()+"手里的现金"+this.nowMoney);
    }
}

2、使用安全类型

例:JUC里面的安全类型(安全列表演示)

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) throws InterruptedException {
        //java.util.concurrent包下定义的安全列表类型
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String >();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }
            ).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

3、Lock锁

例:

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

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

class BuyTicket implements Runnable{

    private int ticketNums = 10;
    //定义Lock锁   可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try{
                this.lock.lock();
                //任意线程对ticketNums上锁后,其他线程想要访问其对应地址都不能实现,会在判断处等待
                if(this.ticketNums>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(this.ticketNums--);
                }else{
                    break;
                }
            }finally {
                this.lock.unlock();
            }
        }
    }
}

相比于synchronized的优势在于,Lock可以显式的加锁与解锁,使用起来更为灵活。

六、死锁

程序中,多个线程互相抱着对方需要的资源,而形成的一种僵持状态,成为死锁

1、产生死锁的四个必要条件及如何避免死锁

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

只要破坏其中的一个或多个条件,即可解除死锁。

2、死锁实例

//多个线程互相抱着对方需要的资源,形成僵持
//小红和小芳是好朋友(两个线程),她们同时要化妆(需要镜子和口红),同时伸手去拿镜子和口红。
//小红拿到了口红,小芳拿到了镜子
//小红停下来等待小芳镜子用完好获得镜子,继续化妆
//小芳停下来等待小红口红用完好获得口红,继续化妆
//谁都不礼让谁,陷入僵持状态

public class DeadLock {
    public static void main(String[] args) {
        Makeup makeup1 = new Makeup(0,"小红");
        Makeup makeup2 = new Makeup(1,"小芳");
        makeup1.start();
        makeup2.start();
    }
}

//口红
class Lipstick{

}

//镜子
class Mirror{

}

//化妆
class Makeup extends Thread{
    //需要的资源只有一份,用static来保证0
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

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

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

    @Override
    //重写run()方法
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆
    private void makeup() throws InterruptedException {
        if(this.choice==0){
            synchronized (lipstick){
                System.out.println(this.name + "拿到了口红,希望拿到镜子");
                Thread.sleep(2000);
                //等待两秒后,此时另外一个线程拿到了镜子
                //另外一个线程在等待口红
                //两个线程同时占用对方需要的资源并开始等待,陷入僵持
                synchronized (mirror){
                    System.out.println(this.name + "拿到了镜子,可以开始化妆了");
                    Thread.sleep(2000);
                }
            }
        }else{
            synchronized (mirror){
                System.out.println(this.name + "拿到了镜子,希望拿到口红");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.name + "拿到了口红,可以开始化妆了");
                    Thread.sleep(2000);
                }
            }
        }
    }
}

七、线程通信

1、管程法

//测试生产者消费者模型 --> 利用缓冲区解决

//需要 生产者、消费者、产品、缓冲区
public class TestPC {
    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        new Thread(new Producer(warehouse)).start();
        new Thread(new Consumer(warehouse)).start();
    }
}

//生产者
class Producer implements Runnable{
    Warehouse warehouse;

    public Producer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        while(true){
            this.warehouse.WarehousingProducts(new Product());
            //模拟生产延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable{
    Warehouse warehouse;

    public Consumer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        while(true){
            this.warehouse.OutboundProducts();
            //模拟消费延时
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//产品
class Product{

}

//仓库
class Warehouse{
    //设置仓库大小,最多只能容纳10件产品
    private Product []products = new Product[10];
    //设置计数器
    int count = 0;

    //入库产品,因为要使用公共资源-仓库,所以使用方法锁
    public synchronized void  WarehousingProducts(Product product){
        if(this.count == this.products.length){
            //仓库满了,通知生产者停止生产并等待,通知消费者尽快消费
            try {
                //释放锁等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果仓库没满
        this.products[count] = product;
        count++;
        System.out.println("生产者生产了产品,此时仓库有"+this.count+"件产品");
        //生产完成,通知消费者消费
        this.notify();
    }

    //出库产品
    public synchronized  void OutboundProducts(){
        if(this.count==0){
            //仓库是空的,没有产品可消费,通知消费者停止消费并等待,通知生产者抓紧时间生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果仓库里有产品
        this.count--;
        System.out.println("消费者消费了产品,此时仓库有"+this.count+"件产品");
        //消费完成,通知生产者生产
        this.notify();
    }
}

2、信号灯法

//测试生产者消费者模型 --> 利用缓冲区解决

//需要 生产者、消费者、产品、缓冲区
public class TestPC {
    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        new Thread(new Producer(warehouse)).start();
        new Thread(new Consumer(warehouse)).start();
    }
}

//生产者
class Producer implements Runnable{
    Warehouse warehouse;

    public Producer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        while(true){
            this.warehouse.WarehousingProducts(new Product());
            //模拟生产延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable{
    Warehouse warehouse;

    public Consumer(Warehouse warehouse) {
        this.warehouse = warehouse;
    }

    @Override
    public void run() {
        while(true){
            this.warehouse.OutboundProducts();
            //模拟消费延时
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//产品
class Product{

}

//仓库
class Warehouse{
    //设置标记位
    boolean flag = true;
    //入库产品,因为要使用公共资源-仓库,所以使用方法锁
    public synchronized void  WarehousingProducts(Product product){
        if(!flag){
            //仓库满了,通知生产者停止生产并等待,通知消费者尽快消费
            try {
                //释放锁等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("生产者生产了产品");
        //生产完成,通知消费者消费
        this.flag = !this.flag;
        this.notifyAll();
    }

    //出库产品
    public synchronized  void OutboundProducts(){
        if(this.flag){
            //仓库是空的,没有产品可消费,通知消费者停止消费并等待,通知生产者抓紧时间生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者消费了产品");
        //消费完成,通知生产者生产
        this.flag = !this.flag;
        this.notifyAll();
    }
}

八、线程池

1、为什么要使用线程池

不使用线程池的问题: 经常创建和销毁线程,会使用特别大的资源,对程序的性能影响很大。
解决办法: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,避免频繁的创建销毁线程。

2、使用线程池的好处

①提高程序响应速度。
②降低资源消耗。
③便于线程管理(核心池大小、最大线程数、没有任务时县城多久被销毁)。

3、代码实例

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

public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池     工具类       创建线程池           参数为池容量
        ExecutorService service = Executors.newFixedThreadPool(10);

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

        //2.关闭连接
        service.shutdown();
    }

}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐先生没洗头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值