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张 

  

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

  Java 多线程详解(三)------线程的同步编程语言

解决办法分析:即我们不能同时让超过两个以上的线程进入到 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 

  主要方法:

Java 多线程详解(三)------线程的同步编程语言

  常用实现类:

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(){ 
		 
	} 
 
}

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/14153.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论