前言:
我们通过几个示例对JAVA中线程的几个问题进行讲述:同步、死锁、交互
//TODO 线程池
java线程的同步问题
示例背景为:火车站有一个系统,有票20000张,允许工作人员往里面加票,顾客从其中购票
现在我们用JAVA来模拟一下这个场景:顾客从其中购票20000次,工作人员加票20000次,两者同时进行。
这是Ticket类,保存有票的数量和对票的操作
/*
* Ticket 类 -- 保存有票的数量,以及对票的操作
*/
public class Ticket{
private int num;
public Ticket(int num) {
this.num = num;
}
public int getNum() {
return num;
}
//加票
public void addTicket() {
num = num+1;
}
//减票
public void subTicket() {
num = num-1;
}
}
以下是工作人员用20000个线程加票和客户用20000个线程减票,正常情况下剩余票结果为20000不变。
public class Main {
public static void main(String[] args) {
Ticket ticket = new Ticket(20000);//有10000张票
int time = 20000;
Thread[] addThread = new Thread[time];
Thread[] subThread = new Thread[time];
//顾客购票
for(int i = 0; i < time; i++) {
Thread client = new Thread() {
public void run() {
ticket.subTicket();
}
};
client.start();
subThread[i] = client;
}
//工作人员往系统中加票
for(int i = 0; i<time; i++) {
Thread worker = new Thread() {
public void run() {
ticket.addTicket();
}
};
worker.start();
addThread[i] = worker;
}
try {
for(Thread thread : addThread) {
thread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
for(Thread thread : subThread) {
thread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket.getNum());
}
}
但是运行结果如下(具有很多种结果,以下是其中一种):
20002
分析:以此结果为例,对出现上述结果的原因
假设运行到ticket的剩余票数为10000
①某个加票线程得到ticket(num=10000),并使得票数加一(10001),但还未写回num
②某个减票线程也得到了ticket(num=10000),使票数减一(9999),但还未写回num
③减票线程将结果9999写回num,此时ticket(num=9999)
④加票线程将结果10001写回num,此时ticket(num=10001)
结果明显不应该是10001,原因在于减票线程在加票线程还未运行结束时,就取了ticket的值(脏数据),如果设计一种方案,使得加票线程在运行某方法时,不允许减票线程取num数据,则可以解决脏数据问题。
因为需要限定当num有一个线程使用时,其他线程不得读取num,所以对num进行synchronized限制,synchronized表示当前线程,独占对象,防止其他线程操作对象。这样在某个线程对ticket进行addTicket和subTicket操作时,其他线程不得操作ticket,防止了读取脏数据。
public void addTicket() {
synchronized (this) {
num = num + 1;
}
}
public void subTicket() {
synchronized (this) {
num = num - 1;
}
}
java线程的死锁问题
示例背景:有一户人家居住着一个父亲和两个儿子,父亲买了一把枪和一个子弹给儿子们玩,哥哥迅速拿到了枪,弟弟迅速拿到了子弹,哥哥:“你把子弹给我”,弟弟:“你把枪给我”,双方陷入了僵持。
public class Main {
public static void main(String[] args) {
String gun = "gun";//枪
String bull = "bull";//子弹
Thread old = new Thread() {
public void run() {
synchronized (gun) {
System.out.println("哥哥抢到了枪");
try {
Thread.sleep(1000);//哥哥迟钝了1000ms,给了弟弟抢占子弹的时间
System.out.println("哥哥试图抢到子弹");
System.out.println("哥哥等待弟弟交出子弹...");
synchronized (bull) {
System.out.println("哥哥抢到了子弹");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
old.start();
Thread young = new Thread() {
public void run() {
synchronized (bull) {
System.out.println("弟弟抢到了子弹");
try {
Thread.sleep(1000);//弟弟迟钝了1000ms,给了哥哥抢枪的时间
System.out.println("弟弟试图抢到枪");
System.out.println("弟弟等待哥哥交出枪...");
synchronized (gun) {
System.out.println("弟弟抢到了枪");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
young.start();
}
}
哥哥抢到了枪
弟弟抢到了子弹
哥哥试图抢到子弹
哥哥等待弟弟交出子弹...
弟弟试图抢到枪
弟弟等待哥哥交出枪...
哥哥等待弟弟交出子弹,弟弟等待哥哥交出枪,双方僵持,造成死锁。
java线程的交互问题
示例背景为:火车站有一个系统,有票10张,允许工作人员往里面加票,顾客从其中购票。但是当票数为0时,禁止顾客购票。
重点:当票数为0时,禁止顾客线程购票;当票数大于0时,唤醒顾客购票。
使用wait() -- 阻塞和notify() -- 唤醒
/*
* Ticket 类 -- 保存有票的数量,以及对票的操作
*/
public class Ticket{
private int num;
public Ticket(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public synchronized void addTicket() {
num = num+1;
System.out.println("加入一张票,当前票数为:"+num);
this.notify();//唤醒由于num<0阻塞的客户线程
}
public synchronized void subTicket() {
if(num == 0) {
try {
this.wait();//阻塞客户线程,不准买票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println("购出一张票,当前票数为:"+num);
}
}
为了效果,工作人员缓慢地加票,客户迅速购票
public class Main {
public static void main(String[] args) {
Ticket ticket = new Ticket(10);
Thread client = new Thread() {
public void run() {
while(true) {
ticket.subTicket();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
client.start();
Thread worker = new Thread() {
public void run() {
while(true) {
ticket.addTicket();
try {
Thread.sleep(1000);//为了效果,让工作人员慢点加票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.start();
}
}
打印结果为(部分结果):
购出一张票,当前票数为:9
加入一张票,当前票数为:10
购出一张票,当前票数为:9
购出一张票,当前票数为:8
购出一张票,当前票数为:7
购出一张票,当前票数为:6
购出一张票,当前票数为:5
购出一张票,当前票数为:4
购出一张票,当前票数为:3
购出一张票,当前票数为:2
购出一张票,当前票数为:1
加入一张票,当前票数为:2
购出一张票,当前票数为:1
购出一张票,当前票数为:0
加入一张票,当前票数为:1
购出一张票,当前票数为:0
加入一张票,当前票数为:1
购出一张票,当前票数为:0
加入一张票,当前票数为:1
购出一张票,当前票数为:0
加入一张票,当前票数为:1
购出一张票,当前票数为:0
加入一张票,当前票数为:1
购出一张票,当前票数为:0
可以看出,当票数为0时,客户已经不能购票(线程被wait()阻塞了) ,当票数不为0时,客户被notify()唤醒了。