美文网首页
多线程操作同一对象的问题

多线程操作同一对象的问题

作者: 何几时 | 来源:发表于2020-11-25 19:13 被阅读0次

    1. 抢票代码

    package demo01_threadCreation;
    
    // 多个线程操作同一个对象的情况下,线程不安全,数据紊乱
    public class TestThread4 implements Runnable{
    
        // 票数
        private int ticketNum = 10;
    
        @Override
        public void run() {
            while (true)
            {
                if (ticketNum<=1) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
                // Thread.currentThread().getName() 得到当前线程的名字
            }
        }
    
        public static void main(String[] args) {
            TestThread4 tt = new TestThread4();
            new Thread(tt, "小明").start();
            new Thread(tt, "小红").start();
            new Thread(tt, "小强").start();
        }
    }
    
    ==================terminal===================
    小红拿到了第10票
    小红拿到了第7票
    小红拿到了第6票
    小红拿到了第5票
    小红拿到了第4票
    小红拿到了第3票
    小红拿到了第2票
    小红拿到了第1票
    小明拿到了第8票
    小强拿到了第9票
    

    分析:两个线程很有可能会同时操作同一个变量,这是很危险的行为,这时我们就要建立一个互斥锁

    1.1 抢票代码改进版

    package demo01_threadCreation;
    
    // 多个线程操作同一个对象的情况下,线程不安全,数据紊乱
    public class TestThread4 implements Runnable{
    
        // 票数
        private int ticketNum = 10;
        private boolean isLock = false;
    
        @Override
        public void run() {
            while (true)
            {
                if (ticketNum<=2) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!isLock)
                {
                    isLock = true;
                    System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
                }
                // Thread.currentThread().getName() 得到当前线程的名字
                isLock = false;
            }
        }
    
        public static void main(String[] args) {
            TestThread4 tt = new TestThread4();
            new Thread(tt, "小明").start();
            new Thread(tt, "小红").start();
            new Thread(tt, "小强").start();
        }
    }
    
    ==========terminal=============
    小强拿到了第10票
    小红拿到了第9票
    小明拿到了第8票
    小强拿到了第7票
    小红拿到了第6票
    小明拿到了第5票
    小明拿到了第4票
    小红拿到了第4票
    小强拿到了第3票
    小明拿到了第2票
    

    问题:为甚么还是会存在重票?这里重票真的意味着两个线程同时在写同一个内存吗?
    回答:这还是线程安全问题,也就是即使是以类变量(static)定义一个锁,或者用 boolean 类型的成员变量来作为线程锁,实际上都是不可行的,还是会出现同一时间有几个线程同时写入同一个地址。

    2.0 synchronized 线程同步

    参考这篇博客:https://www.cnblogs.com/littlepage/p/11655265.html#%E4%B8%80%E3%80%81%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%AE%9A%E4%B9%89

    概念:线程同步指的是,当有一个线程在对内存进行操作时,其他线程不能操作这块内存(也就是写入操作),直至到该线程结束操作,其他线程才能对改内存地址进行操作。
    多线程同时访问同一块内存地址会有线程安全问题,加锁就很有必要了。

    抢票改进代码2.0
    public synchronized void run(){...}

    package demo01_threadCreation;
    
    // 多个线程操作同一个对象的情况下,线程不安全,数据紊乱
    public class TestThread4_1 implements Runnable{
    
        // 票数
        private static int ticketNum = 10;
    
        @Override
        public synchronized void run() {
            while (true)
            {
                if (ticketNum<=0) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
    
                // Thread.currentThread().getName() 得到当前线程的名字
    
            }
        }
    
        public static void main(String[] args) {
            TestThread4_1 tt = new TestThread4_1();
            new Thread(tt, "小明").start();
            new Thread(tt, "小红").start();
            new Thread(tt, "小强").start();
        }
    }
    
    ===============terminal====================
    小强拿到了第10票
    小明拿到了第9票
    小明拿到了第8票
    小强拿到了第7票
    小红拿到了第6票
    小红拿到了第5票
    小强拿到了第5票
    小强拿到了第4票
    小红拿到了第3票
    小明拿到了第2票
    小红拿到了第1票
    

    在方法名前加 synchronized ,实际上等同于

    package demo01_threadCreation;
    
    // 多个线程操作同一个对象的情况下,线程不安全,数据紊乱
    public class TestThread4_1 implements Runnable{
    
        // 票数
        private static int ticketNum = 10;
    
        @Override
        public void run() {
            synchronized(TestThread4_1.class){
                while (true)
                {
                    if (ticketNum<=0) {
                        break;
                    }
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
    
                    // Thread.currentThread().getName() 得到当前线程的名字
    
                }
            }
    
    
        }
    
        public static void main(String[] args) {
            TestThread4_1 tt = new TestThread4_1();
            new Thread(tt, "小明").start();
            new Thread(tt, "小红").start();
            new Thread(tt, "小强").start();
        }
    }
    

    这个MyThread.class是一个互斥锁(mutex),C程序中,我们mutex的实现是,指定一个信号量,对其进行pv操作实现锁机制,Java中的对象锁,深纠底层,原理是锁是存在对象头里面的,什么是Java对象头?Java对象头是一个与对象本身无关的一个额外区域,我们在使用锁的时候,实际上对Java对象头内部指针进行了操作。

    Q:pv操作是什么呢?
    A:PV操作是一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思。

    相关文章

      网友评论

          本文标题:多线程操作同一对象的问题

          本文链接:https://www.haomeiwen.com/subject/dvbviktx.html