面试题分析
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,再回到主线程循环100次,往复循环50次,请写出程序
- 分析
子线程在循环的时候,主线程不能执行,说明子线程和主线程之间一定要互斥,子线程循环10次的代码要被保护起来,主线程循环100次的代码要被保护起来
分析清楚业务逻辑,业务逻辑是子线程循环10次,接着主线程循环100次,彼此往复调用,然后外部调用业务逻辑这块
代码实现:
public class ThreadCommunication {
public static void main(String[] args) {
// 子线程
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 50; i++) {
for (int j = 1; j < 10; j++) {
System.out.println("sub thread sequence " + j + " of loop " + i);
}
}
}
}).start();
//主线程
for (int i = 1; i <= 50; i++) {
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequence " + j + " of loop " + i);
}
}
}
}
执行结果:
如下修改
public class ThreadCommunication {
public static void main(String[] args) {
// 子线程
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 50; i++) {
synchronized(ThreadCommunication.class) {
for (int j = 1; j <=10; j++) {
System.out.println("sub thread sequence " + j + " of loop " + i);
}
}
}
}
}).start();
//主线程
for (int i = 1; i <= 50; i++) {
synchronized (ThreadCommunication.class){
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequence " + j + " of loop " + i);
}
}
}
}
}
针对于循环使用synchronized保护起来,但是用了字节码作为锁,这时候,你确信你所有线程访问这个类下面的 某个方法是确实要全部同步就使用字节码对象作为锁,就是不分组,轻易不要使用字节码对象作为锁
优化:使用业务类封装业务逻辑
public class ThreadCommunication {
public static void main(String[] args) {
final Bussiness bs = new Bussiness();
// 子线程
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 50; i++) {
bs.sub(i);
}
}
}).start();
//主线程
for (int i = 1; i <= 50; i++) {
bs.main(i);
}
}
}
class Bussiness {
public synchronized void sub(int i){
for (int j = 1; j <=10; j++) {
System.out.println("sub thread sequence " + j + " of loop " + i);
}
}
public synchronized void main(int i){
for (int j = 1; j <=100; j++) {
System.out.println("main thread sequence " + j + " of loop " + i);
}
}
}
执行结果:
进行到这一步,说明已经达到了线程同步效果,即线程执行是互斥的,下面我们再看看如何线程通信,当子线程执行完去执行主线程
线程通信
java 线程之间通信是使用notify(),wait()方法
- Bussiness类做如下修改:
class Bussiness {
private boolean beShouldSub = true;
public synchronized void sub(int i){
if(!beShouldSub){
try {
//当不改论到自己执行的时候就等待,使用同一个同步监视器对象
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <=10; j++) {
System.out.println("sub thread sequence " + j + " of loop " + i);
}
beShouldSub = false;
}
public synchronized void main(int i){
//当不改自己执行的时候就等待,使用同一个同步监视器对象
if(beShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <=100; j++) {
System.out.println("main thread sequence " + j + " of loop " + i);
}
beShouldSub = true;
}
}
假设CPU 一下子就给了主线程,主线程一检查,不该自己执行,然后就等待,然后CPU就去找sub 线程,sub线程得到cpu资源就开始执行,执行完了,更新状态,这时候该主线程执行了,但是这时候主线程已经在等待了,不会去反推变量状态,所以主线程不会执行,这时候要主线程执行的话,需要将其唤醒,使用notify()
class Bussiness {
private boolean beShouldSub = true;
public synchronized void sub(int i){
if(!beShouldSub){
try {
//当不改论到自己执行的时候就等待,使用同一个同步监视器对象
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <=10; j++) {
System.out.println("sub thread sequence " + j + " of loop " + i);
}
beShouldSub = false;
this.notify();
}
public synchronized void main(int i){
//当不改自己执行的时候就等待,使用同一个同步监视器对象
if(beShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <=100; j++) {
System.out.println("main thread sequence " + j + " of loop " + i);
}
beShouldSub = true;
this.notify();
}
}
执行结果:
再次优化,将Bussiness 类的两个 if条件换成while条件,比如 if(beShouldSub) 改成while (beShouldSub)
会判断两次,第一次发现当不是自己执行的时候就等待,然后别人执行完把自己唤醒,唤醒之后又判断一下,是不是该自己执行了,是的话就自己执行了,while方式比if 好,更健壮,牢靠
为什么用while而不是if
有一些情况下,线程会被假唤醒, 就是没有被通知的时候会被唤醒
经验
要用到共同数据的若干方法应该归到一个类上,这种设计正好体现了高类聚和程序的健壮性
锁本身是一个对象,两个线程指定的代码要实现同步互斥的效果,那必须使用同一个lock对象,锁是在要操作的资源类(业务类) 的内部方法中,而不是在线程中,如上,线程操作的是Business 对象, synchronized是在Business类的两个内部方法上,而不是在线程上,因此将线程的同步,通信都是在资源类上,线程只是将资源类拿过去运行
写在最后的话
感谢张孝祥老师讲的java基础增强课程,一路走好,感谢!