• 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()唤醒了。

  • 相关阅读:
    Ubunut16.04 安装 Theano+GPU
    ubuntu源与常用python配置pip源(win)、pip常用命令
    集群(heartbeat)搭建
    Linux下搭建企业共享目录方案之------samba
    LAMP的安装和注意事项
    Linux最小化安装,忘记安装开发工具的解决方法
    去掉Linux尖锐的提示音
    最小化安装CentOS7,没有ifconfig命令---yum search command_name搜索未知包名
    编译安装php-5.4.44
    configure: error: Please reinstall the libcurl distribution
  • 原文地址:https://www.cnblogs.com/theory/p/11884318.html
Copyright © 2020-2023  润新知