zoukankan      html  css  js  c++  java
  • Java 多线程详解(三)------线程的同步

      介绍完如何创建进程以及线程了,那么我们接着来看一个实例:

      利用多线程模拟 3 个窗口卖票

    第一种方法:继承 Thread 类

     创建窗口类 TicketSell 

    package com.ys.thread;
    
    public class TicketSell extends Thread{
    	//定义一共有 50 张票,注意声明为 static,表示几个窗口共享
    	private static int num = 50;
    	
    	//调用父类构造方法,给线程命名
    	public TicketSell(String string) {
    		super(string);
    	}
    	@Override
    	public void run() {
    		//票分 50 次卖完
    		for(int i = 0 ; i < 50 ;i ++){
    			if(num > 0){
    				try {
    					sleep(10);//模拟卖票需要一定的时间
    				} catch (InterruptedException e) {
    					// 由于父类的 run()方法没有抛出任何异常,根据继承的原则,子类抛出的异常不能大于父类, 故我们这里也不能抛出异常
    					e.printStackTrace();
    				}
    				System.out.println(this.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
    			}
    		}
    	}
    	
    
    }
    

      创建主线程测试:

    package com.ys.thread;
    
    public class TestTicket {
    
    	public static void main(String[] args) {
    		//创建 3 个窗口
    		TicketSell t1 = new TicketSell("A窗口");
    		TicketSell t2 = new TicketSell("B窗口");
    		TicketSell t3 = new TicketSell("C窗口");
    		
    		//启动 3 个窗口进行买票
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

      结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同

    B窗口卖出一张票,剩余48张
    A窗口卖出一张票,剩余47张
    C窗口卖出一张票,剩余49张
    C窗口卖出一张票,剩余46张
    B窗口卖出一张票,剩余44张
    A窗口卖出一张票,剩余45张
    A窗口卖出一张票,剩余43张
    ...
    C窗口卖出一张票,剩余5张
    A窗口卖出一张票,剩余4张
    B窗口卖出一张票,剩余3张
    A窗口卖出一张票,剩余2张
    C窗口卖出一张票,剩余3张
    B窗口卖出一张票,剩余1张
    C窗口卖出一张票,剩余0张
    A窗口卖出一张票,剩余-1张

    第二种方法:实现 Runnable 接口

      创建窗口类 TicketSellRunnable

    package com.ys.thread;
    
    public class TicketSellRunnable implements Runnable{
    
    	//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
    	private int num = 50;
    	
    	@Override
    	public void run() {
    		//票分 50 次卖完
    		for(int i = 0 ; i < 50 ;i ++){
    			if(num > 0){
    				try {
    					//模拟卖一次票所需时间
    					Thread.sleep(10);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
    			}
    		}
    	}
    
    }
    

      创建主线程测试:

    package com.ys.thread;
    
    public class TicketSellRunnableTest {
    	public static void main(String[] args) {
    		TicketSellRunnable t = new TicketSellRunnable();
    		
    		Thread t1 = new Thread(t,"A窗口");
    		Thread t2 = new Thread(t,"B窗口");
    		Thread t3 = new Thread(t,"C窗口");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    
    }
    

      结果:同理为了篇幅我们也省略了中间的一些结果

    B窗口卖出一张票,剩余49张
    C窗口卖出一张票,剩余48张
    A窗口卖出一张票,剩余49张
    B窗口卖出一张票,剩余47张
    A窗口卖出一张票,剩余45张
    ......
    A窗口卖出一张票,剩余4张
    C窗口卖出一张票,剩余5张
    A窗口卖出一张票,剩余3张
    B窗口卖出一张票,剩余2张
    C窗口卖出一张票,剩余1张
    B窗口卖出一张票,剩余0张
    A窗口卖出一张票,剩余-2张
    C窗口卖出一张票,剩余-1张
    

      

    结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

      

    解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:

    1、使用 同步代码块

    2、使用 同步方法

    3、使用 锁机制

    ①、使用同步代码块

    语法:
    synchronized (同步锁) {
        //需要同步操作的代码          
    }
    
    同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
    Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象
    
    注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着
    

      实例:

    public void run() {
    		//票分 50 次卖完
    		for(int i = 0 ; i < 50 ;i ++){
    			//这里我们使用当前对象的字节码对象作为同步锁
    			synchronized (this.getClass()) {
    				if(num > 0){
    					try {
    						//模拟卖一次票所需时间
    						Thread.sleep(10);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
    				}
    			}
    			
    		}
    	}

    ②、使用 同步方法

    语法:即用  synchronized  关键字修饰方法

    @Override
    	public void run() {
    		//票分 50 次卖完
    		for(int i = 0 ; i < 50 ;i ++){
    			sell();
    			
    		}
    	}
    	private synchronized void sell(){
    		if(num > 0){
    			try {
    				//模拟卖一次票所需时间
    				Thread.sleep(10);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
    		}
    	}
    

      注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。

    ③、使用 锁机制

    public interface Lock
    

      主要方法:

      常用实现类:

    public class ReentrantLock
    extends Object
    implements Lock, Serializable
    //一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

      例子:

    package com.ys.thread;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TicketSellRunnable implements Runnable{
    
    	//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
    	private int num = 50;
    	//创建一个锁对象
    	Lock l = new ReentrantLock();
    	
    	@Override
    	public void run() {
    		//票分 50 次卖完
    		for(int i = 0 ; i < 50 ;i ++){
    			//获取锁
    			l.lock();
    			try {
    				if(num > 0){
    				//模拟卖一次票所需时间
    				Thread.sleep(10);
    				System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
    				}
    			} catch (Exception e) {
    				e.printStackTrace();
    			}finally{
    				//释放锁
    				l.unlock();
    			}
    			
    			
    		}
    	}
    	private void sell(){
    		
    	}
    
    }
  • 相关阅读:
    系列化与反系列化
    改造一下C# Substring()函数
    在C#后端处理一些结果然传给前端Javascript或是jQuery
    ASP.NET MVC传送参数至服务端
    MySQL 操作命令梳理(3)-pager
    MySQL 操作命令梳理(2)-alter(update、insert)
    redis持久化策略梳理及主从环境下的策略调整记录
    php-redis扩展模块安装记录
    centos下部署redis服务环境及其配置说明
    Iptables 规则用法小结
  • 原文地址:https://www.cnblogs.com/ysocean/p/6883729.html
Copyright © 2011-2022 走看看