java基础-多线程应用案例展示
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.smallTestBullKnife; 7 8 public class BeeDemo { 9 public static void main(String[] args) { 10 Box box = new Box(); 11 Bear b1 = new Bear(box , "熊大"); 12 Bear b2 = new Bear(box , "熊二"); 13 b1.start(); 14 b2.start(); 15 for(int i = 1 ; i <= 100 ; i ++){ 16 new Bee(box , "Bee" + i).start(); 17 } 18 } 19 } 20 21 /** 22 * 罐子 23 */ 24 class Box{ 25 public static int MAX = 30 ; 26 private int size = 0 ; 27 28 /** 29 * 添加蜂蜜方法 30 */ 31 public synchronized void add(int cap){ 32 while((size + cap) > MAX){ 33 this.notifyAll(); 34 try { 35 this.wait(); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 } 40 size = size + cap ; 41 this.notifyAll(); 42 } 43 44 /** 45 * 清空罐子 46 */ 47 public synchronized int clear(){ 48 while(size < 20){ 49 this.notifyAll(); 50 try { 51 this.wait(); 52 } catch (InterruptedException e) { 53 e.printStackTrace(); 54 } 55 } 56 int temp = size ; 57 size = 0 ; 58 return temp ; 59 } 60 } 61 62 /** 63 * 生产者 64 */ 65 class Bee extends Thread{ 66 private String beeName ; 67 private Box box ; 68 private int production = 1; 69 public Bee(Box box ,String name){ 70 this.beeName = name ; 71 this.box = box ; 72 } 73 74 public void run() { 75 while(true){ 76 box.add(production); 77 System.out.println(beeName + "生产了" + production +"蜂蜜"); 78 try { 79 Thread.sleep(50); 80 } catch (InterruptedException e) { 81 e.printStackTrace(); 82 } 83 } 84 } 85 } 86 87 /** 88 * 消费者 89 */ 90 class Bear extends Thread{ 91 private String bearName ; 92 private Box box ; 93 public Bear(Box box , String name){ 94 this.bearName = name ; 95 this.box = box ; 96 } 97 98 public void run() { 99 while(true){ 100 101 int size = box.clear(); 102 System.out.println(bearName + " 吃了【" + size +"】个蜂蜜"); 103 } 104 } 105 }
二.有30个和尚,100个馒头,每个和尚最多吃4馒头,最少一个馒头,满足上述条件下,尽快把馒头吃没,使用多线程模拟,和尚就是线程。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.smallTestBullKnife; 7 8 /** 9 * 和尚吃馒头问题 10 */ 11 public class MonkDemo { 12 public static void main(String[] args) { 13 Basket b = new Basket(); 14 for(int i = 0 ; i < 30 ; i ++){ 15 new Monk(b , "和尚" + (i+1)+": ").start(); 16 } 17 } 18 } 19 20 /** 21 * 篮子,100个馒头 22 */ 23 class Basket{ 24 //馒头数量==编号 25 private int count = 100 ; 26 //和尚数量 ==没吃馒头的和尚数量 27 private int numMonks = 30 ; 28 /** 29 * 获取馒头的方法,返回馒头的编号 30 */ 31 public synchronized int getBread(Monk monk){ 32 //和尚第一次吃 33 if(monk.count == 0){ 34 int temp = count ; 35 count -- ; 36 numMonks -- ; 37 return temp ; 38 } 39 //和尚还可以吃的情况 40 else if(monk.count < Monk.MAX){ 41 //判断是否有多余的馒头 42 if(count > numMonks){ 43 int temp = count ; 44 count -- ; 45 return temp ; 46 } 47 else{ 48 return -1 ; 49 } 50 } 51 return -1 ; 52 } 53 } 54 55 /** 56 * 和尚 57 */ 58 class Monk extends Thread{ 59 private String monkName ; 60 private Basket basket ; 61 62 //最少一个馒头 63 public static int MIN = 1 ; 64 //最多4个馒头 65 public static int MAX = 4 ; 66 67 //吃的馒头数 68 public int count ; 69 70 71 public Monk(Basket basket , String monkName){ 72 this.basket = basket ; 73 this.monkName = monkName ; 74 } 75 76 public void run() { 77 while(true){ 78 int no = basket.getBread(this); 79 if(no ==-1){ 80 break ; 81 } 82 else{ 83 count ++ ; 84 System.out.println(monkName + "吃了编号为[" + no + "]的馒头"); 85 } 86 } 87 System.out.println(monkName + "共吃了【" + count + "】馒头"); 88 } 89 }
1 和尚2: 吃了编号为[100]的馒头 2 和尚3: 吃了编号为[99]的馒头 3 和尚3: 吃了编号为[96]的馒头 4 和尚3: 吃了编号为[95]的馒头 5 和尚3: 吃了编号为[94]的馒头 6 和尚3: 共吃了【4】馒头 7 和尚4: 吃了编号为[97]的馒头 8 和尚4: 吃了编号为[93]的馒头 9 和尚4: 吃了编号为[92]的馒头 10 和尚4: 吃了编号为[91]的馒头 11 和尚4: 共吃了【4】馒头 12 和尚2: 吃了编号为[98]的馒头 13 和尚2: 吃了编号为[90]的馒头 14 和尚2: 吃了编号为[89]的馒头 15 和尚2: 共吃了【4】馒头 16 和尚6: 吃了编号为[88]的馒头 17 和尚6: 吃了编号为[87]的馒头 18 和尚6: 吃了编号为[86]的馒头 19 和尚6: 吃了编号为[85]的馒头 20 和尚6: 共吃了【4】馒头 21 和尚1: 吃了编号为[84]的馒头 22 和尚1: 吃了编号为[82]的馒头 23 和尚8: 吃了编号为[83]的馒头 24 和尚8: 吃了编号为[80]的馒头 25 和尚8: 吃了编号为[79]的馒头 26 和尚8: 吃了编号为[78]的馒头 27 和尚8: 共吃了【4】馒头 28 和尚1: 吃了编号为[81]的馒头 29 和尚1: 吃了编号为[77]的馒头 30 和尚1: 共吃了【4】馒头 31 和尚5: 吃了编号为[76]的馒头 32 和尚5: 吃了编号为[75]的馒头 33 和尚12: 吃了编号为[74]的馒头 34 和尚12: 吃了编号为[71]的馒头 35 和尚12: 吃了编号为[70]的馒头 36 和尚12: 吃了编号为[69]的馒头 37 和尚12: 共吃了【4】馒头 38 和尚14: 吃了编号为[68]的馒头 39 和尚14: 吃了编号为[67]的馒头 40 和尚14: 吃了编号为[66]的馒头 41 和尚5: 吃了编号为[73]的馒头 42 和尚5: 吃了编号为[64]的馒头 43 和尚5: 共吃了【4】馒头 44 和尚14: 吃了编号为[65]的馒头 45 和尚14: 共吃了【4】馒头 46 和尚10: 吃了编号为[72]的馒头 47 和尚10: 吃了编号为[63]的馒头 48 和尚10: 吃了编号为[62]的馒头 49 和尚10: 吃了编号为[61]的馒头 50 和尚10: 共吃了【4】馒头 51 和尚9: 吃了编号为[60]的馒头 52 和尚9: 吃了编号为[59]的馒头 53 和尚7: 吃了编号为[58]的馒头 54 和尚7: 吃了编号为[56]的馒头 55 和尚7: 吃了编号为[55]的馒头 56 和尚11: 吃了编号为[54]的馒头 57 和尚11: 吃了编号为[52]的馒头 58 和尚11: 吃了编号为[51]的馒头 59 和尚9: 吃了编号为[57]的馒头 60 和尚9: 吃了编号为[48]的馒头 61 和尚9: 共吃了【4】馒头 62 和尚11: 吃了编号为[50]的馒头 63 和尚11: 共吃了【4】馒头 64 和尚7: 吃了编号为[53]的馒头 65 和尚15: 吃了编号为[45]的馒头 66 和尚15: 吃了编号为[44]的馒头 67 和尚15: 吃了编号为[42]的馒头 68 和尚15: 吃了编号为[41]的馒头 69 和尚15: 共吃了【4】馒头 70 和尚17: 吃了编号为[46]的馒头 71 和尚17: 吃了编号为[39]的馒头 72 和尚19: 吃了编号为[38]的馒头 73 和尚19: 吃了编号为[36]的馒头 74 和尚19: 吃了编号为[35]的馒头 75 和尚19: 吃了编号为[34]的馒头 76 和尚19: 共吃了【4】馒头 77 和尚13: 吃了编号为[47]的馒头 78 和尚13: 吃了编号为[33]的馒头 79 和尚13: 吃了编号为[32]的馒头 80 和尚13: 吃了编号为[31]的馒头 81 和尚13: 共吃了【4】馒头 82 和尚20: 吃了编号为[49]的馒头 83 和尚20: 吃了编号为[30]的馒头 84 和尚20: 吃了编号为[29]的馒头 85 和尚20: 吃了编号为[28]的馒头 86 和尚20: 共吃了【4】馒头 87 和尚22: 吃了编号为[26]的馒头 88 和尚22: 吃了编号为[25]的馒头 89 和尚22: 吃了编号为[23]的馒头 90 和尚22: 吃了编号为[22]的馒头 91 和尚22: 共吃了【4】馒头 92 和尚17: 吃了编号为[37]的馒头 93 和尚17: 吃了编号为[20]的馒头 94 和尚17: 共吃了【4】馒头 95 和尚18: 吃了编号为[40]的馒头 96 和尚18: 吃了编号为[19]的馒头 97 和尚18: 吃了编号为[18]的馒头 98 和尚18: 吃了编号为[17]的馒头 99 和尚18: 共吃了【4】馒头 100 和尚25: 吃了编号为[16]的馒头 101 和尚27: 吃了编号为[15]的馒头 102 和尚27: 吃了编号为[13]的馒头 103 和尚16: 吃了编号为[43]的馒头 104 和尚16: 吃了编号为[11]的馒头 105 和尚16: 吃了编号为[10]的馒头 106 和尚16: 吃了编号为[8]的馒头 107 和尚16: 共吃了【4】馒头 108 和尚26: 吃了编号为[7]的馒头 109 和尚7: 共吃了【4】馒头 110 和尚26: 吃了编号为[6]的馒头 111 和尚26: 吃了编号为[4]的馒头 112 和尚26: 吃了编号为[3]的馒头 113 和尚26: 共吃了【4】馒头 114 和尚29: 吃了编号为[5]的馒头 115 和尚30: 吃了编号为[2]的馒头 116 和尚28: 吃了编号为[9]的馒头 117 和尚28: 共吃了【1】馒头 118 和尚27: 吃了编号为[12]的馒头 119 和尚27: 共吃了【3】馒头 120 和尚25: 吃了编号为[14]的馒头 121 和尚25: 共吃了【2】馒头 122 和尚24: 吃了编号为[21]的馒头 123 和尚24: 共吃了【1】馒头 124 和尚23: 吃了编号为[24]的馒头 125 和尚21: 吃了编号为[27]的馒头 126 和尚21: 共吃了【1】馒头 127 和尚23: 共吃了【1】馒头 128 和尚30: 共吃了【1】馒头 129 和尚29: 吃了编号为[1]的馒头 130 和尚29: 共吃了【2】馒头
三.(熊吃蜂蜜升级版本)两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1到5不等,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光,蜜蜂向罐子中添加尽可能的蜂蜜,如果有剩余的话,下次继续添加剩余的量。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.smallTestBullKnife; 7 8 import java.util.Random; 9 10 public class BeeproDemo { 11 public static void main(String[] args) { 12 BoxPro box = new BoxPro(); 13 Bear b1 = new Bear(box , "熊大"); 14 Bear b2 = new Bear(box , "熊二"); 15 b1.start(); 16 b2.start(); 17 for(int i = 1 ; i <= 100 ; i ++){ 18 new BeePro( "Bee" + i,box).start(); 19 } 20 } 21 } 22 23 /** 24 * 罐子 25 */ 26 class BoxPro{ 27 //定义管子容量的最大值为30 28 public static int MAX = 30 ; 29 //定义当前管子的容量 30 private int size = 0 ; 31 32 /** 33 * 添加蜂蜜方法 34 */ 35 public synchronized void add(int cap) { 36 //判断添加的蜂蜜是否会使得管子变满 37 if (cap > (MAX - size)){ 38 //获取添加蜂蜜多出的量 39 int remain = cap + size -MAX; 40 //如果添加的密封会是管子溢出的话,我们直接把罐子加满即可! 41 size = MAX; 42 //加满罐子后通知其他线程来消费,并让当前线程进入等待队列 43 this.notifyAll(); 44 try { 45 this.wait(); 46 Thread.sleep(500); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 //如果当前线程被唤醒后,我们需要将之前剩余量继续进行添加操作! 51 add(remain); 52 } 53 //如果当前的蜂蜜不能使罐子盛满,我们就直接添加蜂蜜即可! 54 size+=cap; 55 //加完之后需要通知其他线程 56 this.notifyAll(); 57 } 58 59 /** 60 * 清空罐子 61 */ 62 public synchronized int clear(){ 63 while(size < 20){ 64 this.notifyAll(); 65 try { 66 this.wait(); 67 } catch (InterruptedException e) { 68 e.printStackTrace(); 69 } 70 } 71 int temp = size ; 72 size = 0 ; 73 return temp ; 74 } 75 } 76 77 /** 78 * 生产者 79 */ 80 class BeePro extends Thread{ 81 private String beeName ; 82 private BoxPro box ; 83 public BeePro(String name,BoxPro box){ 84 this.beeName = name ; 85 this.box = box ; 86 } 87 88 public void run() { 89 Random r = new Random(); 90 while(true){ 91 int production =r.nextInt(5) + 1; 92 box.add(production); 93 System.out.println(beeName + "生产了" + production +"蜂蜜"); 94 } 95 } 96 } 97 98 /** 99 * 消费者 100 */ 101 class Bear extends Thread{ 102 private String bearName ; 103 private BoxPro box ; 104 public Bear(BoxPro box , String name){ 105 this.bearName = name ; 106 this.box = box ; 107 } 108 109 public void run() { 110 while(true){ 111 int size = box.clear(); 112 System.out.println(bearName + " 吃了【" + size +"】个蜂蜜"); 113 } 114 } 115 }
四.两个售票员一起买100000张票,使用两种加锁方式(synchronize | ReentrantLock),看性能比对。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.smallTestBullKnife; 7 8 import java.util.concurrent.locks.ReentrantLock; 9 10 public class SaleDemo { 11 public static void main(String[] args) throws InterruptedException { 12 TicketPool pool1 = new TicketPool(); 13 Thread[] ts = new Thread[4]; 14 long start = System.currentTimeMillis(); 15 for (int i=0;i<4;i++){ 16 Saler yzj = new Saler("yinzhengjie" + i, pool1, true); 17 ts[i] = yzj; 18 yzj.start(); 19 } 20 for (Thread t : ts) { 21 t.join(); 22 } 23 long end = System.currentTimeMillis(); 24 System.out.printf("传统(normal)买票方式用时为:[%d] ",(end-start)); 25 26 27 TicketPool pool2 = new TicketPool(); 28 start = System.currentTimeMillis(); 29 for (int i=0;i<4;i++){ 30 Saler yzj = new Saler("yinzhengjie" + i, pool2, false); 31 ts[i] = yzj; 32 yzj.start(); 33 } 34 for (Thread t : ts) { 35 t.join(); 36 } 37 end = System.currentTimeMillis(); 38 System.out.printf("轻量级(light)买票方式用时为:[%d] ",(end-start)); 39 40 } 41 } 42 43 /** 44 * 定义票池 45 */ 46 class TicketPool{ 47 private int Tickets = 100000; 48 49 50 /** 51 * 定义传统synchronized方式买票 52 */ 53 public int doGetTickets1(){ 54 //判断是否符合买票的规则,如果票已经卖完了,就直接返回0. 55 if (Tickets <= 0){ 56 return 0; 57 } 58 //使用synchronized方法同步代码块,保证原子性! 59 synchronized(this){ 60 //定义当前票数的编号变量temp 61 int temp = Tickets; 62 //如果卖出去一张票,就将票数自减1. 63 Tickets--; 64 return temp; 65 } 66 } 67 68 ReentrantLock lock = new ReentrantLock(); 69 public int doGetTickets2(){ 70 //判断是否符合买票的规则,如果票已经卖完了,就直接返回0. 71 if (Tickets <= 0){ 72 return 0; 73 } 74 //上锁,知道写锁的代码块,同样也是保证原子性! 75 lock.lock(); 76 //定义当前票数的编号变量temp 77 int temp = Tickets; 78 //如果卖出去一张票,就将票数自减1. 79 Tickets--; 80 //解锁 81 lock.unlock(); 82 return temp; 83 } 84 } 85 86 /** 87 * 定义售票员类 88 */ 89 class Saler extends Thread{ 90 private String saleName; 91 private TicketPool pool; 92 private boolean normal; 93 /** 94 * 95 * @param saleName //制定和售票员名称 96 * @param pool //指定票池 97 * @param normal //是都使用传统方式买票 98 */ 99 public Saler(String saleName,TicketPool pool,boolean normal){ 100 this.saleName = saleName; 101 this.pool = pool; 102 this.normal = normal; 103 } 104 105 public void run() { 106 while (true){ 107 int num = normal ? pool.doGetTickets1():pool.doGetTickets2(); 108 if (num == 0){ 109 return; 110 } 111 // System.out.printf("售票员[%s]出售了第【%d】票 ",saleName,num); 112 113 } 114 115 } 116 } 117 118 /* 119 以上代码输出结果如下: 120 传统(normal)买票方式用时为:[9] 121 轻量级(light)买票方式用时为:[8] 122 */
五.编写socket通信的MyServer,使用分线程完成和每个client的通信。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.socket; 7 8 import java.io.OutputStream; 9 import java.net.InetSocketAddress; 10 import java.net.ServerSocket; 11 import java.net.Socket; 12 13 public class MyServer { 14 public static void main(String[] args) throws Exception { 15 //服务器套接字 16 ServerSocket ss = new ServerSocket(8888) ; 17 while(true){ 18 //接受连接, 19 System.out.println("正在接受连接....."); 20 Socket sock = ss.accept(); 21 new CommThread(sock).start(); 22 } 23 } 24 } 25 26 /** 27 * 服务器和每个客户端的通信线程 28 */ 29 class CommThread extends Thread{ 30 private Socket sock; 31 32 public CommThread(Socket sock){ 33 this.sock = sock ; 34 } 35 36 public void run() { 37 try { 38 //获取远程地址和端口 39 InetSocketAddress addr = (InetSocketAddress) sock.getRemoteSocketAddress(); 40 int port = addr.getPort(); 41 String ip = addr.getAddress().getHostAddress(); 42 System.out.printf("有人连接进来了!! : %s , %d ", ip, port); 43 44 //向客户端发送消息 45 int index = 0; 46 OutputStream out = sock.getOutputStream(); 47 while (true) { 48 index ++ ; 49 out.write(("yinzhengjie" + index + " ").getBytes()); 50 out.flush(); 51 Thread.sleep(1000); 52 } 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 } 57 }
需要启动上述代码的服务端,才能用客户端进行连接操作
1>.使用java编写的客户端连接服务端
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.socket; 7 8 import java.io.BufferedReader; 9 import java.io.InputStream; 10 import java.io.InputStreamReader; 11 import java.net.Socket; 12 13 public class MyClient { 14 public static void main(String[] args) throws Exception { 15 Socket s = new Socket("www.yinzhengjie.org.cn" ,8888) ; 16 System.out.println("连接到服务器了!!"); 17 InputStream in = s.getInputStream(); 18 BufferedReader br = new BufferedReader(new InputStreamReader(in)) ; 19 String line = null ; 20 //获取服务端发来的消息 21 while((line = br.readLine())!= null){ 22 System.out.println("收到消息 : " + line); 23 } 24 } 25 }
测试结果如下:
2>.使用Windows自带的telnet客户端连接服务端
查看接收的数据信息如下:
3>.使用xshell客户端连接服务端
综上所述,有三个端连接了服务端,我们可以查看服务端的输出数据如下:
上述的解决方案当客户端数量过多的话,最终可能会存在资源耗尽的情况,建议使用NIO技术,详情请参考:https://www.cnblogs.com/yinzhengjie/p/9257142.html。