多线程操作同一对象的问题
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
线程同步
概念:线程同步指的是,当有一个线程在对内存进行操作时,其他线程不能操作这块内存(也就是写入操作),直至到该线程结束操作,其他线程才能对改内存地址进行操作。
多线程同时访问同一块内存地址会有线程安全问题,加锁就很有必要了。
抢票改进代码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表示释放的意思。