JAVA 多线程

前言:

我们通过几个示例对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()唤醒了。

原文地址:https://www.cnblogs.com/theory/p/11884318.html