线程同步

本文介绍了线程安全的概念,通过火车站售票系统举例说明了线程不安全的问题。接着详细讲解了Java中的线程同步机制,包括同步方法和同步块的使用,以确保多线程环境下资源的正确访问,避免数据冲突和不一致。示例代码展示了如何应用synchronized关键字实现线程安全的代码执行。

  在单线程中,每次只做一件事情,但是如果使用多线程,就会发生两个线程抢占资源的问题,比如两个人过独木桥,两个人同时说话,所以要在线程编程中避免这些资源访问的冲突。Java 提供了线程同步的机制来防止资源访问的冲突。

一、线程安全

在实际开发中,使用多线程的情况很多,如网上购票系统,银行排号系统。以火车站售票系统为例,在代码中判断票数是否大于0,如果大于 0 则执行售票功能,但是当多个线程同时访问这个代码时(假如就剩一张票),第一个线程将票售出,此时票数已经没有了,但是后面的线程也在执行此操作,所以票数会出现负数的情况,这样的线程是不安全的,所以在编写程序的时候,应该考虑到线程安全问题。
实际上线程安全问题来源于多个线程操作同时存取单一对象的数据。
下面用代码来模拟火车站的网上售票系统的功能,代码如下:

public class BuyTicket implements Runnable {
    boolean flag = true;
    //定义了10张票
    int tickNum = 10;
    @Override
    public void run() {
        //调用买票的方法
        while (flag) {
            ticket();
        }

    }
    public void ticket() {
//如果票数小于0就结束抢票
        if (tickNum <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "抢到了第" + tickNum-- + "张票");
    }
    public static void main(String[] args) {
        BuyTicket threadTest = new BuyTicket();
        //创建三个线程去抢票
        new Thread(threadTest,"小明").start();
        new Thread(threadTest,"黄牛").start();
        new Thread(threadTest,"老师").start();
        new Thread(threadTest,"小红").start();
    }
}

运行结果如下图:

在这里插入图片描述
通过上面的结果我们可以看出,最后打印的负的票数,这样就出现了问题,这样的线程是不安全的

二、线程同步机制

上面的那种不安全的机制我们如何解决呢,产生这种情况的原因是多个线程访问同一个资源,所以这个时候我们就需要给这个资源加上一把锁,每个线程进入之后,该锁就锁上,等这个线程出来之后,再把锁打开让别的线程进行访问。好比是一个人上洗手间时,他把门锁上,出来时再讲锁打开,然后其他人才能进入。
在 Java 中提供了同步机制,可以有效的防止资源冲突。同步机制使用 synchronized 关键字。

1、同步方法

同步方法就是在方法前面用关键词 synchronized ,其语法如下

synchronized void ticket(){}

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。修改上面的代码实例如下:

public class BuyTicket implements Runnable {
    boolean flag = true;
    //定义了10张票
    int tickNum = 10;
    @Override
    public void run() {
        //调用买票的方法
        while (flag) {
            ticket();
        }

    }
    //为这个方法加锁
    public synchronized void ticket() {
//如果票数小于0就结束抢票
        if (tickNum <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "抢到了第" + tickNum-- + "张票");
    }
    public static void main(String[] args) {
        BuyTicket threadTest = new BuyTicket();
        //创建三个线程去抢票
        new Thread(threadTest,"小明").start();
        new Thread(threadTest,"黄牛").start();
        new Thread(threadTest,"老师").start();
    }
}

运行结果为
在这里插入图片描述
由上面的代码可以看出,无论怎样运行,结果都不会出现 0 和负数,说明这个线程编码是安全的。

2、同步块

synchronized (Object){}
上面的代码称为同步块,其中 Object 是同步监视器,Object 可以是任何对象,但是一般为共享资源,这样也相当于为这个资源加上了一把锁,实例代码如下:

public class BuyTicket implements Runnable {
    //定义了10张票
    int tickNum = 10;
    @Override
    public void run() {
        while (true) {
            //把共享资源放入到同步块中
            synchronized (""){  if (tickNum <= 0) {
                break;
            }
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + tickNum-- + "张票");}
        }
    }

    public static void main(String[] args) {
        BuyTicket threadTest = new BuyTicket();
        //创建三个线程去抢票
        new Thread(threadTest,"小明").start();
        new Thread(threadTest,"黄牛").start();
        new Thread(threadTest,"老师").start();

    }
}

上面的运行结果和同步方法的一样,无论如何运行都不会有负数和 0 出现,说明线程也是安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值