Java并发教程:掌握多线程陷阱的破解之道
前言
各位码界掘金者们,本期教程旨在为各位解锁Java并发编程世界的奥秘,揭露那些潜藏的多线程陷阱,助力大家在并发编程的道路上披荆斩棘。
一、什么是并发编程?
并发编程,顾名思义,就是允许一个程序同时执行多个任务,就像我们俗称的“一心多用”。在Java中,实现并发编程往往依赖于多线程和异步编程。
二、Java并发编程常见的陷阱
1.死锁:互相等待的悲剧
死锁,就像交通拥堵中的恶性循环,发生在两个或多个线程互相等待对方释放资源时。比如,线程A持有锁A,需要获取锁B;而线程B持有锁B,需要获取锁A。于是,两线程原地踏步,陷入死锁。
解决之道:
采用死锁检测和恢复机制
避免在特定条件下嵌套锁
使用锁的升级策略,如先锁住公共资源,再锁住私有资源
死锁示范
publicListNodehead;
publicListNodetail;
publicvoidlockHead(){
synchronized(head){}
publicvoidlockTail(){
synchronized(tail){}
publicvoidinsertToList(ListNodenode){
lockHead();
lockTail();
insert(node);
publicvoidtest(){
ListNodenode1=newListNode(1);
ListNodenode2=newListNode(2);
newThread(()->{
insertToList(node1);
}).start();
newThread(()->{
insertToList(node2);
}).start();
解决
publicListNodehead;
publicListNodetail;
publicObjectlock=newObject();
publicvoidlockHead(){
synchronized(lock){}
publicvoidlockTail(){
synchronized(lock){}
publicvoidinsertToList(ListNodenode){
lockHead();
lockTail();
insert(node);
publicvoidtest(){
ListNodenode1=newListNode(1);
ListNodenode2=newListNode(2);
newThread(()->{
insertToList(node1);
}).start();
newThread(()->{
insertToList(node2);
}).start();
饥饿:等待无限远的失落
饥饿是一种极端的情况,发生在某个线程无限期地等待访问资源或执行任务,而其他线程却可以继续执行。例如,如果一个线程不断地占用锁,其他线程则可能永远无法获得它而陷入饥饿。
解决之道:
限制线程持有锁的时间
使用公平锁或轮询调度算法
饥饿示范
publicclass
privatestaticfinalObjectlock=newObject();
publicstaticvoidmain(String[]args){
newThread(()->{
synchronized(lock){
while(true){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}).start();
newThread(()->{
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
synchronized(lock){
}).start();
解决方法
publicclass
privatestaticfinalObjectlock=newObject();
publicstaticvoidmain(String[]args){
newThread(()->{
synchronized(lock){
while(true){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}).start();
newThread(()->{
try{
Thread.sleep(2000);
}catch(InterruptedExceptione){
e.printStackTrace();
synchronized(lock){
//dosomething
}).start();
竞态条件:时序混乱的噩梦
竞态条件是指多个线程争夺同一资源时,最终结果取决于线程的执行顺序。例如,如果两个线程同时更新同一个变量,那么最终的值将是不可预测的。
解决之道:
确保资源访问或更新是原子性的(不可分割的)
使用同步机制(如锁或原子操作)
竞态条件示范
publicintintNum;
publicvoidincreasing(){
intNum=intNum+1;
publicstaticvoidmain(String[]args){
exampleexample=newexample();
newThread(()->{
for(inti=0;i<1000;i++){
example.increasing();
}).start();
newThread(()->{
for(inti=0;i<1000;i++){
example.increasing();
}).start();
解决方法
AtomicLongatomicLong=newAtomicLong(0L);
publicvoidincreasing(){
atomicLong.incrementGetAndAdd(1);
publicstaticvoidmain(String[]args){
exampleexample=newexample();
newThread(()->{
for(inti=0;i<1000;i++){
example.increasing();
}).start();
newThread(()->{
for(inti=0;i<1000;i++){
example.increasing();
}).start();
内存可见性:一览众山小的孤独
内存可见性问题发生在多个线程更新共享变量时,由于每个线程具有自己的本地内存,导致其他线程看不到更新后的值。例如,一个线程更改了一个变量的值,而另一个线程仍然在使用旧的值。
解决之道:
使用volatile关键字
使用synchronized关键字
使用栅栏指令(如MemoryBarrier)
内存可见性示范
publicvolatilebooleanflag=false;
publicvoidupdateFlag(){
flag=true;
//dosomething
publicvoidcheckFlag(){
while(!flag){
publicstaticvoidmain(String[]args){
exampleexample=newexample();
newThread(()->{
example.updateFlag();
}).start();
newThread(()->{
example.checkFlag();
System.out.println("checked");
}).start();
解决
publicbooleanflag=false;
publicvoidupdateFlag(){
flag=true;
//dosomething
publicvoidcheckFlag(){
while(!flag){
publicstaticvoidmain(String[]args){
exampleexample=newexample();
newThread(()->{
example.updateFlag();
}).start();
newThread(()->{
example.checkFlag();
System.out.println("checked");
}).start();
添加微信