关于多线程不安全问题以及解决方法

关于多线程不安全问题以及解决方法

线程不安全

  线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据并不是你所想要的。下面举例说明:

public class Demo {
     
    /**
     * 线程不安全
     * @param args
     */
    public static void main(String[] args) {
     
        //创建一个任务
        Runnable run=new Ticket();
        //创建三个线程,相当于三个售票窗口,它们共同售卖库存中的10张票
        //调用start()方法启动线程,开始售票
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
     
        private int count=10;//一共10张票
        @Override
        public void run() {
     
            while (count>0){
     
                System.out.println("正在售票中……");
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                count--;
                //Thread.currentThread().getName()获取当前执行的线程的名称
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
            }
        }

    }

}


运行结果:

关于多线程不安全问题以及解决方法_第1张图片

  从上面的运行结果我们可以看到余票出现的负数的情况。明明我们在代码中规定了能进入循环的条件是count>0,不希望出现票数为负数的情况,但是现在却出现了。
  这是由于count=1时,Thread-1进入循环了,但是还没走到count–的时候,Thread-0和Thread-2也进入了循环,然后1线程执行完count=0,接下来0和2线程都相继进行count–,就出现了负数的情况。像这样的情况就是线程不安全。

线程安全

  线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

解决线程不安全问题的方法

1.同步代码块
因为这个例子的main方法和上面的例子一样,所以我就不写main方法了。代码示例:

static class Ticket implements Runnable{
     
        private int count=10;//一共10张票
        Object o=new Object();//创建一个锁对象
        @Override
        public void run() {
     

            while (true) {
     
            //当线程进入循环后就会看同步代码中的锁对象是否有锁标记,若有则在这里排队等待
                synchronized (o) {
     
                    if (count > 0) {
     
                        System.out.println("正在售票中……");
                        try {
     
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
     
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                    }else {
     
                        break;//没有票之后结束循环
                    }
                }
                //当线程执行完同步代码块中的代码后就会放开锁标记,其他的线程就可以进入同步代码块
            }

        }

    }

运行结果:
关于多线程不安全问题以及解决方法_第2张图片
现在就不会出现票数为负数的情况了。代码并没有出现bug,实在是Thread-0太厉害了,它卖了第一张票之后,直接将锁打上了标记,别人进不去,它自己又进去卖票了,执行了好多次,Thread-1和Thread-2都抢不到。

2.同步方法
同步方法和同步代码块差不多。同步方法就是把需要线程排队的代码写到一个方法里,这个方法用synchronized修饰。

static class Ticket implements Runnable{
     
        private int count=10;//一共10张票
        //Object o=new Object();//创建一个锁对象
        @Override
        public void run() {
     
            while (true) {
     
                sale();
            }
        }

        //定义一个方法,用synchronized修饰
        public synchronized void sale(){
     
            if (count > 0) {
     
                System.out.println("正在售票中……");
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
            }
        }
    }

运行结果:
关于多线程不安全问题以及解决方法_第3张图片
3.显式锁Lock
所谓的显式 就是我们能看得见的,显式锁需要我们手动去上锁和释放锁。上面说的同步代码块和同步方法都属于隐式锁。

    static class Ticket implements Runnable{
     
        private int count=10;//一共10张票
        //创建一个显式锁l,这个显式锁是一个公平锁
        Lock l=new ReentrantLock(true);
        @Override
        public void run() {
     
            while (true) {
     
                l.lock();//锁住
                if (count > 0) {
     
                    System.out.println("正在售票中……");
                    try {
     
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                }else {
     
                    break;
                }
                l.unlock();//释放锁
            }
        }

    }

运行结果:
关于多线程不安全问题以及解决方法_第4张图片
  这次并没有出现都是Thread-0卖票了,这是因为我在创建显式锁的时候传了一个参数true,这表示创建一个公平的显式锁。所谓的公平就是Thread-0执行完之后,轮到后面排队的Thread-1和Thread-2执行,并不能像上面的同步代码块和同步方法那样,刚执行完又回去打上锁标记继续执行。

你可能感兴趣的:(java,多线程,并发编程)