多线程问题

本文详细讲解了多线程中的生产者消费者问题,强调了线程安全与协作的重要性,通过实例展示了如何使用synchronized关键字以及wait()、notify()方法解决此类问题。此外,还介绍了守护线程的概念,以及如何设置守护线程。最后,通过一个故事模拟了死锁现象,提醒开发者避免线程间的资源竞争导致死锁。
摘要由CSDN通过智能技术生成

问题:
    多线程的程序到底是并行还是并发?

并行:宏观和微观都是同时进行。
并发:宏观上同时进行,微观是交替进行。

如果电脑是单个CPU单核,不可能实现并行。
如果电脑是多个CPU或者多核,并且操作系统也支持多任务的操作系统,才有可能支持并行。

目前大家的电脑都是可以支持并行的。

目前因为我们两个线程都在IDEA的控制台输出信息,控制台只有一个,所以一次只能接收一个线程的输出数据,所以会出现交替的情况。

 总结:是否能够并行看三个要素:
    (1)CPU
    (2)操作系统
    (3)多个线程使用的资源是否有多个,如果大家用一个也是不能并行的

一、多线程的生产者与消费者问题
1、什么是生产者与消费者问题?
有的时候任务需要多个线程来一起完成,一些线程是负责“产生/生产”数据的,一些线程是负责“消费/消耗”数据的。
数据缓冲区的大小毕竟是有限的,生产者不能无限制的往缓冲区填充数据,
当缓冲区“满”的时候,“生产者”线程应该停下来“wait”等待,等待“消费者”线程取走一些数据之后,重新“唤醒/通知”“生产者”继续生产;
反过来,如果缓冲区里面没有数据,即是“空”,
“消费者”线程此时应该停下来“wait”等待,等待“生产者”先生成一些数据之后,重新“唤醒/通知”“消费者”继续消费;

2、具体的问题:
(1)线程安全问题?有  ==> 如何解决? ==>使用锁,目前学过的是同步锁synchronized
    如何判断一个多线程的程序是否有线程安全问题?
    A:是否有多个线程同时运行(无论并行还是并发,只要是宏观角度是同时进行)
    B:这个多个线程是否使用了“共享”数据
    C:多个线程是否既有读操作又有写操作
(2)协作问题即通信问题?有
    多个线程需要“等待”和“唤醒”操作。
    ==>如何解决?  使用Object类看到过的 wait()、 wait(long timeout) 、 wait(long timeout, int nanos)
                                    notify() 、notifyAll()

IllegalMonitorStateException:非法监视器对象异常。当wait和notify等这些方法被调用时,必须确保它们是由“锁”对象调用的。否则就会报这个异常。
    什么是监视器对象?其实就是“锁”对象。


3、例如:二虎同学觉得写代码太难了,不打算做程序员了,和他媳妇(翠花)准备开一个饭馆。
     二虎负责在厨房做菜,他媳妇负责在前厅招待客户。
     厨房与前厅中间有一个窗口(工作台),当二虎炒好一盘菜之后,就把菜放到“工作台”,
     他媳妇就可以端给客户。

     菜比喻成数据。 “工作台”比喻成数据缓冲区。
     二虎相当于生产者,他负责把数据放到“工作台”上。
     他媳妇(翠花)相当于消费者,她负责把“工作台”上的数据取走。

     当“工作台”满的时候,二虎应该停下来,等他媳妇(翠花)取走一些菜之后再唤醒他;
     当“工作台”空的时候,他媳妇应该停下来,等他炒好新的菜之后再唤醒她。

public class TestCommunicate {
    public static void main(String[] args) {
        Workbench workbench = new Workbench();

        Cook cook = new Cook("二虎",workbench);
        Waiter waiter = new Waiter("翠花",workbench);

        cook.start();
        waiter.start();
    }
}
//Cook代表厨师
class Cook extends Thread{
    private Workbench workbench;

    public Cook(String name, Workbench workbench) {
        super(name);
        this.workbench = workbench;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            workbench.put();
        }
    }
}
//Waiter代表服务员
class Waiter extends Thread{
    private Workbench workbench;

    public Waiter(String name, Workbench workbench) {
        super(name);
        this.workbench = workbench;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            workbench.take();
        }
    }
}
//Workbench工作台
class Workbench{
    private static final int MAX_VALUE = 10;//工作台上最多能放MAX_VALUE盘菜
    private int total;//记录工作台上的菜的数量

