您的位置:首页 > 其它

多线程的安全问题

2010-08-05 15:05 176 查看
首先先提出几个问题:
1.多线程编程何时会出现线程不安全的问题?
2.如何解决线程不安全的问题?

线程不安全的本质是多线程共享数据,那么什么情况下多线程会共享数据?无外乎这么几种情况:
(1)多线程访问单实例中的实例变量
(2)多线程访问静态变量

下面将举例说明,这个例子模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,详见贴所示:http://bbs.misonsoft.com/thread-1225-1-1.html(创建线程两种方式的比较)。
第一种情况:多线程访问单实例中的实例变量

public class ThreadDemo {

        public static void main(String[] args) {

                ThreadTest t = new ThreadTest();

                new Thread(t).start();

                new Thread(t).start();

                new Thread(t).start();

                new Thread(t).start();

        }

}

class ThreadTest implements Runnable {

        private int tickets = 100;

        public void run() {

                while (true) {

                        if (tickets > 0) {

                                try {

                                        Thread.sleep(200);

                                } catch (InterruptedException e) {

                                        e.printStackTrace();

                                }

                                System.out.println(Thread.currentThread().getName()

                                                + " is saling ticket " + tickets--);

                        } else {

                                break;

                        }

                }

        }

}


打印结果:

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张),还有一种情况是同个座位的车票卖出了多次(本例不会出现)。

接下来说明一下造成数据不安全的第二种情况:多线程访问静态变量

public class ThreadDemo {

        public static void main(String[] args) {

                new ThreadTest().start();

                new ThreadTest().start();

                new ThreadTest().start();

                new ThreadTest().start();

        }

}

class ThreadTest extends Thread {

        private static int tickets = 100;// 共享该静态变量

        public void run() {

                while (true) {

                        if (tickets > 0) {

                                try {

                                        Thread.sleep(200);

                                } catch (InterruptedException e) {

                                        e.printStackTrace();

                                }

                                System.out.println(Thread.currentThread().getName()

                                                + " is saling ticket " + tickets--);

                        } else {

                                break;

                        }

                }

        }

}


上述代码就是多线程访问静态变量导致的数据不同步问题,因为4个线程共享ticket这个静态数据,所以也会导致卖出超过100张车票。

我们再来举个多线程共享静态数据的例子,懒汉式的单例模式并非线程安全的,如下:

public class Singleton {

        private static Singleton s;

        private static int cnt = 0;

        private Singleton() {

                System.out.println("对象创建了" + ++cnt + "次");

        }

        public static Singleton getSingleton() {

                        if (s == null) {

                                try {

                                        Thread.sleep(1000);

                                } catch (InterruptedException e) {

                                        e.printStackTrace();

                                }

                                s = new Singleton();

                        }

                        return s;

        }

}

public class ThreadClient extends Thread{

        

        public void run(){

                Singleton s1 = Singleton.getSingleton();

                System.out.println(s1);

        }

        

        public static void main(String[]args){

                new ThreadClient().start();

                new ThreadClient().start();

                new ThreadClient().start();

                new ThreadClient().start();

                new ThreadClient().start();

        }

}


运行结果:

对象创建了1次

Singleton@10b30a7

对象创建了2次

Singleton@1a758cb

对象创建了3次

Singleton@1b67f74

对象创建了4次

Singleton@69b332

对象创建了5次

Singleton@173a10f

很明显,该对象创建了5次,每一个线程都创建了一个对象。

总结一下,当多线程共享数据时,该数据极有可能出现不同步的状况。接下来就是第二个问题了:如何防止线程不安全的状况发生。加锁可以解决这个问题,每个对象和每个类都提供了一把锁,当多线程中某一个线程持有这把锁的时候,所有其他线程都必须等待该线程执行完毕释放锁之后才能继续执行。这把锁是哪个对象提供的或者是哪个类提供的并不重要,关键是该锁必须是唯一的,因为只有唯一的对象锁才是排他的。
因此第一个场景的代码可以做如下修改:

class ThreadTest implements Runnable {

        private int tickets = 100;

        public void run() {

                while (true) {

                        synchronized (this) {//当前对象作为锁,该锁是唯一的

                                //省略

                        }

                }

        }

}


也可以这样加锁:

class ThreadTest implements Runnable {

        private Object o = new Object();

        private int tickets = 100;

        public void run() {

                while (true) {

                        synchronized (o) {//o作为锁,该锁是唯一的

                                //省略

                        }

                }

        }

}


甚至可以这样加锁:

class ThreadTest implements Runnable {

        private int tickets = 100;

        public void run() {

                while (true) {

                        synchronized (ThreadTest.class) {//类提供的锁,该锁是唯一的

                                //省略

                        }

                }

        }

}


因此只要确保该锁是唯一的,即可达到同步的效果。

再来看下第二个场景的数据不同步问题如何解决:
这样加锁能否解决问题:

class ThreadTest extends Thread {

        private static int tickets = 100;// 共享该静态变量

        public void run() {

                while (true) {

                        synchronized (this) {//以当前对象作为锁,能否起到同步的目的?

                                if (tickets > 0) {

                                        try {

                                                Thread.sleep(200);

                                        } catch (InterruptedException e) {

                                                e.printStackTrace();

                                        }

                                        System.out.println(Thread.currentThread().getName()

                                                        + " is saling ticket " + tickets--);

                                } else {

                                        break;

                                }

                        }

                }

        }

}


很显然,这样加锁并不能达到同步的目的,因为当前对象并不是唯一的,这里的当前对象就是每次创建的线程对象,这里有4个线程对象,每个对象指代的当前对象都是不一样的,因此加的锁都是不一样的锁,无法达到排他的目的,因此仍然会造成卖出超过100张车票。这个代码可以定义一个static对象做为锁,或者以ThreadTest.class作为锁。

最后单例模式如何加锁留给大家思考。
总结:加锁是解决线程不安全的方法之一,加锁的时候必须确保该锁是唯一的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: