写在前面

本文全文以售票系统为例,简诉了java多线程间共享数据的两种方式、线程同步。文章可能还有很多不足,请大家谅解,欢迎大佬提意见。

本文使用到的东西

  1. java
  2. eclipse 2019-11

1.多线程共享数据

1.1 共享Runnable

  当多个线程执行的内容相同,可以采用共享Runnable接口实现类对象的方式共享数据,将共享的数据作为Runnable对象的成员变量。
  这里我们以包含多个售票处的售票系统为例,每一个售票处一个线程,多个线程共享余票数变量。

class TicketRunnable implements Runnable{
	private int num;
	
	public TicketRunnable(int num) {
		this.num=num;
	}
	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5张票
			if(num>0) {
				num--;
				System.out.println(Thread.currentThread().getName()+":售票1张,余票"+num);
			}else {
				System.out.println(Thread.currentThread().getName()+":暂时无余票");
			}
		}
	}
}
public class 多线程共享数据 {
	public static void main(String[] args) {
		Runnable runnable = new TicketRunnable(8);	//初始化8张余票
		new Thread(runnable,"武汉售票点").start();
		new Thread(runnable,"北京售票点").start();
	}
}

1.2 封装数据为对象

  当多个线程执行的是相同的操作时可以采用共享Runnable对象的方法来共享变量,执行不同操作时这个方法就不适用,可以将共享的数据封装成对象,多个线程共享该对象。
  以售票系统为例,一个线程执行退票,一个线程执行售票。

class Ticket{
	private int num;
	public Ticket(int num) {
		this.num=num;
	}
	public void sell() {
		if(num>0) {
			num--;
			System.out.println(Thread.currentThread().getName()+":售票1张,余票"+num);
		}else {
			System.out.println(Thread.currentThread().getName()+":暂时无余票");
		}
	}
	public void returned() {
		num++;
		System.out.println(Thread.currentThread().getName()+":退票1张,余票"+num);
	}
}
public class 多线程共享数据 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(8);	//初始化为8张票
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<8;i++) {
					ticket.sell();//售票
				}
			}
		},"售票处").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<8;i++) {
					ticket.returned();//退票
				}
			}
		},"退票处").start();
	}
}

2.线程同步与互斥

2.1 上述代码存在的问题

以共享Runnable对象实现同步的方式为例,运行该程序,运行结果如下:

武汉售票点:售票1张,余票6
武汉售票点:售票1张,余票5
北京售票点:售票1张,余票6
武汉售票点:售票1张,余票4
武汉售票点:售票1张,余票2
北京售票点:售票1张,余票3
北京售票点:售票1张,余票0
北京售票点:暂时无余票
北京售票点:暂时无余票
武汉售票点:售票1张,余票1

  我们设置的初始票数为8,查看运行结果,余票数量并不是从7开始递减,而是从6开始,而且并不是递减。出现该问题是因为武汉售票点将票数减1还未输出的时候,北京售票点也将票数减1,这时候输出结果就是6了。不是按递减输出也同样是因为读取了数据还未输出,另一个线程执行了卖票输出。对TicketRunnable的run()方法稍加修改,修改为

	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5张票
			synchronized (this) {
				if(num>0) {
					num--;
					System.out.println(Thread.currentThread().getName()+":售票1张,余票"+num);
				}else {
					System.out.println(Thread.currentThread().getName()+":暂时无余票");
				}
			}
		}
	}

此时,又是按递减顺序输出程序内容,因为synchronized给代码块添加了同步锁,将修改值和取值的操作进行了同步,所以不会在出现乱序、输出余票不正确的情况。

2.2 同步与互斥

1. 什么是互斥?
在计算机中很多资源都是有限的,这种有限资源叫做临界资源。多个进程争抢同一个临界资源,抢到了可以运行,没抢到就无法运行。互斥就是争夺临界资源进程的间接制约关系。 例如多个打印线程争夺一台打印机资源,进程间就形成了互斥。

2. 什么是同步?
同步是协调多个相互关联线程合作完成任务,彼此之间存在一定约束,执行顺序往往是有序的。 同步是进程间的直接制约关系,例如供销系统,当仓库满了需要停止供货,仓库空了无法出货,此时供货进程和销货进程就形成了同步关系。

2.3 synchronized实现同步

synchronized可用于修饰方法、静态方法和代码块

//对象锁,修饰方法
synchronized void a() {

}
//类锁,修饰静态方法
synchronized static void b() {

}
void c() {
	//对象锁,修饰代码块
	synchronized (this) {
			
	}
	//类锁,修饰代码块
	synchronized (Ticket.class) {

	}	
}

2.4 ReentrantLock实现同步

1.使用

ReentrantLock lock = new ReentrantLock();
lock.lock();	//加锁
lock.unlock();	//解锁

2.实现上述的售票同步

class TicketRunnable implements Runnable{
	private int num;
	ReentrantLock lock = new ReentrantLock();
	
	public TicketRunnable(int num) {
		this.num=num;
	}
	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5张票
			lock.lock();
			if(num>0) {
				num--;
				System.out.println(Thread.currentThread().getName()+":售票1张,余票"+num);
			}else {
				System.out.println(Thread.currentThread().getName()+":暂时无余票");
			}
			lock.unlock();
		}
	}
}
public class 多线程共享数据 {
	public static void main(String[] args) {
		Runnable runnable = new TicketRunnable(8);	//初始化8张余票
		new Thread(runnable,"武汉售票点").start();
		new Thread(runnable,"北京售票点").start();
	}
}

3.总结

synchronized实现的是同步还是互斥这一点有些难理解,网上也有说synchronized是互斥锁的,synchronized实现的是修饰的内容同步。有不清楚的地方欢迎评论留言,看到的我都会回复的。本文到此结束,有什么不足的地方请大家不吝指正。