    //同步方法,非静态方法的锁对象默认是this
    public synchronized void put(){
        if(total >= MAX_VALUE){
            //如果当前方法是非静态的同步方法,那么wait方法就默认由this调用即可,
            //如果当前方法是静态的同步方法,那么wait方法就要由  “当前类名.class”调用即可
            //如果当前方法不是同步方法,是同步代码块,synchronized (锁对象){} 那么就由同步代码块指定的“锁对象“调用调用
            try {
                wait();//默认现在是this调用wait  虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total++;
        System.out.println(Thread.currentThread().getName() + "生产一份菜,现在剩余:" + total);
        notify();//默认现在是this调用notify
    }

    public synchronized void take(){
        if(total <= 0){
            try {
                wait();//默认现在是this调用wait   哪个线程对象调用,就是哪个线程停下来
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total--;
        System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在剩余:" + total);
        notify();//默认现在是this调用notify
    }
}

4、问题升级一下

二虎和他媳妇挣到钱了,扩张一下,雇了其他的厨师和服务员,也就是生产者和消费者不是一对一的关系了,
而是多个生产者和多个消费者的情况。

public final void notify():唤醒在此对象监视器上等待的单个线程。
public final void notifyAll():唤醒在此对象监视器上等待的所有线程。

    无论是wait还是notify和notifyAll都要由对象监视器(即锁对象)来调用的原因,
    就是我们要区别哪些线程是和我们是一组“生产者消费者”。

    如果说JAVA程序中有多组的“生产者消费者”,大家各种完成的任务是不同的,
    那么要通过锁区分。只有使用相同锁的,才会去唤醒它,否则和我们无关。


void wait():无限等待,直到有人唤醒它
void wait(long timeout)  :限时等待,不需要唤醒也可以在时间到了自动醒来
void wait(long timeout, int nanos) :限时等待,不需要唤醒也可以在时间到了自动醒来

5、为什么wait方法和notify系列的方法,不放在Thread类中,而是在Object类中?
因为我们的wait和notify和notifyAll这些方法必须由“锁”对象调用,
而“锁”对象可能是任意引用数据类型的对象,所以我要保证任意类型的对象都要有这个方法,
所以只能放在Object类中。

如果放在Thread类中,只能由Thread类的对象来调用。

public class TestCommunicate {
    public static void main(String[] args) {
        Workbench workbench = new Workbench();

        Cook cook = new Cook("二虎",workbench);
        Waiter waiter = new Waiter("翠花",workbench);

        Cook cook2 = new Cook("邱世玉",workbench);
        Waiter waiter2 = new Waiter("如花",workbench);

        cook.start();
        waiter.start();

        cook2.start();
        waiter2.start();
    }
}

//Cook代表厨师
class Cook extends Thread{
    private Workbench workbench;

    public Cook(String name, Workbench workbench) {
        super(name);
        this.workbench = workbench;
    }

    @Override
    public void run() {
        while(true){
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            workbench.put();
        }
    }
}

//Waiter代表服务员
class Waiter extends Thread{
    private Workbench workbench;

    public Waiter(String name, Workbench workbench) {
        super(name);
        this.workbench = workbench;
    }

    @Override
    public void run() {
        while(true){
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            workbench.take();
        }
    }
}

//Workbench工作台
class Workbench{
    private static final int MAX_VALUE = 1;//工作台上最多能放MAX_VALUE盘菜
                                        //为了让问题暴露的更快,更明显,我暂时先把MAX_VALUE修改为1
    private int total;//记录工作台上的菜的数量

    //同步方法,非静态方法的锁对象默认是this
    public synchronized void put(){
//        if(total >= MAX_VALUE){
        while(total >= MAX_VALUE){
            //如果当前方法是非静态的同步方法,那么wait方法就默认由this调用即可,
            //如果当前方法是静态的同步方法,那么wait方法就要由  “当前类名.class”调用即可
            //如果当前方法不是同步方法,是同步代码块,synchronized (锁对象){} 那么就由同步代码块指定的“锁对象“调用调用
            try {
                wait();//默认现在是this调用wait  虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
//                wait(5000);//默认现在是this调用wait  虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total++;
        System.out.println(Thread.currentThread().getName() + "生产一份菜,现在剩余:" + total);
//        notify();//默认现在是this调用notify  唤醒在此对象监视器上等待的单个线程。
        notifyAll();//默认现在是this调用notify  唤醒在此对象监视器上等待的所有线程。
    }

    public synchronized void take(){
//        if(total <= 0){
        while(total <= 0){//循环
            try {
                wait();//默认现在是this调用wait   哪个线程对象调用,就是哪个线程停下来
//                wait(5000);//默认现在是this调用wait   哪个线程对象调用,就是哪个线程停下来
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total--;
        System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在剩余:" + total);
//        notify();//默认现在是this调用notify
        notifyAll();//默认现在是this调用notify
    }
}

二、守护线程(了解)
有一种线程是只为其他线程服务的,不能独立存在的,当被服务的线程结束之后,它自动就结束了。
例如:Java后台的GC线程等都是守护线程。

public class TestDaemonThread {
    public static void main(String[] args) {
        MyThread my = new MyThread();
        my.setDaemon(true);//把my线程对象变为守护线程
        my.start();

        for(int i=1; i<=5; i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main=" + i);
        }
    }
}

class MyThread extends Thread{
    public void run(){
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我默默的守护你");
        }
    }
}

三、死锁(了解)
总结:当两个或更多个线程,互相等待对方占有的锁时,就会发生死锁情况。
    尽量避免嵌套同步或者加条件。

模拟死锁,讲个故事,二虎的女朋友翠花被绑架了,绑匪说要500万,给钱就放人。二虎说,你先放人我给你钱。

public class TestDead {
    public static void main(String[] args) {
        Object money = new Object();
        Object girl = new Object();

        Boy boy = new Boy(money,girl);
        Bang bang = new Bang(money, girl);

        boy.start();
        bang.start();
    }
}

class Boy extends Thread{
    private Object money;
    private Object girl;

    public Boy(Object money, Object girl) {
        this.money = money;
        this.girl = girl;
    }

    public void run(){
        synchronized (money) {
            System.out.println("你先放人,我给你500万");
            synchronized (girl){
                System.out.println("给你500万,拜拜");
            }
        }
    }
}
class Bang extends Thread{
    private Object money;
    private Object girl;

    public Bang(Object money, Object girl) {
        this.money = money;
        this.girl = girl;
    }

    public void run(){
        synchronized (girl) {
            System.out.println("你先给我500万,我再放人,否则撕票");
            synchronized (money){
                System.out.println("给你人,谢谢");
            }
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值