5分钟搞懂多线程安全问题
前言
线程相关的面试题经久不衰,尤其是在JAVA领域
曾经某架构师对我说,不懂线程的是初级,懂线程的是高级,半懂不懂的是中级。
可见线程在面试中是一个怎样的角色了。但是,面试造火箭,入职拧螺丝是常态。我们真的有必要学线程吗?
有,很有必要!
就算是拧螺丝,你拿着比别人多的薪水拧,难道不舒服吗?
线程
什么是线程?
你就是一个线程
或者说,你拧螺丝的时候真像一个线程.
线程在计算机中是CPU的最小调度单位,啥意思呢?
程序要运行就会在内存中占用内存,那么计算机要怎么给程序分配内存呢,这个就由操作系统来管理分配,操作系统要怎么统一分配呢?
这时就产生了进程的概念。
操作系统会为每一个程序分配一块地盘,并加上标识,记录这是哪个程序的地盘。
地盘分完了得干活呀。谁来干呢?线程就出马了,每个进程都会有一个线程在工作。
而且一个进程可以有N个线程存在.
但同一时间,只能执行一个线程的工作(单核CPU中)。
这个就是所谓的最小调度单位,也可以说是最小工作单位。
所以说,你在拧螺丝的时候就是个线程。而在公司里有千千万万个这样的线程。
线程安全问题
假设,你接到一个需求,要拧200个螺丝,你一看文档现在还有200个整没拧。 这时候你的同事也接到这个需求,一看文档剩200个没拧。 这时候你们都去拧了一个,各自记录-1,还剩199个没拧。 但其实已经拧了2个了,这就有问题了。
用代码来演示一下,300个人拧200个螺丝会出现什么情况。
package Thread;
public class MyRun implements Runnable {
public static int luosi = 200;
@Override
public void run() {
for (int i = 1;i<=100;i++) { // 100人去拧这200个螺丝
if (luosi > 0) {
try {
Thread.sleep(50); // 假设每个人耗费50毫秒去拧
} catch (InterruptedException e) {
e.printStackTrace();
}
luosi-= 1; // 拧成功了,总数减去1
System.out.println(Thread.currentThread()+"拧了螺丝,还剩:"+luosi+"个没拧");
}
}
}
}
再来个入口函数去执行:
package Thread;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
MyRun mr = new MyRun();
Thread mh = new Thread(mr); // 第一个地方 100个人拧
Thread mh2 = new Thread(mr); // 第二个地方
Thread mh3 = new Thread(mr); // 第三个地方
mh.start();
mh2.start();
mh3.start();
}
}
结果:
Thread[Thread-1,5,main]拧了螺丝,还剩:2个没拧
Thread[Thread-0,5,main]拧了螺丝,还剩:2个没拧
Thread[Thread-1,5,main]拧了螺丝,还剩:0个没拧
Thread[Thread-2,5,main]拧了螺丝,还剩:-1个没拧
Thread[Thread-0,5,main]拧了螺丝,还剩:0个没拧
再来看下总共拧了多少螺丝
可以看到拧了222次螺丝。
这个就是传说中的线程安全问题。
多个线程操作同一个数据,出现的数据紊乱现象
为什么会出现问题
回想一下,在拧螺丝的时候,是否都要在拧之前查看一下还有多少个螺丝没拧.
因为每个人都有一份文档,各自优先更新自己的那份,没有及时同步给其他人.
这个例子和JAVA工作模式很是相近,画个图让大家理解一下:
每个线程都是优先操作自己的工作区,而主内存更新有可能不及时。
如何解决
最经典的一个办法
- 加锁
保证同一时间只有一个人在操作,并且直接更新主内存数据,拿到锁的一方也必须从主内存读取最新数据进行操作.
JAVA如何解决:
- synchronized
package Thread;
public class MyRun implements Runnable {
public static int luosi = 200;
@Override
public void run() {
for (int i = 1;i<=100;i++) { // 100人去拧这200个螺丝
synchronized (MyRun.class) { // 加锁,保证原子性,可见性操作
if (luosi > 0) {
try {
Thread.sleep(50); // 假设每个人耗费50毫秒去拧
} catch (InterruptedException e) {
e.printStackTrace();
}
luosi-= 1; // 拧成功了,总数减去1
System.out.println(Thread.currentThread()+"拧了螺丝,还剩:"+luosi+"个没拧");
}
}
}
}
}
结果:
Thread[Thread-0,5,main]拧了螺丝,还剩:4个没拧
Thread[Thread-0,5,main]拧了螺丝,还剩:3个没拧
Thread[Thread-2,5,main]拧了螺丝,还剩:2个没拧
Thread[Thread-1,5,main]拧了螺丝,还剩:1个没拧
Thread[Thread-1,5,main]拧了螺丝,还剩:0个没拧
拧了多少:
这次正常了,我们完美的解决了线程安全问题。
总结
线程安全问题只会出现在多个线程操作同一个数据上,否则不会出现线程安全问题。 而一般解决这种问题的方式就是加锁。我们回想一下,这个锁是不是很熟悉,在MySQL中,多个事务操作同一条数据也是通过加锁来隔离的。而这都会造成一个共同的问题,性能下降,甚至死锁问题。
加锁是否是解决线程安全问题的最优解呢?
而且我们在做业务需求时,真的有必要开启多线程吗?我觉得这200个螺丝,我一个人拧的更快!
PS:你的赞是我创作的动力!
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: