Java之线程并发同步——synchronized方法总结

huatechinfo
发布于 2021-3-23 14:17
浏览
0收藏

我们知道,多线程存在着数据安全问题。在并发线程中,一个对象同时可以被多个线程访问,修改等。比如购买火车票的例子,若只剩下最后一张火车票,但此时同时有多个线程正在访问该数据,那么如果不加约束,那么这些线程都将成功购得同一张火车票,最后导致系统中火车票剩余数量变为负数!
以下是一个不加约束的示范:

//线程并发即多个线程操作同一个对象
//买火车票的例子
public class TestThread3 implements Runnable{
    private int ticket=1;//这是第一张票
    public void run() {//重写run方法
        while (true){
            if(ticket>10)  //当取走10张票后结束此线程
            {break;}
             try {
                Thread.sleep(100);//此处增加短暂的阻塞,使问题更容易被发现。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticket++ +"票");
        }

    }

    public static void main(String[] args) {
        TestThread3 test=new TestThread3();
        //以下三个线程准备就绪,由CPU随机调度
        new Thread(test,"小明").start();
        new Thread(test,"小张").start();
        new Thread(test,"小李").start();
    }
}

以下是运行的结果(由于在这CPU调度调度是随机的,所以每次结果都有不同,在此我特意选了一个存在问题的结果展示)

Java之线程并发同步——synchronized方法总结-鸿蒙开发者社区

从上面的结果我们发现了问题,第2张票同时被两个人取到。这里的原因就在于,每个线程都是在自身的工作内存中进行交互,这里小张和小明都看到了第2张票,于是都将第2张信息拷入自身内存进行处理,从而都获得了第2张票。所以要解决这个数据安全问题,我们需要对线程进行排队(队列+锁)处理,即实现线程的同步!

接下来,介绍同步。
首先是方法的同步(同步方法,即synchronized方法)。
简单来说,synchronized方法就是控制对“对象”的访问,每一个对象都对应着一把锁,而每一个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞。而synchronized方法一旦执行,就独占该锁,使得其他需要获取该锁线程阻塞,直到该方法返回后才会释放其所占的锁。而后其他线程可以重新抢夺该锁,继续执行。

为了更好示范同步,这里我将之前的代码更改:

public class BuyTicket{
    public static void main(String[] args) {
        Buy thread =new Buy();
        new Thread(thread,"小张").start();
        new Thread(thread,"小李").start();
        new Thread(thread,"小明").start();

    }
}
class Buy implements Runnable{
    private int ticket=1;
    boolean flag=true;
    @Override
    public void run() {
        while(flag) {
            try {
                Thread.sleep(100);
                buy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private void buy() throws InterruptedException {
        if (ticket>10) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"拿到第"+ticket++ +"票");
    }

}

面的代码仅仅是对之前进行了修改,但本质不变,其仍存在数据安全问题。
但是我们在这里可以使用synchronized方法解决这个问题,synchronized方法使用非常简单,以下是使用synchronized方法后的代码:

public class BuyTicket{
    public static void main(String[] args) {
        Buy thread =new Buy();
        new Thread(thread,"小张").start();
        new Thread(thread,"小李").start();
        new Thread(thread,"小明").start();

    }
}
class Buy implements Runnable{
    private int ticket=1;
    boolean flag=true;
    @Override
    public void run() {
        while(flag) {
            try {
                Thread.sleep(100);
                buy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    //同步方法,锁的是this对象
    private synchronized void buy() throws InterruptedException {
        if (ticket>10) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"拿到第"+ticket++ +"票");
    }

}

执行结果如下图,数据安全问题被顺利解决:

最后synchronized方法总结一下:

synchronized 方法其实就是线程在使用该方法操作对象时,会获取该对象的锁,将其锁住,而其他线程同样使用synchronized 方法的时候因为无法获取该锁(一个对象只有一个锁),因此只能陷入阻塞状态,直到第一个获取该锁的synchronized 方法释放该锁!

不过synchronized方法只能锁住一个固定的对象(this对象),因此当我们需要锁住其他对象的时候,我们需要利用到同步块。
————————————————
版权声明:本文为「小白说java」的原创文章

分类
已于2021-3-23 14:28:52修改
1
收藏
回复
举报
回复
    相关推荐