Java从遗忘到入门——Day06

渴望逛街 自由,于是出门体验了一把,发现还是在家方便,记录一下出门吃火锅的运动轨迹:上公交车量体温——进银行量体温——进商场量体温——进火锅店量体温——上出租车量体温——回小区量体温。不得不说疫情期间的防控力度真的很大,为基层人员和配合的市民们点个赞~

Java从遗忘到入门——Day06

死锁

死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

示例代码:

class A {}
class B {}

class Test implements Runnable {
    boolean flag;
    static A a = new A();
    static B b = new B();

    Test(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (a) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象a的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象b的锁");
                }
            }
        } else {
            synchronized (b) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象b的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象a的锁");
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Test(true));
        t1.start();
        Thread t2 = new Thread(new Test(false));
        t2.start();
    }
}

运行结果为:
在这里插入图片描述
程序没有运行完,死锁了。

解除死锁

死锁是由于“同步块需要同时持有多个对象锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁。

上述代码可以改成:

class A {}
class B {}

class Test implements Runnable {
    boolean flag;
    static A a = new A();
    static B b = new B();

    Test(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (a) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象a的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (b) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象b的锁");
            }
        } else {
            synchronized (b) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象b的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (a) {
                System.out.println("线程" + Thread.currentThread().getName() + "拿到了对象a的锁");
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Test(true));
        t1.start();
        Thread t2 = new Thread(new Test(false));
        t2.start();
    }
}

运行结果:
在这里插入图片描述
当然工作中解除死锁的做法具体还需要根据业务内容而定,但宗旨就是尽量不要让一个同步代码块先后持有多个锁,而有不得已的情况就需特别注意死锁问题。

消费者生产者

  • 生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
  • 消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
  • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

示意代码:

public class Test {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        Produce produce = new Produce(myStack);
        produce.start();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Consumer consumer = new Consumer(myStack);
        consumer.start();
    }
}

class Production {
    int id;

    Production(int id) {
        this.id = id;
    }
}

class MyStack {
    int index = 0;
    Production[] p = new Production[10];

    synchronized void push(Production production) {
        while (index == p.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产了" + production.id + "号产品");
        this.notifyAll();
        p[index++] = production;
    }

    synchronized void pop() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();
        Production production = p[--index];
        System.out.println("消费了第" + production.id + "号产品");
    }
}

class Produce extends Thread {
    MyStack myStack;

    Produce(MyStack ms) {
        this.myStack = ms;
    }

    @Override
    public void run() {
        for (int i = 0; i < 15; ++i) {
            myStack.push(new Production(i));
        }
    }
}

class Consumer extends Thread {
    MyStack myStack;

    Consumer(MyStack ms) {
        this.myStack = ms;
    }

    @Override
    public void run() {
        for (int i = 0; i < 15; ++i) {
            myStack.pop();
        }
    }
}

运行结果:
在这里插入图片描述
在这里插入图片描述
(中间由于截图原因隔断了 )

线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
在这里插入图片描述

任务定时调度

通过Timer和Timetask,可以实现定时启动某个线程。

java.util.Timer
在这种实现方式中,Timer类作用是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的。

java.util.TimerTask
TimerTask类是一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。

在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。

示意代码:

public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new MyTask();
        //timer.schedule(task,1000); //表示1秒后执行1次
        //timer.schedule(task,2000,1000); //表示2秒后每隔1秒执行一次
        // 以下是指定在2020年3月21号20点38分20秒执行一次
        GregorianCalendar gregorianCalendar = new GregorianCalendar(2020, 2, 21, 20, 38, 20);
        timer.schedule(task, gregorianCalendar.getTime());
    }
}

class MyTask extends TimerTask {
    @Override
    public void run() {
        System.out.println("定时执行1次!");
    }
}

注意:

  1. 在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。
  2. 实际开发中,可以使用开源框架quanz,更加方便的实现任务定时调度。实际上,quanz底层原理就是我们这里介绍的内容。
发布了41 篇原创文章 · 获赞 9 · 访问量 9841

猜你喜欢

转载自blog.csdn.net/Serena0814/article/details/105012194