线程安全:
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的,反之则是线程不安全的。
卖票案例:
public class Demo08 { public static void main(String[] args) { //创建线程任务对象 Ticket ticket = new Ticket(); //创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); //卖票 t1.start(); t2.start(); t3.start(); } static class Ticket implements Runnable { //Object lock = new Object(); ReentrantLock lock = new ReentrantLock(); private int ticket = 10; public void run() { String name = Thread.currentThread().getName(); while (true) { sell(name); if (ticket <= 0) { break; } } } private void sell(String name) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
if (ticket > 0) { System.out.println(name + "卖票:" + ticket); ticket--; } } } }
运行之后,结果为:出现了卖重复的票
分析:创建一个实现类对象,传到三个线程里面,能保证三个线程卖100张票,访问的是共享资源,此时只有一个对象,即单例对象。如果创建三个实现类对象,那么,票就300张了,自己玩自己的,就没有关系了。
由于这个对象是单例的对象,这种情况下,对象只被创建了一次,从而类中的成员只会初始化一次,这时候你要操作类成员的话,类成员在方法中能够进行改变的时候,单例对象有线程安全问题。
多例对象没有线程安全问题,但是多例对象由于对象被创建了多次,执行效率没有单例对象高,而单例对象虽然有线程问题,但是之前讲过的代码并没有在方法中修改类成员的值,在service和dao中是没有的,所有单例对象如果没有可以改变的类成员,则是没有线程问题的。
多个线程访问或多个对象访问且对象的实例只有一个时,类成员会由于第一个对象的修改,后面看到的都是改过的数据.出现了线程安全问题.所以尽量不要定义类成员,而是定义到方法中成为局部变量,一旦定义到方法中去之后,这个问题就随着消失了,即单例对象且类中没有可以改变的类成员,则没有线程安全问题.
单例对象:从始至终只有一个对象实例。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。
该案例中,ticket为全局变量,开启了三个线程对全局变量进行写操作,故产生了安全问题。
如果要解决线程安全问题,做同步进行了,可以使用Lock锁。
public class Demo08 { public static void main(String[] args) { //创建线程任务对象 Ticket ticket = new Ticket(); //创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); //卖票 t1.start(); t2.start(); t3.start(); } static class Ticket implements Runnable { //Object lock = new Object(); ReentrantLock lock = new ReentrantLock(); private int ticket = 10; public void run() { String name = Thread.currentThread().getName(); while (true) { lock.lock();
try{
sell(name);
}finally{
lock.unlock();
}if (ticket <= 0) { break; } } } private void sell(String name) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
if (ticket > 0) { System.out.println(name + "卖票:" + ticket);
ticket--; } } } }
结果如下:
也可以使用同步方法:
public class Demo08 { public static void main(String[] args) { //创建线程任务对象 Ticket ticket = new Ticket(); //创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); //卖票 t1.start(); t2.start(); t3.start(); } static class Ticket implements Runnable { //Object lock = new Object(); ReentrantLock lock = new ReentrantLock(); private int ticket = 10; public void run() { String name = Thread.currentThread().getName(); while (true) { sell(name); if (ticket <= 0) { break; } } } private synchronized void sell(String name) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }if (ticket > 0) { System.out.println(name + "卖票:" + ticket); ticket--; } } } }