首先先提出几个问题:
1.多线程编程何时会出现线程不安全的问题?
2.如何解决线程不安全的问题?
线程不安全的本质是多线程共享数据,那么什么情况下多线程会共享数据?无外乎这么几种情况:
(1)多线程访问单实例中的实例变量
(2)多线程访问静态变量
下面将举例说明,这个例子模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,详见贴所示:http://bbs.misonsoft.com/thread-1225-1-1.html(创建线程两种方式的比较)。
第一种情况:多线程访问单实例中的实例变量
打印结果:
- Thread-2 is saling ticket 100
- Thread-1 is saling ticket 99
- Thread-3 is saling ticket 98
- 。。。
- Thread-2 is saling ticket 2
- Thread-0 is saling ticket 1
- Thread-3 is saling ticket 0
- Thread-1 is saling ticket -1
- Thread-2 is saling ticket -2
这个代码展示了多线程访问单实例中的实例变量而导致的数据不同步问题。多线程就是4个售票窗口,即new Thread(t),单个实例便是ThreadTest类,共享的数据就是该实例的tickets变量,即为100张车票。而出现的数据不同步现象有两种情况:一个是卖出超过100张车票(如打印结果所示售出102张),还有一种情况是同个座位的车票卖出了多次(本例不会出现)。
接下来说明一下造成数据不安全的第二种情况:多线程访问静态变量
上述代码就是多线程访问静态变量导致的数据不同步问题,因为4个线程共享ticket这个静态数据,所以也会导致卖出超过100张车票。
我们再来举个多线程共享静态数据的例子,懒汉式的单例模式并非线程安全的,如下:
运行结果:
- 对象创建了1次
- Singleton@10b30a7
- 对象创建了2次
- Singleton@1a758cb
- 对象创建了3次
- Singleton@1b67f74
- 对象创建了4次
- Singleton@69b332
- 对象创建了5次
- Singleton@173a10f
很明显,该对象创建了5次,每一个线程都创建了一个对象。
总结一下,当多线程共享数据时,该数据极有可能出现不同步的状况。接下来就是第二个问题了:如何防止线程不安全的状况发生。加锁可以解决这个问题,每个对象和每个类都提供了一把锁,当多线程中某一个线程持有这把锁的时候,所有其他线程都必须等待该线程执行完毕释放锁之后才能继续执行。这把锁是哪个对象提供的或者是哪个类提供的并不重要,关键是该锁必须是唯一的,因为只有唯一的对象锁才是排他的。
因此第一个场景的代码可以做如下修改:
也可以这样加锁:
甚至可以这样加锁:
因此只要确保该锁是唯一的,即可达到同步的效果。
再来看下第二个场景的数据不同步问题如何解决:
这样加锁能否解决问题:
很显然,这样加锁并不能达到同步的目的,因为当前对象并不是唯一的,这里的当前对象就是每次创建的线程对象,这里有4个线程对象,每个对象指代的当前对象都是不一样的,因此加的锁都是不一样的锁,无法达到排他的目的,因此仍然会造成卖出超过100张车票。这个代码可以定义一个static对象做为锁,或者以ThreadTest.class作为锁。
最后单例模式如何加锁留给大家思考。
总结:加锁是解决线程不安全的方法之一,加锁的时候必须确保该锁是唯一的。