对线程的补充
1. 线程死锁:
-
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁 -
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
死锁代码示例:
public class DeadLock implements Runnable{
int flag = 1;
Object obj1 = new Object();
Object obj2 = new Object();
@Override
public void run() {
if (flag == 1) {
synchronized (obj1) {
System.out.println("obj1的外锁");
try {
Thread.sleep(1000);
flag = 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("obj2的内锁");
}
}
}
if (flag == 0) {
synchronized (obj2) {
System.out.println("obj2的外锁");
try {
Thread.sleep(1000);
flag = 1;
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("obj1的内锁");
}
}
}
}
public static void main(String[] args) {
DeadLock dl1 = new DeadLock();
new Thread(dl1).start();
new Thread(dl1).start();
}
}
运行结果:  可以看到右上角的方块是亮红的,这意味着程序没有终止,但是控制台只打印了四行就没再继续打印了
注意打印的最后两行,打印了obj1的外锁之后就没有下文了,这说明obj1在打印后去锁obj2,可是obj2却在这个时候去锁obj1,这个时候两个锁都没有放开,谁都无法拿到对方的资源,死锁就是这样发生的
2. 线程的通信
线程的通信就是一个线程会利用到另一个线程处理后的数据
-
wait() 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。 -
notify()
- 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- 唤醒条件:当前线程必须对该对象有监控权(synchronized)
- notifyAll()
- 唤醒正在排队等待资源的所有线程结束等待,最好用在两个以上的线程中
- 唤醒条件:当前线程必须对该对象有监控权(synchronized)
注意:
以上三个方法都必须用在synchronized修饰的代码块中或方法中,否则会报 java.lang.IllegalMonitorStateException异常
其中很典型的就是模拟生产者和消费者
代码示例:
class Clerk {
private int product = 0;
public synchronized void addProduct() {
if (product >= 30) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
product++;
System.out.println("生产者生产了第" + product + "个商品");
notifyAll();
}
}
public synchronized void getProduct() {
if (this.product <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println("消费者取走了第" + (product--) + "个商品");
notifyAll();
}
}
}
class Producer implements Runnable{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产商品");
while(true) {
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable{
Clerk clerk;
public Consumer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
while(true) {
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.getProduct();
}
}
}
public class Test6 {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Thread producerThread = new Thread(new Producer(clerk));
Thread consumerThread = new Thread(new Consumer(clerk));
producerThread.start();
consumerThread.start();
}
}
运行结果:  可以看到商品不断地被生成和取走
注意: wait()方法只是暂时失去锁,程序的执行暂停,获得锁后程序会从停止处继续执行而非重新开始执行
而sleep()方法和yield()方法只会使线程延时执行,不会让线程失去锁
3. 线程的声明周期流程图
 等待与计时等待的区别:
等待就是线程没有抢到CPU时间片无法执行的那一段时间,可能很短暂也可能很长,无法确定是多久
计时等待就是人为的给线程调用sleep()方法,有固定的等待时间
4. 一道通过LOCK改写的题目
使用线程在控制台打印"回首向来萧瑟处,归去,也无风雨也无晴"
class Shower {
int count = 0;
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void show1() throws InterruptedException {
for (int i = 0; i < 100; i++) {
lock.lock();
while (count != 0) {
c1.await();
}
Thread.sleep(20);
System.out.println("回首向来萧瑟处");
count = 1;
c2.signal();
lock.unlock();
}
}
public void show2() throws InterruptedException {
for (int i = 0; i < 100; i++) {
lock.lock();
while (count != 1) {
c2.await();
}
Thread.sleep(20);
System.out.println("归去");
count = 2;
c3.signal();
lock.unlock();
}
}
public void show3() throws InterruptedException {
for (int i = 0; i < 100; i++) {
lock.lock();
while (count != 2) {
c3.await();
}
Thread.sleep(20);
System.out.println("也无风雨也无晴");
count = 0;
c1.signal();
lock.unlock();
}
}
}
public class Test1 {
public static void main(String[] args) {
Shower s = new Shower();
new Thread() {
@Override
public void run() {
try {
s.show1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread() {
@Override
public void run() {
try {
s.show2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread() {
@Override
public void run() {
try {
s.show3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
运行结果: 
可以看到,三句话循环打印了很多遍 Condition 有一个很大的优点就是可以指定线程去唤醒和睡眠,这是很节省内存的
5.三道练习题
- 模拟多个人通过一个山洞,这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒。随机生成10个人,同时准备过此山洞,显示每次通过山洞人的姓名。
class Cave implements Runnable {
int count = 1;
@Override
public void run() {
cross();
}
private synchronized void cross() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "通过了山洞,他是第" + (count++) + "个过山洞的");
}
}
public class Test3 {
public static void main(String[] args) {
Cave c = new Cave();
for(int i=1;i <= 10;i++){
new Thread(c,i+"号勇士").start();
}
}
}
运行结果: 
- 有100个限量版的水杯,但是只能通过实体店和官网才能进行购买,并且分别统计卖了多少。请用线程进行模拟并设置线程名称用来代表售出途径,再将信息打印出来。
class Cup implements Runnable{
int num = 100;
Map<String,Integer> map = new HashMap<>();
@Override
public void run() {
while(true) {
synchronized (Cup.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num > 0) {
String key = Thread.currentThread().getName();
if (map.containsKey(key)) {
map.put(key, map.get(key)+1);
}else {
map.put(key, 1);
}
System.out.println(key + "共卖出了" + map.get(key) + "个杯子");
System.out.println("还剩" + (--num) + "个杯子");
}else {
break;
}
}
}
}
}
public class Test4 {
public static void main(String[] args) {
Cup c = new Cup();
new Thread(c,"实体店").start();
new Thread(c,"网店").start();
}
}
运行结果: 
- 有一辆班车除司机外只能承载80个人,假设前中后三个车门都能上车,如果坐满则不能再上车。请用线程模拟上车过程并且在控制台打印出是从哪个车门上车以及剩下的座位数。
class Car implements Runnable{
int num = 80;
@Override
public void run() {
while(true) {
synchronized (Car.class) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num < 1) {
break;
}
System.out.println(Thread.currentThread().getName() + "上车了");
System.out.println("还剩" + (--num) + "个座位");
}
}
}
}
public class Test5 {
public static void main(String[] args) {
Car c = new Car();
new Thread(c,"前门").start();
new Thread(c,"中门").start();
new Thread(c,"后门").start();
}
}
运行结果: 
还是没太懂多线程…
|