学习记录324@java多线程卖火车票问题分析

原始代码

package com.dream.test01;

public class MyThread extends Thread{
	//共享变量,无论创建多少了对象,这个static是类共享的
	private static int ticket =1000; 
	
	//构造方法,传入线程名字
	public MyThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		while(ticket>0){
			System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
			ticket--;
		}
	}
}

package com.dream.test01;

public class Test01 {

	
	public static void main(String[] args) {
		
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

在这里插入图片描述

问题1

因为是多线程问题,因此出现同步问题,当某个线程打印正在卖票的时候,还没来得及将票数减一,另外的线程就开始运行打印了,就会出现打印重复票的问题;

改进1

将打印和票数减一两行代码进行加锁,让某线程在运行这两段代码的时候,其他线程无法进入同步代码块,这里的问题是锁对象应该是谁。

synchronized 锁住某个对象,只要锁住的是同一对象,就能实现同步,但是注意,不是一定要锁住共享变量所代表的那个对象
synchronized实现的机制是锁对象,每个对象只有一把锁,jvm底层是monitorenter和monitorexit,当线程尝试进入synchronized修饰的代码块或者方法时候,会先执行monitorenter指令,首先尝试获取传入的对象的锁(如果是同步方法,对象是this,也就是方法所在的对象),如果这个对象没有被锁定,或者当前这个线程已经获得了这个锁,就会把锁的计数器加一,并且进入代码块内部,代码运行完毕就解锁,转却的说是monitorexit指令执行,并且锁的计数器减一,当锁计数器为0,代表没有线程持有这个对象锁;

因此我们synchronized()括号内的变量只要是传入一个多个线程共享的同一个对象就可以,比如传入的是"abc"都行,因为"abc"在常量池内,是多个线程共享的对象;这里我们选择新建一个Object对象,但是注意放置的位置且必须是static的;

package com.dream.test01;

public class MyThread extends Thread{
	
	private static int ticket =1000; 
	//这里必须是静态的才行,因为后面创建了三个MyThread对象,但是静态的变量是类共享的
	private static Object obj=new Object(); 
	//构造方法,传入线程名字
	public MyThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		while(ticket>0){
			synchronized (obj) {
				System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
				ticket--;
			}
		}
	}
}

package com.dream.test01;

public class Test01 {

	
	public static void main(String[] args) {
		
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

在这里插入图片描述

问题2

同步的问题已经解决了,但是出现了-1
的情况,原因如下是当ticket1时,线程1进入while,整准备进入synchronized,时间片到了,这时线程2一看,ticket1,也进入while,然后线程1继续进入synchronized执行,执行后ticket为0,然后线程2继续进入synchronized执行,执行后ticket=-1了。

在这里插入图片描述

改进2

在进入synchronized后再次判断ticket的值即可

package com.dream.test01;

public class MyThread extends Thread{
	
	private static int ticket =1000; 
	//这里必须是静态的才行,因为后面创建了三个MyThread对象,但是静态的变量是类共享的
	private static Object obj=new Object(); 
	//构造方法,传入线程名字
	public MyThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		while(ticket>0){
			synchronized (obj) {
				if (ticket>0) {
					System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
					ticket--;
					if (ticket==0) {
						System.out.println(Thread.currentThread().getName()+"票已售完");
					}
				}else {
					System.out.println(Thread.currentThread().getName()+"票已售完");
				}
				
			}
		}
	}
}

在这里插入图片描述

扩展

问题已经解决了,但是我们使用的是同步代码块的方法,还有同步方法和lock的两种方法

同步方法

同步方法,注意同步方法的锁对象是this,就是这个方法所在的类的对象,因为后面新建了三个MyThread 这个类的对象,因此为了保证同一个锁对象,对这个方法使用static即可,这样就是属于类的

package com.dream.test02;

public class MyThread extends Thread{
	
	private static int ticket =1000; 
	
