一.并发编程
-
卖票问题
类似于12306卖火车票的问题。假设有10个售票员,共售150张票。
1.1. 首先尝试单进程顺序执行public class TicketSeller1{ public void SellTicket(int agent,int numTicketToSell){ while(numTicketToSell>0){ System.out.println("agent:"+agent+"sells a ticket"); numTicketToSell--; } } public static void main(String[] args) { TicketSeller1 s=new TicketSeller1(); int numAgent=10; int numTicket=150; for(int agent=1;agent<=numAgent;agent++){ s.SellTicket(agent,numTicket/numAgent); } } }
结果:10个售票员,每人售票15张。
1.2. 给每个售货员加上线程,进行多线程并发执行public class TicketSeller2 implements Runnable{ public int agent; public int numTicketToSell; @Override public void run(){ while(numTicketToSell>0){ System.out.println("agent:"+agent+"sells a ticket,"+numTicketToSell+" ticket left"); numTicketToSell--;//原子操作? } System.out.println("agent:"+agent+"all done!"); } public static void main(String[] args) { int numAgent=10; int numTicket=150; for(int agent=1;agent<=numAgent;agent++){ TicketSeller2 thisSeller = new TicketSeller2(); thisSeller.agent=agent; thisSeller.numTicketToSell=numTicket/numAgent; Thread thread = new Thread(thisSeller); thread.start(); } } }
结果:不同售票员之间的相关售票信息无法共享,每个售票员在自顾自卖票,与TicketSeller1类似。
1.3. 将numTicketToSell票数信息设置为static进行共享(注意:不是每个人15张票了,是150张票的信息共享)public class TicketSeller3 implements Runnable{ public int agent; public static int numTicketToSell; @Override public void run(){ while(numTicketToSell>0){ System.out.println("agent:"+agent+" sells:"+numTicketToSell+" ticket"); numTicketToSell--; } System.out.println("agent:"+agent+"all done!"); } public static void main(String[] args) { int numAgent=10; int numTicket=150; for(int agent=1;agent<=numAgent;agent++){ TicketSeller3 thisSeller = new TicketSeller3(); thisSeller.agent=agent; thisSeller.numTicketToSell=numTicket; Thread thread = new Thread(thisSeller); thread.start(); } } }
结果:解决了票数的共享,但是会出现不同售票员在售同一张票的情况。比如售票员1和售票员2 都售卖了第150号票。
问题原因:在处理临界区数据(票数)时,未上锁。
1.4. 加上同步锁:一个线程访问一个对象中的synchronized同步代码块时,其他试图访问该对象的线程将被阻塞。public class TicketSeller4 implements Runnable{ public int agent; public static int numTicketToSell; public static String lock; //public String lock; @Override public void run(){ synchronized(lock){//同步锁 while(numTicketToSell>0){ System.out.println("agent:"+agent+" sells:"+numTicketToSell+" ticket"); numTicketToSell--; } } System.out.println("agent:"+agent+"all done!"); } public static void main(String[] args) { int numAgent=10; int numTicket=150; String lock="mylock"; for(int agent=1;agent<=numAgent;agent++){ TicketSeller4 thisSeller = new TicketSeller4(); thisSeller.agent=agent; thisSeller.numTicketToSell=numTicket; thisSeller.lock=lock; Thread thread = new Thread(thisSeller); thread.start(); } } }
结果:1号售票员将150张票全部卖出才截止。
问题原因:锁的粒度问题(范围过大的问题),导致要将票全部卖完才算一个seller结束。把其他不在临界区的数据也放入了锁内,导致线程效率被降低。若将synchronized放入while循环内也是相同的问题,因为不同进程在进入while的时候都已经获取了numTicketToSell的值,会导致重复或产生负票。
1.5. 改用信号量:有无static都一样,因为所有seller对象用的都是同一个lock的String对象,指向的都是lock变量的地址。但如果将lock放入for内,就不行了。import java.util.concurrent.Semaphore; public class TicketSeller5 implements Runnable{ public int agent; public static int numTicketToSell=150; public static String lock; //public static Semaphore semaphore; public Semaphore semaphore; // @Override public void run(){ try{ while(true){ int num=0; semaphore.acquire(); if(numTicketToSell>0){ num=numTicketToSell; numTicketToSell--; } else{ semaphore.release(); break; } semaphore.release(); System.out.println("agent:"+agent+" sells:"+num+" ticket"); } } catch(Exception e){} System.out.println("agent:"+agent+"all done!"); } public static void main(String[] args) { int numAgent=10; //int numTicket=150; String lock="mylock"; Semaphore semaphore = new Semaphore(1); for(int agent=1;agent<=numAgent;agent++){ TicketSeller5 thisSeller = new TicketSeller5(); thisSeller.agent=agent; //thisSeller.numTicketToSell=numTicket; thisSeller.lock=lock; thisSeller.semaphore=semaphore; Thread thread = new Thread(thisSeller); thread.start(); } } }
结果:150票由10位售票员并发执行。成功!
-
下载器问题(生产者-消费者问题)
设置8个槽位——Size为8的数组,需要读取26个英文字母。
2.1. 不加任何锁,只是分了读/写线程import java.util.concurrent.Semaphore; class BufferChar{ public static char[] buffer=new char[8]; } class Witer implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ BufferChar.buffer[i%8]=(char)(i+65); } } } class Reader implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ System.out.println(BufferChar.buffer[i%8]); } } } public class DownLoad1{ public static void main(String[] args) { Witer w = new Witer(); Reader r = new Reader(); Thread threadw = new Thread(w); Thread threadr = new Thread(r); threadw.start(); threadr.start(); } }
结果:只能重复地读到XYZSTUW这几位字母。
问题原因:写的速度远远大写读的速度,导致8个槽位被最后的8个字母(YZSTUVWX)覆盖,第一次读的时候就已经是最后8个字母了
2.2. 加上同步锁import java.util.concurrent.Semaphore; class BufferChar{ public static char[] buffer=new char[8]; } class Witer implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ synchronized(BufferChar.buffer){ BufferChar.buffer[i%8]=(char)(i+65); } } } } class Reader implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ synchronized(BufferChar.buffer){ System.out.println(BufferChar.buffer[i%8]); } } } } public class DownLoad2{ public static void main(String[] args) { Witer w = new Witer(); Reader r = new Reader(); Thread threadw = new Thread(w); Thread threadr = new Thread(r); threadw.start(); threadr.start(); } }
结果:同2.1无差别:只能重复地读到XYZSTUW这几位字母。
问题原因:读写用的是同一个锁,所以写线程先开始的话,要等26个字母全部写入后再读。其实本质与2.1没差别,都是全部写完了再读。2.3. 改用信号量
import java.util.concurrent.Semaphore; class BufferChar{ public static Semaphore semaphore=new Semaphore(1); public static char[] buffer=new char[8]; } class Witer implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ try{ BufferChar.semaphore.acquire(); BufferChar.buffer[i%8]=(char)(i+65); BufferChar.semaphore.release(); } catch(Exception e){} } } } class Reader implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ try{ BufferChar.semaphore.acquire(); System.out.println(BufferChar.buffer[i%8]); BufferChar.semaphore.release(); } catch(Exception e){} } } } public class DownLoad3{ public static void main(String[] args) { Witer w = new Witer(); Reader r = new Reader(); Thread threadw = new Thread(w); Thread threadr = new Thread(r); threadw.start(); threadr.start(); } }
结果:同2.1无差别:只能重复地读到XYZSTUW这几位字母。
问题原因:读写用的是同一个信号量。由于写线程先开始,速度远远大于读线程。其实本质与2.1没差别,都是全部写完了再读。2.4. 改用两个信号量
设置写线程的empty信号量,表示槽位空闲值,初始为8;当槽位有空闲才能写,写完释放读线程信号量。读线程full信号量表示写操作时不能进行读操作。由于先写再读,所以full信号量初始为0,不能读,要先写入才行。
个人理解:整体相当于,在不被读的情况下,写操作最多执行八次。将槽位全部填充满后,就无法在执行写操作。同步地,被读之后就多出一个空位,就可以继续执行写操作。从而实现了,26个字母的顺序写和读。import java.util.concurrent.Semaphore; class BufferChar{ public static Semaphore empty=new Semaphore(8); public static Semaphore full=new Semaphore(0); public static char[] buffer=new char[8]; } class Witer implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ try{ BufferChar.empty.acquire(); BufferChar.buffer[i%8]=(char)(i+65); BufferChar.full.release(); } catch(Exception e){} } } } class Reader implements Runnable{ @Override public void run(){ for(int i=0;i<26;i++){ try{ BufferChar.full.acquire(); System.out.println(BufferChar.buffer[i%8]); BufferChar.empty.release(); } catch(Exception e){} } } } public class DownLoad4{ public static void main(String[] args) { Witer w = new Witer(); Reader r = new Reader(); Thread threadw = new Thread(w); Thread threadr = new Thread(r); threadw.start(); threadr.start(); } }
结果:26个英文字母实现了顺序写读。
注意:empty=new Semaphore(8);empty的初始值不一定要设成8。大于0 小于8都可以,但是会导致空间浪费。若empty=new Semaphore(1),相当于只能写入1个字符,要等被读掉才可以继续写。
-
哲学家吃饭问题
假设,圆形饭桌上共有5个哲学家准备吃饭,每个人左右两边各1根筷子。
初始化筷子的信号值数组,每根筷子都是可以被获取的。允许饭桌上最大的同时进餐人数为4。(5就会产生死锁)import java.util.concurrent.Semaphore; class BufferSomething{ public static Semaphore[] kuaiZi={new Semaphore(1),new Semaphore(1),new Semaphore(1), new Semaphore(1),new Semaphore(1)}; public static Semaphore allowEat=new Semaphore(4); } class Phi implements Runnable{ public int phiId; @Override public void run(){ try{ for(int i=0;i<1000;i++){ //饭桌上每个人的吃饭次数 BufferSomething.allowEat.acquire(); BufferSomething.kuaiZi[phiId].acquire(); BufferSomething.kuaiZi[(phiId+1)%5].acquire(); System.out.println("phi:"+phiId+" eating and stop thinking"); BufferSomething.kuaiZi[phiId].release(); BufferSomething.kuaiZi[(phiId+1)%5].release(); BufferSomething.allowEat.release(); System.out.println("phi:"+phiId+" eated and start thinking"); } System.out.println("All Done!"); } catch(Exception e){ System.out.println("error!!!"); } } } public class PhiProblem{ public static void main(String[] args) { for(int i=0;i<5;i++){ Phi phi=new Phi(); phi.phiId=i; Thread phiThread=new Thread(phi); phiThread.start(); } } }
结果:不会产生死锁。
-
手工艺店铺管理问题
整个流程为:顾客告诉店员要多少产品,店员(店员和产品一对一)一个一个完成制造,经理审核好该产品后告诉店员产品质量ok不,如果不ok的话店员继续制造,ok交给顾客。
直到所有工艺品都被完成交给顾客,顾客结账给收银员后走人。
因此,顾客和店员的个数相同,收银员和经理各一位。有顾客才需要店员,所以店员线程是在顾客类方法中创建的。而顾客、收银员、经理是一定存在的,就在main方法里面。import java.util.concurrent.Semaphore; import java.util.Random; //手工艺品 class Craft{ public static int passed=0; public static Semaphore requestinspection = new Semaphore(0); public static Semaphore finishinspection = new Semaphore(0); public static Semaphore managerlock = new Semaphore(1); } //收银员 class Cashier implements Runnable{ public static int number=0; public static Semaphore numberlock=new Semaphore(1); public static Semaphore cashierrequested=new Semaphore(0); public static Semaphore customers[]={new Semaphore(0),new Semaphore(0),new Semaphore(0), new Semaphore(0),new Semaphore(0),new Semaphore(0), new Semaphore(0),new Semaphore(0),new Semaphore(0), new Semaphore(0)}; @Override public void run(){ try{ for(int i=0;i<10;i++){ //等待顾客 cashierrequested.acquire(); System.out.println(i+":checked out"); customers[i].release(); } }catch(Exception e){ System.out.println("error!!!"); } } } //顾客 class Customer implements Runnable{ public int numCrafts; public int customerid; @Override public void run(){ try{ System.out.println(customerid+": customer entry shop"); for(int i=0;i<numCrafts;i++){ Service service=new Service(); service.customerid=customerid; Thread serviceThread = new Thread(service); //一个产品 启动一个店员 去制作产品 serviceThread.start(); System.out.println(customerid+": customer comsune "+i+" craft,total:"+numCrafts); } for(int i=0;i<numCrafts;i++){ //等待店员做完一个产品 Service.servicedone.acquire(); System.out.println(customerid+": customer get "+i+" craft,total:"+numCrafts); } System.out.println(customerid+": customer start checkout"); // 收银员一次只接待一位顾客 Cashier.numberlock.acquire(); int number=Cashier.number++; Cashier.numberlock.release(); //让 Cashier.cashierrequested.release(); //等待收银员结算完 第number位顾客就结束了 即整个流程结束 Cashier.customers[number].acquire(); }catch(Exception e){ System.out.println("error!!!"); } } } //经理 class Manager implements Runnable{ public static int approved=0; public static int inspected=0; @Override public void run(){ try{ while(approved<HandmadeShop.total){ //审查通过的产品数小于总的产品数 //等待产品的申请 Craft.requestinspection.acquire(); inspected++; Craft.passed=new Random().nextInt(2); if(Craft.passed==1){ approved++; } Craft.finishinspection.release(); } }catch(Exception e){ System.out.println("error!!!"); } } } //店员 class Service implements Runnable{ public int customerid; public static Semaphore servicedone=new Semaphore(0); @Override public void run(){ try{ int passed=0; while(passed==0){ //直到店员将该产品制作成功 System.out.println("Make Drink for customer:"+customerid); //每做完一个产品 就给经理审核 并且 经理一次只能审核一个产品 Craft.managerlock.acquire(); Craft.requestinspection.release(); //等待经理审核结束 Craft.finishinspection.acquire(); passed=Craft.passed; Craft.managerlock.release(); } //店员做完该产品 servicedone.release(); } catch(Exception e){ System.out.println("error!!!"); } } } public class HandmadeShop{ public static int total=0; public static void main(String[] args) { for(int i=0;i<10;i++){ //假设共有10位顾客 Customer customer=new Customer(); int numCrafts = new Random().nextInt(4) + 1; //每位顾客需要的产品数量 customer.numCrafts=numCrafts; customer.customerid=i; Thread customerThread = new Thread(customer); customerThread.start(); total=total+numCrafts; //总产品量 } Cashier cashier=new Cashier(); Thread cashierThread=new Thread(cashier); cashierThread.start(); Manager manager=new Manager(); Thread managerThread=new Thread(manager); managerThread.start(); } }
结果:不会产生死锁。