写在前面
本文全文以售票系统为例,简诉了java多线程间共享数据的两种方式、线程同步。文章可能还有很多不足,请大家谅解,欢迎大佬提意见。
本文使用到的东西
- java
- 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实现的是修饰的内容同步。有不清楚的地方欢迎评论留言,看到的我都会回复的。本文到此结束,有什么不足的地方请大家不吝指正。