多线程之数据同步(初识synchronized关键字)

1 数据不一致问题的引入

之前的一个窗口叫号程序:链接地址

public class TicketRunnable implements Runnable{
    /**
     * 定义50张门票
     */
    private final  int TICKET_TOTAL = 500;

    private int index = 1;

    @Override
    public void run() {
        while (index <= TICKET_TOTAL) {
            System.out.println(Thread.currentThread().getName() + " 售出 " + "第 " + index++ + " 张票");
        }
    }

    public static void main(String[] args) throws InterruptedException {

        TicketRunnable ticketRunnable = new TicketRunnable();
        Thread ticketThread1 = new Thread(ticketRunnable,"售票窗口1");
        Thread ticketThread2 = new Thread(ticketRunnable,"售票窗口2");
        Thread ticketThread3 = new Thread(ticketRunnable,"售票窗口3");
        ticketThread1.start();
        ticketThread2.start();
        ticketThread3.start();

    }
}

这里没有考虑线程安全问题,调整到500、1000等稍微大一些的数字就会出现线程安全的问题

多次运行上述程序,每次都会有不一样的发现,但是总结起来主要有三个问题,具体如下:

  • 第一,某个号码被略过没有出现。
  • 第二,某个号码被多次显示。
  • 第三,号码超过了最大值500。

1.1 数据不一致问题原因分析

  1. 号码被略过
    线程的执行是由CPU时间片轮询调度的,假设此时线程1和2都执行到了index=65的位置,其中线程2将index修改为66之后未输出之前,CPU调度器将执行权利交给了线程1,线程1直接将其累加到了67,那么66就被忽略了

  2. 号码重复出现
    线程1执行index+1,然后CPU执行权落入线程2手里,由于线程1并没有给index赋予计算后的结果(假设+1后是394),因此线程2执行index+1的结果仍然是394,所以会出现重复号码的情况。

  3. 号码超过了最大值
    下面来分析一下号码超过最大值的情况,当index=499的时候,线程1和线程2都看到条件满足,线程2短暂停顿,线程1将index增加到了500,线程2恢复运行后又将500增加到了501,此时就出现了超过最大值的情况

2 初识synchronized关键字

出现的几个问题,究其原因就是因为多个线程对index变量(共享变量/资源)同时操作引起的,在JDK1.5版本以前,要解决这个问题需要使用synchronized关键字,synchronized提供了一种排他机制,也就是在同一时间只能有一个线程执行某些操作。

下面是一段来自于JDK官网对synchronized关键字比较权威的解释:
在这里插入图片描述
synchronized关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的所有读或者写都将通过同步的方式来进行

  • synchronized关键字提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据不一致问题的出现

  • synchronized关键字包括monitor enter和monitor exit两个JVM指令,它能够保证在任何时候任何线程执行到monitor enter成功之前都必须从主内存中获取数据,而不是从缓存中,在monitor exit运行成功之后,共享变量被更新后的值必须刷入主内存

  • synchronized的指令严格遵守java happens-before规则,一个monitor exit指令之前必定要有一个monitor enter

2.1 synchronized关键字的语法

synchronized可以用于对代码块或方法进行修饰,而不能够用于对class以及变量进行修饰。

  1. 同步方法
    就是使用synchronized关键字修饰方法即可:
public synchronized void sync(){}
  1. 同步静态方法
public synchronized static void staticSync(){}
  1. 同步代码块
// 锁对象
private final Object MUTEX = new Object();
public void sync(){
	synchronized (MUTEX){
	}
}

同步代码块还定义了一个锁对象,这个锁对象必须对所有线程是同一个对象,这样synchronized 关键字才有用,也就是前面说的一个对象对多个线程是可见的。

而同步方法没有定义锁对象,是因为同步方法有默认的锁对象:

  • 同步方法:this
  • 同步静态方法:就是这个方法所在类的class对象

2.2 使用synchronized 解决问题

public class TicketWindowRunnable implements Runnable
{

    private int index = 1;

    private final static int MAX = 500;

    private final static Object MUTEX = new Object();

    @Override
    public void run()
    {
        synchronized (MUTEX)
        {
            while (index <= MAX)
            {
                System.out.println(Thread.currentThread() + " 的号码是:" + (index++));
            }
        }
    }

    public static void main(String[] args)
    {
        final TicketWindowRunnable task = new TicketWindowRunnable();

        Thread windowThread1 = new Thread(task, "一号窗口");
        Thread windowThread2 = new Thread(task, "二号窗口");
        Thread windowThread3 = new Thread(task, "三号窗口");
        Thread windowThread4 = new Thread(task, "四号窗口");
        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
        windowThread4.start();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值