在讲述继承Thread和实现Runnable接口时,我们说到两者的区别中有一个区别是实现Runnable接口,可以多个线程操作同一个实例变量,如下代码,抢票的例子,3个线程共同去操作同一个变量ticket:

public class MainClass {
  public static void main(String [] args) throws InterruptedException {
    Runnable runnable = new QiangPiaoThread();
    new Thread(runnable,"小明").start();
    new Thread(runnable,"小红").start();
    new Thread(runnable,"小牛").start();

  }
}

class QiangPiaoThread implements  Runnable{
  Integer ticket = 10;
  @Override
  public void run() {
    String name = Thread.currentThread().getName();
    while(ticket>0) {
      try {
        synchronized (ticket) {
        System.out.println(name + "抢票了第" +ticket + "张票");
        ticket = ticket -1;
       }

        Thread.sleep(1000L);
        }catch(Exception ex){}
      }
    }
}

// 运行结果
小明抢票了第10张票
小红抢票了第9张票
小牛抢票了第8张票
小牛抢票了第7张票
小明抢票了第6张票
小红抢票了第5张票
小红抢票了第4张票
小明抢票了第3张票
小牛抢票了第2张票
小红抢票了第1张票
小明抢票了第0张票
小牛抢票了第-1张票

这面这段代码通过Runnable来让三个线程操作同一个ticket变量,这事实现接口的一个特点。但是我这里要讲的是,当synchronized和ticket为了实现原子性共同使用时,要注意的一些问题,

从代码的打印结果可以看出,虽然我使用了synchronized并且也用ticket作为了锁,但是结果还是错的。错误的原因是while条件中的ticket>0导致的。因为while(ticket>0)这里还没有通过synchronized实现原子性的操作,所以导致当ticket=1时,小红,小明,小牛都进入了while中,这样就出现三个线程都会执行ticket-1的操作。导致出现了0和-1的错误数据。

 

所以在实现Runnable来操作同一个变量如ticket时,已经要将对ticket的操作(比较,加减等操作),放到synchronized,下面对其进行修改,如下:

public class MainClass {
  public static void main(String [] args) throws InterruptedException {
    Runnable runnable = new QiangPiaoThread();
    new Thread(runnable,"小明").start();
    new Thread(runnable,"小红").start();
    new Thread(runnable,"小牛").start();

  }
}

class QiangPiaoThread implements  Runnable{
  Integer ticket = 10;
  @Override
  public void run() {
    String name = Thread.currentThread().getName();
    while(true) {
      try {
       synchronized (ticket) {
         if(ticket == 0) { //将对ticket的操作放到这里
           break;
         }
         System.out.println(name + "抢票了第" +ticket + "张票");
         ticket = ticket -1;
       }

        Thread.sleep(1000L);
        }catch(Exception ex){}
      }
    }
}

// 运行结果
小明抢票了第10张票
小红抢票了第9张票
小牛抢票了第8张票
小明抢票了第7张票
小红抢票了第6张票
小牛抢票了第5张票
小红抢票了第4张票
小明抢票了第3张票
小牛抢票了第2张票
小红抢票了第1张票

Process finished with exit code 0

这样通过将ticket放到synchronized中,作为原子操作的一部分,这样就不会出现错结果了。所以当使用Runnable来让多个线程使用同一个共享变量时,一定要注意对这个共享变量如代码中ticket的操作的的位置,一定要在synchronized中。