	public MyThread(String name) {
		super(name);
	}
	
	
	@Override
	public void run() {
		while(ticket>0){
			sell();			
		}
	}

//同步方法,注意同步方法的锁对象是this,就是这个方法所在的类的对象,
//因为后面新建了三个MyThread 这个类的对象,因此为了保证同一个所对象,
//对这个方法使用static即可,这样就是属于类的
	private static synchronized void sell() {
		if(ticket>0){
			System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
			ticket--;
			if(ticket == 0){
				System.out.println(Thread.currentThread().getName() + "票已经销售完毕");
			}
		}else {
			System.out.println(Thread.currentThread().getName()+"票已售完");
//					Thread.currentThread().interrupt();
		}
	}
}

package com.dream.test02;

public class Test01 {

	
	public static void main(String[] args) {
		
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

Lock

Lock也要求是同一个锁对象,因此在创建的时候,也用static修饰,让他属于这个类

package com.dream.test03;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread{
	
	private static int ticket =1000; 
	//因为后面要创建多个MyThread,为了保证同一个锁对象,因此用static修饰
	private static Lock lock = new ReentrantLock();
	public MyThread(String name) {
		super(name);
	}
	
	
	@Override
	public void run() {
		while(ticket>0){
			lock.lock();
			if(ticket>0){
				System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
				ticket--;
				if(ticket == 0){
					System.out.println(Thread.currentThread().getName() + "票已经销售完毕");
				}
			}else {
				System.out.println(Thread.currentThread().getName()+"票已售完");
				break;
//					Thread.currentThread().interrupt();
			}
			lock.unlock();
		}
	}
}

package com.dream.test03;

public class Test01 {

	
	public static void main(String[] args) {
		
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

总结

以上问题的精华是什么?答案是同一个锁对象,那锁对象的精华是什么,就是一个对象而已,千万不要理解为锁对象是要可能会出现同步问题的那段代码。
举个例子,一群猴子站在一堆香蕉旁边,要求一个一一个的去拿香蕉,不能哄抢,用多线程的思想就是,规定一个锁对象,只有拿到这个锁对象的猴子才能去拿香蕉,拿完香蕉,释放这个锁对象,然后其他猴子再去拿到锁对象,才能去拿香蕉,那么这个锁对象是什么呢,我可以规定是猴子家园里面的某一个独一无二棍子,也可以规定是某一个独一无二的石头,反正是独一无二的,且是属于这个猴子家园的,每个猴子都可以得到,但是同一时刻,只能有一个猴子可以拿着的东西就行了,不能怎么样呢?不能是这个猴子拿着棍子去,那个猴子拿着石头去,这就不是同一个锁对象了嘛。
总之,你要让同步代码块,同步方法,或者是lock,底层的锁对象是一个多个线程共享的独一无二的对象即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第一章. 概述 1 1.1概述 1 1.2意义 1 1.3任务 1 第二章. 系统的可行性研究与需求分析 2 2.1可行性研究 2 2.1.1经济可行性 2 2.1.2技术可行性 2 2.1.3操作可行性 2 2.2需求分析 2 2.2.1功能需求 2 2.2.2数据需求 3 2.2.3性能需求 3 2.2.4数据库逻辑结构 6 第三章. 系统的总体设计 7 3.1系统软件结构设计 7 3.1.1软件结构 7 3.2系统流程图 9 第四章. 系统的详细设计 10 4.1.1程序流程图 11 第五章. 系统的实现与调试 18 5.1应用系统的开发及测试 18 5.1.1系统首页 18 5.1.2用户登录及访问权限 19 5.1.3车次信息查询 21 5.1.4售票 21 5.15退票 22 结束语 23 致谢.....................................................................24 参考文献 25 附录A...............................................................................26附录B...............................................................................30 附录C............................................................................. 32 附录 登陆窗 #region Windows 窗体设计器生成的代码 private void InitializeComponent() { this.lblID = new System.Windows.Forms.Label(); this.lblPassWord = new System.Windows.Forms.Label(); this.cbSelect = new System.Windows.Forms.ComboBox(); this.lblSelect = new System.Windows.Forms.Label(); this.txtID = new System.Windows.Forms.TextBox(); this.txtPassWord = new System.Windows.Forms.TextBox(); this.btnCancel = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); this.skinEngine1 = new Sunisoft.IrisSkin.SkinEngine(((System.ComponentModel.Component)(this))); this.btnEnter = new System.Windows.Forms.Button(); this.SuspendLayout(); // LoginForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoValidate = System.Windows.Forms.AutoValidate.EnablePreventFocusChange; this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; this.ClientSize = new System.Drawing.Size(322, 312); this.Controls.Add(this.label1); this.Controls.Add(this.btnCancel); this.Controls.Add(this.btnEnter); this.Controls.Add(this.txtPassWord); this.Controls.Add(this.txtID); this.Controls.Add(this.lblSelect); this.Controls.Add(this.cbSelect); this.Controls.Add(this.lblPassWord); this.Controls.Add(this.lblID); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; this.MaximumSize = new System.Drawing.Size(332, 348); this.MinimumSize = new System.Drawing.Size(332, 348); this.Name = "LoginForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "登录界面"; this.Load += new System.EventHandler(this.Login_Load); this.ResumeLayout(false); this.PerformLayout(); } } } 附录B 主界面 namespace TicketMana { partial class SellerForm { /// /// 必需的设计器变量。 /// private System.ComponentModel.IContainer components = null; namespace TicketMana { partial class SellTicketForm { /// /// 必需的设计器变量。 /// private System.ComponentModel.IContainer components = null; /// /// 清理所有正在使用的资源。 /// /// 如果应释放托管资源,为 true;否则为 false。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值