渴望逛街 自由,于是出门体验了一把,发现还是在家方便,记录一下出门吃火锅的运动轨迹:上公交车量体温——进银行量体温——进商场量体温——进火锅店量体温——上出租车量体温——回小区量体温。不得不说疫情期间的防控力度真的很大,为基层人员和配合的市民们点个赞~
死锁
死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
示例代码:
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次!");
}
}
注意:
- 在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。
- 实际开发中,可以使用开源框架quanz,更加方便的实现任务定时调度。实际上,quanz底层原理就是我们这里介绍的内容。