##多线程?我雇你来是偷懒的?
大橙子在学习多线程的过程中,通过调用多线程计算1-100累加和,发现在最终的执行结果中只有一个线程被执行调用了,其它的线程都被闲置了,我明明雇佣了多名员工去做同一件事请,结果好家伙,活全让一个人干了?到底是什么原因呢?我们来一起探讨一下
前言
在处理业务逻辑的时候,光靠一个线程来处理效率太低,因此需要引入多线程来同时处理,在保证线程安全的前提下,可以调高业务处理的效率。但是在实际运行过程中,有几个线程明明调用了,却发现一点都没有参与运算。
例如:我要自建一个房屋,我可以雇佣多个砖瓦工同时开工,期望在短期时间内完成,但是实际就只有一名砖瓦工建造了整个房屋,而我花了多余的钱,最终实现效果却和原来毫无差别。
提示:以下是本篇文章正文内容,下面案例可供参考
一、多线程及线程安全
1.多线程原理
多线程是指从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。可执行线程数量一般与CPU性能挂钩,现在计算机一般分为4核8线程,8核16线程等等。
2.线程安全
在这里我们还要考虑线程安全问题,因为多个线程在操作同一个数据的时候会因为CPU调度的问题使得数据发生异常。每个线程都有自己的栈空间,在调用同一份内存数据的时候,会将内存中的共享数据拷贝到自己的【变量副本】中,再对【变量副本】数据进行改写后,重新写回【共享数据】。如果在此时,另一个线程同一时间使用【共享数据】,两个线程会拿到一模一样的【共享数据】,而不是经过第一个线程修改后的数据了,因此最终结果会与预期结果不一致。
二、利用原子性解决线程安全问题
1.原子性
概念:多个线程同时操作【共享数据】要么都成功要么都失败
为了解决多线程的安全问题,我们需要引入原子性概念来处理数据,此时我们只要将【共享数据】进行【锁】操作就可以保证每次只能有一个线程操作【共享数据】。【锁】分为【乐观锁】(心态乐观,不管哪个线程执行到这里 你该怎么操作就怎么操作,我自己去验证,把悲伤留给自己)和【悲观锁】(心态悲观,认为线程一定会操作共享数据,不管哪个线程执行到这里,都必须要获得锁,然后再释放锁),为了验证本次程序的结果,我们在操作【共享数据】使用synchronized关键字来对执行过程上锁。
synchronized (this) {
if (num >= 101) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sum = sum + num;
num++;
System.out.println(Thread.currentThread().getName()+"==="+sum);
}
测试类中的代码如下(示例):
import java.util.concurrent.*;
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建了一个线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,//核心线程5个
10,//最大线程10个,
2,//备用线程存在2毫秒
TimeUnit.MILLISECONDS,//存在时间单位为毫秒
new ArrayBlockingQueue<>(10),阻塞队列10个
Executors.defaultThreadFactory(),//,默认线程工厂
new ThreadPoolExecutor.AbortPolicy());//异常抛出的拒绝策略
GetSum getSum = new GetSum();//创建了1-100的实现类对象
System.out.println("最终结果是"+pool.submit(getSum).get());//提交处理并打印最终结果
pool.submit(getSum);//将实现类对象提交到资源池进行处理第二次
pool.submit(getSum);//将实现类对象提交到资源池进行处理第三次
pool.submit(getSum);//将实现类对象提交到资源池进行处理第四次
pool.submit(getSum);//将实现类对象提交到资源池进行处理第五次
pool.shutdown();//销毁线程池
}
}
Callable实现类中的代码如下(示例):
import java.util.concurrent.Callable;
public class GetSum implements Callable {
//定义了两个私有int类型成员变量
private static int sum = 0;
private static int num = 1;
@Override
public Integer call() throws Exception {
//通过实现Callable类来重写run方法实现功能
while (true) {
synchronized (this) {
if (num >= 101) {
break;
}
try {
Thread.sleep(10);//线程睡眠10毫秒,模拟线程处理数据的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
sum = sum + num;
num++;
//在进行累加的过程中,输出此时该线程的名称和进行的累加步骤
System.out.println(Thread.currentThread().getName()+"==="+sum);
}
Thread.sleep(10);//线程睡眠10毫秒,使得其它线程能够及时抢到CPU执行权
}
return sum;
}
}
2.执行结果
代码如下(示例):
pool-1-thread-1===1
pool-1-thread-1===3
pool-1-thread-1===6
pool-1-thread-1===10
pool-1-thread-1===15
中间省略运行结果
pool-1-thread-1===4656
pool-1-thread-1===4753
pool-1-thread-1===4851
pool-1-thread-1===4950
pool-1-thread-1===5050
最终结果是5050
现象:多个线程同时进行累加操作,最终只有一个线程参与运算,其它线程根本没有参与运算,这是什么原因呢?在保证了线程安全的前提下也通过线程睡眠来让其它线程有足够时间能抢占到CPU执行权,可最终结果只是用了第一个线程。
通过不断的测试调整,这个结果始终没有改变,但是当我们把输出语句进行对调以后,神奇的事情发生了!!!
以前的输出语句:
System.out.println("最终结果是"+pool.submit(getSum).get());
pool.submit(getSum);
pool.submit(getSum);
pool.submit(getSum);
pool.submit(getSum);
现在的输出语句:
pool.submit(getSum);
pool.submit(getSum);
pool.submit(getSum);
pool.submit(getSum);
System.out.println("最终结果是"+pool.submit(getSum).get());
现在的输出结果:
pool-1-thread-2===1
pool-1-thread-5===3
pool-1-thread-1===6
pool-1-thread-3===10
pool-1-thread-4===15
中间省略运行结果
pool-1-thread-1===4656
pool-1-thread-2===4753
pool-1-thread-3===4851
pool-1-thread-5===4950
pool-1-thread-4===5050
最终结果是5050
现象:多个线程同时进行累加操作,并且都参与了运算,和第一次的运行结果完全不一致,实现了多线程操作同一个数据,提高了效率,使得线程池创建的使用也变得具有了意义,我雇佣的砖瓦工终于没有偷懒了。思来想去,好像跟我们最常用的打印语句有关System.out.printLn(),诶!就是这个输出语句存放的位置不同,导致了最终结果不同,原来是有个家伙偷偷使坏,叫我的工人罢工了,走进去看看这个人是何方神圣。
System.out.printLn()源码:
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
从打印语句我们可以看到,他依然加入了synchronized关键字,将整个打印语句进行了【锁】操作,使得同一时间只能一个线程进行打印操作,其它的线程都必须等着,相当于我雇佣的第一个工人必须把活干完,其他人才能继续干活,而此时其它工人的存在已毫无意义了,线程池这个“专业团队”,全是张伟一个人…
总结
打印语句加入了synchronized关键字,将整个打印语句进行了【锁】操作,使得同一时间只能一个线程进行打印操作,其它的线程都必须等着,使得原本利用多线程实现数据操作的方案落空,罪魁祸首就是打印语句,它限制了其它线程的调用,看来【悲观锁】的影响这么严重,不过人生应该乐观一点,不能因此而上了锁。
———————————————————————————————————————
本人水平有限,很多细节可能理解不到位,希望有各位大佬能悉心指出不当之处,交流学习,图源网络,侵删,我是大橙子,下次再见~