并发技术05传统线程同步通信技术

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发技术05传统线程同步通信技术相关的知识,希望对你有一定的参考价值。

技术图片

我们先来看一个问题:

有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次……如此往返执行50次。

看完这个问题,很明显要用到线程间的通信了, 先分析一下思路:首先肯定要有两个线程,然后每个线程中肯定有个50次的循环,因为每个线程都要往返执行任务50次,主线程的任务是执行5次,子线程的任务是执行10次。线程间通信技术主要用到 wait() 方法和 notify() 方法。 wait() 方法会导致当前线程等待,并释放所持有的锁, notify() 方法表示唤醒在此对象监视器上等待的单个线程。下面来一步步完成这道线程间通信问题。
首先不考虑主线程和子线程之间的通信,先把各个线程所要执行的任务写好:

public

class

TraditionalThreadCommunication

{

public

static

void
 main
(
String
[]
 args
)

{

//开启一个子线程

new

Thread
(
new

Runnable
()

{

@Override

public

void
 run
()

{

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{

synchronized

(
TraditionalThreadCommunication
.
class
)

{

//子线程任务:执行10次               

for
(
int
 j 
=

1
;
j 
<=

10
;
 j 
++)

{

System
.
out
.
println
(
"sub thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}

}

}

}

}).
start
();

//main方法即主线程

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{

synchronized

(
TraditionalThreadCommunication
.
class
)

{

//主线程任务:执行5次

for
(
int
 j 
=

1
;
j 
<=

5
;
 j 
++)

{

System
.
out
.
println
(
"main thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}

}

}

}
}

如上,两个线程各有50次大循环,执行50次任务,子线程的任务是执行10次,主线程的任务是执行5次。为了保证两个线程间的同步问题,所以用了 synchronized 同步代码块,并使用了相同的锁:类的字节码对象。这样可以保证线程安全。但是这种设计不太好,就像我在上一节的死锁中写的一样,我们可以把线程任务放到一个类中,这种设计的模式更加结构化,而且把不同的线程任务放到同一个类中会很容易解决同步问题,因为在一个类中很容易使用同一把锁。所以把上面的程序修改一下:

public

class

TraditionalThreadCommunication

{

public

static

void
 main
(
String
[]
 args
)

{

Business
 bussiness 
=

new

Business
();

//new一个线程任务处理类

//开启一个子线程

new

Thread
(
new

Runnable
()

{

@Override

public

void
 run
()

{

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{
                    bussiness
.
sub
(
i
);

}

}

}).
start
();

//main方法即主线程

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{
            bussiness
.
main
(
i
);

}

}
}
//要用到的共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。
class

Business

{

public

synchronized

void
 sub
(
int
 i
)

{

for
(
int
 j 
=

1
;
j 
<=

10
;
 j 
++)

{

System
.
out
.
println
(
"sub thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}

}

public

synchronized

void
 main
(
int
 i
)

{

for
(
int
 j 
=

1
;
j 
<=

5
;
 j 
++)

{

System
.
out
.
println
(
"main thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}
}

经过这样修改后,程序结构更加清晰了,也更加健壮了,只要在两个线程任务方法上加上 synchronized 关键字即可,用的都是 this 这把锁。但是现在两个线程之间还没有通信,执行的结果是主线程循环执行任务50次,然后子线程再循环执行任务50次,原因很简单,因为有 synchronized 同步。
下面继续完善程序,让两个线程之间完成题目中所描述的那样通信:

public

class

TraditionalThreadCommunication

{

public

static

void
 main
(
String
[]
 args
)

{

Business
 bussiness 
=

new

Business
();

//new一个线程任务处理类

//开启一个子线程

new

Thread
(
new

Runnable
()

{

@Override

public

void
 run
()

{

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{
                    bussiness
.
sub
(
i
);

}

}

}).
start
();

//main方法即主线程

for
(
int
 i 
=

1
;
 i 
<=

50
;
 i 
++)

{
            bussiness
.
main
(
i
);

}

}
}
//要用到共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高雷剧和程序的健壮性。
class

Business

{

private

boolean
 bShouldSub 
=

true
;

public

synchronized

void
 sub
(
int
 i
)

{

while
(!
bShouldSub
)

{

//如果不轮到自己执行,就睡

try

{

this
.
wait
();

//调用wait()方法的对象必须和synchronized锁对象一致,这里synchronized在方法上,所以用this

}

catch

(
InterruptedException
 e
)

{

// TODO Auto-generated catch block
                e
.
printStackTrace
();

}

}

for
(
int
 j 
=

1
;
j 
<=

10
;
 j 
++)

{

System
.
out
.
println
(
"sub thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}

        bShouldSub 
=

false
;

//改变标记

this
.
notify
();

//唤醒正在等待的主线程

}

public

synchronized

void
 main
(
int
 i
)

{

while
(
bShouldSub
)

{

//如果不轮到自己执行,就睡

try

{

this
.
wait
();

}

catch

(
InterruptedException
 e
)

{

// TODO Auto-generated catch block
                e
.
printStackTrace
();

}

}

for
(
int
 j 
=

1
;
j 
<=

5
;
 j 
++)

{

System
.
out
.
println
(
"main thread sequence of "

+
 j 
+

", loop of "

+
 i
);

}
        bShouldSub 
=

true
;

//改变标记

this
.
notify
();

//唤醒正在等待的子线程

}
}

首先,先不说具体的程序实现,就从结构上来看,已经体会到了这种设计的好处了:主函数里不用修改任何东西,关于线程间同步和线程间通信的逻辑全都在 Business 类中,主函数中的不同线程只需要调用放在该类中对应的任务即可。体现了高类聚的好处。

再看一下具体的代码,首先定义一个 boolean 型变量来标识哪个线程该执行,当不是子线程执行的时候,它就睡,那么很自然主线程就执行了,执行完了,修改了 bShouldSub 并唤醒了子线程,子线程这时候再判断一下 while 不满足了,就不睡了,就执行子线程任务,同样地,刚刚主线程修改了 bShouldSub 后,第二次循环来执行主线程任务的时候,判断 while 满足就睡了,等待子线程来唤醒。这样逻辑就很清楚了,主线程和子线程你一下我一下轮流执行各自的任务,这种节奏共循环50次。

另外有个小小的说明:这里其实用 if 来判断也是可以的,但是为什么要用 while 呢?因为有时候线程会假醒(就好像人的梦游,明明正在睡,结果站起来了),如果用的是 if 的话,那么它假醒了后,就不会再返回去判断 if 了,那它就很自然的往下执行任务,好了,另一个线程正在执行呢,啪叽一下就与另一个线程之间相互影响了。但是如果是 while 的话就不一样了,就算线程假醒了,它还会判断一下 while 的,但是此时另一个线程在执行啊,bShouldSub 并没有被修改,所以还是进到 while 里了,又被睡了~所以很安全,不会影响另一个线程!官方 JDK 文档中也是这么干的。

如果觉得对您有帮助,请转发给更多人吧~

关注“程序员私房菜”,学习更多技术干货,领取更多免费资源

↓↓↓

技术图片

以上是关于并发技术05传统线程同步通信技术的主要内容,如果未能解决你的问题,请参考以下文章

并发技术12线程锁技术的使用

java并发线程锁技术的使用

Java多线程与并发库4.传统线程同步通信技术

java并发传统线程同步通信技术

Java多线程与并发库高级应用-传统线程同步通信技术

(黑马Java多线程与并发库高级应用)04 传统线程同步通信技术