java多线程中易犯的错误是什么

发布时间:2022-01-06 15:23:05 作者:iii
来源:亿速云 阅读:109

本篇内容介绍了“java多线程中易犯的错误是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

一、什么时候应该使用多线程?

不知道大家有没有想过这个问题,就是什么时候我该使用多线程呢?使用多线程就一定会提升系统性能吗?

1、其实是否应该使用多线程在很大程度上取决于应用程序的类型。

由于同一进程的多个线程是共享同一片内存资源的,在带来方便的同时也必然会增加其复杂性,如何保证多线程访问数据的一致性问题等。而多线程属于编程中容易翻车的地方。并且多线程编程问题的测试定位也是比较难的。总体来说,好的多线程是写出来,将多线程问题寄希望于测试中发现, 无疑是极度不可靠的。SO,努力的学习吧。

Java API 与多线程息息相关的的几大关键字:volatile、synchronized、 wait、 notify. 理解了这几个关键字,就可以编写多线程的代码了。

二、什么时候需要加锁?

在多线程场合下,最重要的就是保障数据的一致性问题,而保障数据一致性问题,就需要借助于锁了。

其实我们在多线程的场景下应该搞清楚一个问题,就是到底什么需要保护?并不是所有的的数据都需要加锁保护,只有那些涉及到被多线程访问的共享的数据才需要加锁保护。

锁的本质其实就是确保在同一时刻,只有一个线程在访问共享数据,那么此时该共享数据就能得到有效的保护。

举例说明下,比如我们想构造一个多线程下安全的单向链表:

<img src="/Users/luqiang/Downloads/公众号图片/链表插入新图.jpg" alt="链表插入新图"  />

假如现在有两个线程在操作这个链表,一个写线程插入一个新元素7,另一个读线程遍历链表数据,如果不使用任何锁,那就有可能出现下面的执行顺序:

步骤写线程读线程
0修改链表元素2的next指针指向7这个元素... ...
1... ...遍历链表,到7的时候发现next 为null,遍历结束
2修改元素7的next 指针指向3... ...

通过上面的例子我们可以明显看到在多线程下操作这个链表,有可能会导致读线程读到的数据不完整,只有从链表头部到元素7的位置的数据。由此可见,不加入任何保护措施的多线程保护,势必会导致数据的混乱。为了避免数据一致性问题,我们就需要将操作该队列的代码放入同步块内(锁的对象也就是这个链表实例),来确保同一时刻只有一个线程可以访问该链表。

如何加锁?

这里简单的说下,一般我们都是使用synchronized(如果没有特殊需求建议直接使用这个关键字,jdk新版本它真的很快),记住synchronized 锁的就是对象头。

简单的说下,主要有下面几种用法:

三、 多线程中易犯的错误

1、锁范围过大

共享资源访问完成后, 后续的代码没有放在synchronized同步代码块之外。 会导致当前线程长期无效的占用该锁, 而其它争用该锁的线程只能等待, 最终导致性能受到极大影响。

 public void test()
 {
 		synchronized(lock){
 		... ... //正在访问共享资源
 		... ... //做其它耗时操作,但这些耗时操作与共享资源无关
 	}
 }

面对上面这种写法,会导致此线程长期占有此锁,从而导致其他线程只能等待,下面来讨论下解决方法:

1)单CPU场景下,将不需要同步的耗时操作拿到同步块外面,有的情况可以提升性能,有的却不行。

2)多CPU场景下,将耗时的CPU操作拿到同步块外面,总是可以提升性能的

当然,不管怎么样,缩小锁的同步范围对于系统来说都是百利而无一害的,因此上面的代码应该改为:

 public void test()
 {
 		synchronized(lock){
 		... ... //正在访问共享资源
 	}
 		... ... //做其它耗时操作,但这些耗时操作与共享资源无关
 }

综上所述,一个重点,就是只将访问共享资源的代码放在同步块内,保证快进快出。

2、死锁的问题

死锁要知道的:

3、共用一把锁的问题

就是多个共享变量会共用一把锁,特别是在方法级别上使用synchronized,从而人为导致的锁竞争。

上例子,下面是新手容易犯的错误:

1 public class MyTest
2 {
3 Object shared;
4 synchronized void fun1() {...} //访问共享变量shared
5 synchronized void fun2() {...} //访问共享变量shared
6 synchronized void fun3() {...} //不访问共享变量shared
7 synchronized void fun4() {...} //不访问共享变量shared
8 synchronized void fun5() {...} //不访问共享变量shared
9 }

上面的代码每一个方法都被加了synchronized ,明显违背了保护什么锁什么的原则。

三、线程数我们一般设多少比较合理呢?

其实大家都知道,在大多数场合下多线程都是可以提高系统的性能和吞吐量,但一个系统到底多少个线程才是合理的?

总的来说,线程数量太多太少其实都不太好,多了会因为线程频繁切换导致开销增大,有时候反而降低了系统性能。少了又会导致CPU资源不能充分的利用起来,性能没有达到瓶颈。

所以,系统到底使用多少线程合适,是要看系统的线程是否能充分的利用了CPU。其实实际情况,是很多时候不消耗CPU,如:磁盘IO、网络IO等。

磁盘IO、网络IO相比CPU的速度,那简直是相当的慢的,在执行IO的这段时间里CPU其实是空闲的。如果这时其他线程能把这空闲的CPU利用上,就可以达到提示系统性能和吞吐的目的。

其实上面我们也提到过,也就是两种计算特性:

CPU密集型: 因为每个CPU都是高计算负载的情况,如果设置过多的线程反而会产生不必要的上下文切换。所以,一般线程我们会设置 CPU 核数 + 1就可以了,为啥要加1 呢,即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费,其实就是个备份。

IO密集型:因为大量的IO操作,会导致CPU处于空闲状态,所以这时我们可以多设置些线程。 所以, 线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时) 就可以了,希望能给你点启发。

“java多线程中易犯的错误是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. switchover to xxx 犯的低级错误
  2. iOS 推送通知中那些让你故意犯的错误~

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:如何实现IAR中使用堆和栈的问题分析

下一篇:使用香港服务器的误区和诀窍有哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》