Java多线程基础之线程安全

卖票的例子

上一节讲到卖票的例子,现在来实现一下。

    Runnable runnable = new Runnable() {

        int ticket = 100;//100张票
        public void run() {
            while (true) {
                if (ticket > 0) {
                    //卖出一张票并且打印
                    System.out.println(Thread.currentThread().getName() + "__" + ticket--);
                }
            }
        }
    };

    Thread thread1 = new Thread(runnable);
    Thread thread2 = new Thread(runnable);
    Thread thread3 = new Thread(runnable);

    thread1.start();
    thread2.start();
    thread3.start();

嗯,看上去没问题。其实你仔细检查会发现有几个票数是一样的,这不符合现实,100张票卖出了101张、102张、103张……这就是多线程并发存在的问题。有些同学会有疑问,为什么最后的票数不是1,而是其他数。我在第一节讲过,CPU调度工作是乱序的,所以不是按顺序也是正常的。

不安全的原因

多线程存在漏洞,我们是不是不用了呢。也许你是对的,但是开发程序避免不了多线程,到最后发现线程是多么好的一个东西。多线程并发为什么会存在这问题呢。主要执行代码块是run方法,我们来分析run方法。

if (ticket > 0) {//第一站
    System.out.println(Thread.currentThread().getName() + "__" + ticket--);//第二站
}

在第一节中提到,CPU调度工作是不断切换的,执行到某地方就会切换线程继续接下来的工作。我们放慢工作,比如上面线程1执行判断语句(假设票数还剩下50张),这时CPU切换线程2执行判断语句(票数还剩下50张),然后执行打印(票数还剩下49张),可是又切换回线程1(这时线程1的票数还是50张),然后执行打印(49张票数)。简单来说就是多条语句操作线程中的共享数据,一线程没执行完就切换其他线程,导致共享数据错误。

同步锁

不用共享数据是一个解决方法,但是多个用户访问一个数据库,就会存在线程安全问题,这就是多线程访问共享数据的一个例子。Java提供一个synchronized关键字,意为同步,这是为了解决线程安全问题。使用方法很简单。在操作共享数据的多条语句用synchronized括起来。

    synchronized(this){
        if (ticket > 0) {//第一站
            System.out.println(Thread.currentThread().getName() + "__" + ticket--);//第二站
        }
    }

这又有一个疑问,括号里面是什么呢。这就是同步需要的锁。简单来说,哪个线程先拿到这锁就会锁上整个方法,等执行完方法再把锁给下一个线程。那这锁里面一定要this吗,并不是的,这个锁任意对象都是可以的。但不要在括号中new对象,否则它会一直停留在那,因为每次new的对象地址都是不一样的。

    synchronized(new Object()){//杜绝
        ...
    }

还有另一种姿势。

    //同步方法
    private void synchronized saleTicket(){
        ...
    }

    //等价与
    synchronized(this){
        saleTicket();
    }

    private void saleTicket(){
        ...
    }

同步方法的锁就是this,两种方法执行是一致的。

终止线程

当我们run方法执行的是无限循环的代码,这就要注意终止该线程。如果你用Eclipse执行代码,你会发现电脑会开始响起来,这是因为CPU还在不断执行你的循环语句。Java API提供start方法,不是也有对应的stop方法吗?是有的,但是已经标记过时方法了,因为存在很多问题。那有什么方法终止。

    boolean flag = true;
    whlie(flag){
    }

最简单的方式就是用flag判断,flag为flase就视为退出,该线程释放了。

还有一种情况是线程阻塞,在while里面是动不了,无法判断flag,一直无法终止线程。这时就要用到interupt方法,也就是下一节的内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值