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

2020-11-25  本文已影响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表示释放的意思。

上一篇下一篇

猜你喜欢

热点阅读