Java卖票线程安全问题

529 阅读3分钟

例子:创建三个窗口卖票,总票数为100张。 1、分析问题

  • (1).问题:卖票过程中,出现了重票,错票--->线程的安全问题
  • (2).问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也可操作车票
  • (3).如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完成ticket时,其他线程才可以开始操作ticket,这种情况即使线程a出现了阻塞问题,也不能改变
  • (4).在Java中,我们通过同步机制,来解决线程的安全问题

2、解决方法 1)、方法一:同步代码块;synchronized(同步监视器);需要被同步的代码

说明:1.操作共享数据的代码,即为需要被同步的代码。--->不能包含代码多了,也不能包含代码少了. 2.共享数据:多个线程共同操作的变量,比如:ticket就是共享数据。 3.同步监视器,俗称:锁,任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器

2)、方法二:同步方法


Runnable实现

public class MyRunnable {
	
	public static void main(String[] args) {
		Window2 window2 = new Window2();

		Thread t1 = new Thread(window2);
		Thread t2 = new Thread(window2);
		Thread t3 = new Thread(window2);

		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");

		t1.start();
		t2.start();
		t3.start();
	}
}

class Window2 implements Runnable {

	int ticket = 100;

	Object object = new Object();
	Rose rose = new Rose();

	public void run() {

		while (true) {
			// this
			// 任何唯一的类对象
			// Window2.class
			synchronized (Window2.class) {
				try {
					// 休眠重票错票
					Thread.currentThread().sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}

				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName() + " :卖票,票号为:" + ticket);
					ticket--;
				} else {
					break;
				}
			}
		}
	}
}

Thread类

说明:在继承Thread类的创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同上监视器。

public class MyThread {

	public static void main(String[] args) {
		Window3 t1 = new Window3();
		Window3 t2 = new Window3();
		Window3 t3 = new Window3();

		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");

		t1.start();
		t2.start();
		t3.start();
	}

}

class Window3 extends Thread {

	static int ticket = 100;
	static Object object = new Object();

	public void run() {
		while (true) {
			// 错误的方式:this代表着t1,t2,t3三个对象
			// 任何唯一的类对象
			// Class clazz = Window3.class Window3只会加载一次
			synchronized (Window3.class) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				if (ticket > 0) {
					System.out.println(getName() + ":卖票,票号为:" + ticket);
					ticket--;
				} else {
					break;
				}
			}
		}
	}

}
结构:class Rose {
	// 1.属性
	// 2.方法
	// 3.构造方法
	// 4.代码块
	// 5.内部类
}

结果

两种方法实现结果都一样: 两种方法实现的结果都一样

同步的好处和弊端

  • 好处:同步解决了多线程的安全问题
  • 弊端:多线程都需要判断锁,比较消耗资源

总结

主要是用两种表现形式: (1)同步代码块:     可以指定需要获取哪个对象的同步锁,使用synchronized的代码块同样需要锁,但他的锁可以是任意对象,考虑到安全问题,一般还是使用同一个对象,相对来说效率较高。

注意: 1、虽然同步代码快的锁可以使任何对象,但是在进行多线程通信使用同步代码快时,必须保证同步代码快的锁的对象和,否则会报错。 2、同步函数的锁是this,也要保证同步函数的锁的对象和调用wait、notify和notifyAll的对象是同一个对象,也就是都是this锁代表的对象。 格式: synchronized(对象) { 需同步的代码; }

(2)同步函数     同步方法是指进入该方法时需要获取this对象的同步锁,在方法上使用synchronized关键字,使用this对象作为锁,也就是使用了当前对象,因为锁住了方法,所以相对于代码块来说效率相对较低。

注:静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件 格式: 修饰词 synchronized 返回值类型 函数名(参数列表) { 需同步的代码; }