多线程:线程同步与死锁(卖票案例)、线程通信、生产者与消费者

卖票案例

5个窗口同时卖票:
使用Runnable接口,只创建了一个ticket1对象,5个线程共享此对象,实现了资源共享。

public class ticket1 implements Runnable {

    private  int ticket = 5;

    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
            }
        }
    }

    public static void main(String[] args) {
        ticket1 ticket1 = new ticket1();
        Thread t1 = new Thread(ticket1);
        Thread t2 = new Thread(ticket1);
        Thread t3 = new Thread(ticket1);
        Thread t4 = new Thread(ticket1);
        Thread t5 = new Thread(ticket1);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}

输出:

Thread-1正在出售第5张票
Thread-2正在出售第3张票
Thread-0正在出售第4张票
Thread-3正在出售第1张票
Thread-1正在出售第2张票

问题:
售票的顺序不对,应该以售出第5 - 4 - 3 - 2 - 1 张票的顺序

解决方法
1. 使用同步代码块

public class ticketSyn1 implements Runnable {

private int ticket = 5;

@Override
public void run() {

    for (int i = 0; i < 10; i++) {
        synchronized (this) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
            }
        }

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

public static void main(String[] args) {
    ticketSyn1 ticket1 = new ticketSyn1();
    Thread t1 = new Thread(ticket1, "A");
    Thread t2 = new Thread(ticket1, "B");
    Thread t3 = new Thread(ticket1, "C");
    Thread t4 = new Thread(ticket1, "D");
    Thread t5 = new Thread(ticket1, "E");

    t1.start();
    t2.start();
    t3.start();
    t4.start();
    t5.start();
}
  1. 使用同步方法
public class ticketSyn2 implements Runnable {

    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            this.sell();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void sell(){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
            }
    }

    public static void main(String[] args) {
        ticketSyn2 ticket1 = new ticketSyn2();
        Thread t1 = new Thread(ticket1, "A");
        Thread t2 = new Thread(ticket1, "B");
        Thread t3 = new Thread(ticket1, "C");
        Thread t4 = new Thread(ticket1, "D");
        Thread t5 = new Thread(ticket1, "E");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}

总结
同步代码块中的 synchronized(obj){}中的obj可为任何对象,推荐使用共享资源作为同步监视器。

练习

需求说明
张三和妻子各拥有一张银行卡和存折,可以对同一个银行账户迚行存取款的操作,请使用多线程及同步方法模拟张三和妻子同时取款的过程。要求使用同步方法和同步代码块两种方式实现

分析
定义Account类表示银行帐户
定义两个线程分别实现张三和妻子取款的操作

public class Account implements Runnable{

    private float money;

    public Account(){};

    public Account(float money) {
        this.money = money;
    }

    public float getMoney() {
        return money;
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            this.withdraw(400);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void withdraw(float withdrawMoney){
        System.out.println(Thread.currentThread().getName()+"正在取款");
        if(money >= withdrawMoney){
            money = money - withdrawMoney;
        System.out.println(Thread.currentThread().getName()+"取款成功!余额为"+money);
        }else {
            System.out.println("余额不足!取款失败。余额为"+money);
        }
    }

    public static void main(String[] args) {
        Account account = new Account(2000);
        Thread t1 = new Thread(account,"张三");
        Thread t2 = new Thread(account,"张三妻子");

        t1.start();
        t2.start();
    }
}

输出:

张三正在取款
张三取款成功!余额为1600.0
张三妻子正在取款
张三妻子取款成功!余额为1200.0
张三正在取款
张三取款成功!余额为800.0
张三妻子正在取款
张三妻子取款成功!余额为400.0
张三正在取款
张三取款成功!余额为0.0
张三妻子正在取款
余额不足!取款失败。余额为0.0
张三正在取款
余额不足!取款失败。余额为0.0
张三妻子正在取款
余额不足!取款失败。余额为0.0
张三正在取款
余额不足!取款失败。余额为0.0
张三妻子正在取款
余额不足!取款失败。余额为0.0

死锁

同步可以保证资源共享操作的正确性,但是过多同步也会产生死锁。

死锁一般情况下表示相互等待,是程序运行时出现的一种文体

线程通信

Java提供了3 种方法解决线程间的通信问题:

void notify()
Wakes up a single thread that is waiting on this object’s monitor.
唤醒一个处于等待状态的线程

void notifyAll()
Wakes up all threads that are waiting on this object’s monitor.
唤醒所有处于等待状态的线程

void wait()
Causes the current thread to wait until it is awakened, typically by being notified or interrupted.
使此线程一直等待,直到它被其他线程通知

void wait​(long timeoutMillis)
Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.
指定等待的毫秒时间

void wait​(long timeoutMillis, int nanos)
Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real
指定等待的毫秒、微秒时间

生产者与消费者案例

商品类:

public class Goods {

    private String brand;
    private String name;

    //默认最初无商品, flag = false
    private boolean flag = false;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

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


    public synchronized void get(){
        //flag == false 说明无商品,消费者线程等待,进入阻塞状态。
        if(!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费者购买了"+this.getBrand()+this.getName());
        flag = false;
        //唤醒生产者线程进行生产
        notify();
    }

    public synchronized void set(String brand,String name){
        // flag == true 说明有商品,生产者线程等待,进入阻塞状态。
        if(flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        this.setBrand(brand);
        System.out.println("生产者生产了"+this.getBrand()+this.getName());
        flag = true;
        //唤醒消费者去购买
        notify();
    }

}

生产者类:

public class Producer implements Runnable{

    private Goods goods;

    public Producer(Goods goods){
        this.goods = goods;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            if(i%2==0){
                goods.set("娃哈哈","矿泉水");
                goods.set("旺仔","牛奶");
            }
        }
    }
}

消费者类:

public class Consumer implements Runnable{

    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            goods.get();
        }
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);

        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);

        t1.start();
        t2.start();
    }
}

输出:

生产者生产了娃哈哈矿泉水
消费者购买了娃哈哈矿泉水
生产者生产了旺仔牛奶
消费者购买了旺仔牛奶
生产者生产了娃哈哈矿泉水
消费者购买了娃哈哈矿泉水
生产者生产了旺仔牛奶
消费者购买了旺仔牛奶
生产者生产了娃哈哈矿泉水
消费者购买了娃哈哈矿泉水
生产者生产了旺仔牛奶
消费者购买了旺仔牛奶
生产者生产了娃哈哈矿泉水
消费者购买了娃哈哈矿泉水
生产者生产了旺仔牛奶
消费者购买了旺仔牛奶
生产者生产了娃哈哈矿泉水
消费者购买了娃哈哈矿泉水
生产者生产了旺仔牛奶
消费者购买了旺仔牛奶
发布了40 篇原创文章 · 获赞 1 · 访问量 1098

猜你喜欢

转载自blog.csdn.net/weixin_44495162/article/details/103479027