JAVA面向对象-----多线程 3

线程的死锁问题

什么是死锁?

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁;出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

如何解决死锁?

➢ 专门的算法、原则
➢ 尽量减少同步资源的定义
➢ 尽量避免嵌套同步

死锁演示

示例代码1:

public class ThreadTest_7 {
    public static void main(String[] args) {
        StringBuffer stringBuffer1 = new StringBuffer();
        StringBuffer stringBuffer2 = new StringBuffer();

        new Thread(){
            @Override
            public void run(){
                synchronized (stringBuffer1){
                    stringBuffer1.append("A");
                    stringBuffer2.append("1");

                    synchronized (stringBuffer2){
                        stringBuffer1.append("B");
                        stringBuffer2.append("2");
                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (stringBuffer2){
                    stringBuffer1.append("C");
                    stringBuffer2.append("3");

                    synchronized (stringBuffer1){
                        stringBuffer1.append("D");
                        stringBuffer2.append("4");
                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }).start();

    }
}

运行结果:
在这里插入图片描述
示例代码2:

public class ThreadTest_7 {
    public static void main(String[] args) {
        StringBuffer stringBuffer1 = new StringBuffer();
        StringBuffer stringBuffer2 = new StringBuffer();

        new Thread(){
            @Override
            public void run(){
                synchronized (stringBuffer1){
                    stringBuffer1.append("A");
                    stringBuffer2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (stringBuffer2){
                        stringBuffer1.append("B");
                        stringBuffer2.append("2");
                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (stringBuffer2){
                    stringBuffer1.append("C");
                    stringBuffer2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (stringBuffer1){
                        stringBuffer1.append("D");
                        stringBuffer2.append("4");
                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }).start();
    }
}

运行结果:
在这里插入图片描述
对比发现,示例代码2中加入了sleep()后,由于一个线程进入阻塞后,另一个线程立即进入,两个线程之间相互等待对方结束线程,导致死锁问题的产生~ ~

同步锁

Lock(锁)的概念

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

代码演示

参考代码:

public class Lock_test {
    public static void main(String[] args) {
        Window window = new Window();
        Thread window1 = new Thread(window);
        Thread window2 = new Thread(window);
        Thread window3 = new Thread(window);
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}

class Window implements Runnable{
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(); //默认false
    @Override
    public void run() {
        while(true) {
            try {
                //2.调用锁定方法:lock()
                lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }finally{
                //3.调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}

运行结果:
在这里插入图片描述

synchronized 和 lock 的异同?

相同:
二者都可以解决线程安全问题~~

不同:

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放;
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁;
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock----->同步代码块(已经进入了方法体,分配了相应资源) ----->同步方法中(在方法体之外) ~~

练习

银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。问题:该程序是否有安全问题,如果有,如何解决?
[提示]
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

参考代码:

public class AccountTest  {
    public static void main(String[] args) {
        Account account = new Account(0);
        Customer customer1 = new Customer(account);
        Customer customer2 = new Customer(account);

        customer1.setName("老公");
        customer2.setName("老婆");

        customer1.start();
        customer2.start();
    }
}

class Account{
    private double balance;
    public Account(double balance) {
        this.balance = balance;
    }
    //存钱
    public synchronized void deposit(double amt){
        if (amt>0){
            balance += amt;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":存钱成功,余额为:"+ balance);
        }
    }
}

class Customer extends Thread{
    private Account account;

    public Customer(Account account) {
        this.account = account;
    }
    @Override
    public void run(){
        for (int i=0;i<5;i++){
            account.deposit(1000);
        }
    }
}

运行结果:
在这里插入图片描述

线程的通信

概述

线程通信的目的是为了能够让线程之间相互发送信号。另外,线程通信还能够使得线程等待其它线程的信号,比如,线程B可以等待线程A的信号,这个信号可以是线程A已经处理完成的信号。

线程通信涉及的方法

  1. wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  2. notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程。
  3. notifyALl():一旦执行此方法,就会唤醒所有被wait的线程。

说明:

  1. wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中。
  2. wait(), notify(), notifyALl()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常。
  3. wait(), notify(), notifyALl()三个方法是定义在java. Lang. object类中。

练习

例题:使用两个线程打印1-100,线程1和线程2交替打印。

参考代码:

public class ThreadCommunicateTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread thread1 = new Thread(number);
        Thread thread2 = new Thread(number);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}

class Number implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while(true){
            synchronized (this){
                //将阻塞的进程唤醒
                notify();
                if (number<100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        //使得调用如下wait()方法的线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}

运行结果:
在这里插入图片描述
sleep()方法和wait()方法的异同?
答:

相同点: 一旦执行方法,都会使得当前线程进入阻塞状态。
不同点:

  1. 两个方法声明的位置不同: Thread类中声明sLeep(),object 类中声明wait();
  2. 调用的要求不同: sleep() 可以在任何需要的场景下调用。wait() 必须使用在同步代码块或同步方法中;
  3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

经典问题:生产者/消费者问题

题目描述:

生产者(Prpocuctor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

这里可能出现两个问题:

➢ 生产者比消费者快时,消费者会漏掉一些数据没有取到。
➢ 消费者比生产者快时,消费者会取相同的数据。

参考代码:

public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer1 = new Producer(clerk);
        producer1.setName("生产者");
        Consumer consumer1 = new Consumer(clerk);
        consumer1.setName("消费者");
        producer1.start();
        consumer1.start();
    }
}

class Clerk{
    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {
        if (productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            notify();//唤醒消费者
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void consumeProduct() {
        if (productCount > 0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            notify();//唤醒消费者
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生产者
class Producer extends Thread{
    private Clerk clerk;
    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run(){
        System.out.println(getName()+":开始生产产品!");
        while(true){
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

//消费者
class Consumer extends Thread{
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run(){
        System.out.println(getName()+":开始消费产品!");
        while(true){
            try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

运行结果:
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值