写两个线程,其中一个线程打印1-52,另一个打印A-Z,打印顺序为12A34B56C....5152Z
2017-06-22 12:21
676 查看
这是疯狂java讲义的一道题。在网上能搜到各种正确答案,有各种不同版本。在此我整理了一下,然后把其中的道理归纳总结一下。下面列出了各个版本:
1.有四个类,一个打印类,两个线程类,和一个测试类。打印类里面用的同步方法,线程类是继承thread,不仅控制循环次数,还控制打印的字符串。执行完毕并没有阻塞,因为设置了阻塞的终点。
这是我最开始想到的版本,但是犯了个严重的错误,错误代码如下
错误在于,打印方法里面,一旦进入if,那么进行wait阻塞,就算之后另一个线程唤醒了它,也没有打印操作了。如果要保留else的这种写法,那么循环次数必须是52次,而不是26次,因为进入if26次加上进入else26次,而且交换着进入。
2.有四个类,一个打印类,两个线程类,和一个测试类。打印类里面用的同步方法,方法还控制打印的字符串的起点和终点,线程类是继承thread,只控制循环次数。执行完毕并没有阻塞,因为设置了阻塞的终点。
感觉这样好像更符合面对对象的思想,线程最好还是只控制循环次数,当然这是指同步监视器有方法可以用的时候,如果只是用无意义的object作为同步监视器,那么线程还得控制每次循环的操作。
3.有三个类,两个线程类,一个测试类。线程类用的同步代码块,不仅控制循环次数,还控制每次循环的输出。同步监视器是object,当然这里也可以新建个打印类,把打印类作为监视器,然后线程执行打印类的方法就行。但是感觉原来这样更方便了,毕竟只要满足题意就够了。执行完毕没有阻塞因为有终点。
4.这个版本相当于是第一版本的错误版,保留else,所以循环次数为52。但是有一点不一样的是,线程只控制循环次数,打印类控制输出操作是什么。
4.5 在4版本上升级下,用实现runnable接口来实现线程,新建一个匿名类。
5.两个类,打印类和测试类。为了线程实现同步,不用同步方法或者同步代码块了,而是用lock和condition来实现加锁解锁和等待唤醒。线程是用runnable接口实现,而且不是用的普通建立匿名类的方法,而是用的lambda表达式,因为runnable也是个函数式接口。用线程池newCachedThreadPool来调度线程。
总结一下:1.其实两个线程不需要设置flag,但是应该熟悉这种编程思路,不用flag的话,那么就在代码块最后先notifyall,再wait,就好了,顺序不能反。
2.哪个线程先start,就先执行。而此题过程很明确,很方便设置堵塞的终点。
3.为了更符合面对对象,尽量只让线程类控制循环次数,把实际的方法写在同步监视器的实际方法,最后只是让线程调用就行。为了少新建类,可以多用lamdba表达式创建runnable对象。
1.有四个类,一个打印类,两个线程类,和一个测试类。打印类里面用的同步方法,线程类是继承thread,不仅控制循环次数,还控制打印的字符串。执行完毕并没有阻塞,因为设置了阻塞的终点。
package testone; public class print { private boolean flag=true;//为真打印数字,为假打印字母 public print() { } public synchronized void printNumber(String s) { try { if(!flag) { wait(); } //为真时进入 System.out.print(s); flag=false; notifyAll(); } catch(InterruptedException ex) { ex.printStackTrace(); } } public synchronized void printLetter(String s) { try { if(flag) { wait(); } //为假时进入 System.out.print(s); flag=true; notifyAll(); } catch(InterruptedException ex) { ex.printStackTrace(); } } }
package testone; public class numthread extends Thread{ public print printer; public numthread(String name,print printer) { super(name); this.printer=printer; // TODO Auto-generated constructor stub } public void run() { int count=0; for(int i=1;i<=26;i++) { count++; String s=(2*i-1)+" "+2*i+" "; printer.printNumber(s); } } }
package testone; public class letterthread extends Thread { public print printer; public letterthread(String name,print printer) { super(name); this.printer=printer; // TODO Auto-generated constructor stub } public void run() { for(int i=1;i<=26;i++) { int temp=i+64; char c=(char) temp; printer.printLetter(c+" "); } } }
package testone; public class printTest { public static void main(String[] args) { // TODO Auto-generated method stub print printer=new print(); new numthread("打印数字线程",printer).start();; new letterthread("打印字母线程",printer).start(); } }
这是我最开始想到的版本,但是犯了个严重的错误,错误代码如下
package testone; public class print { private boolean flag=true;//为真打印数字,为假打印字母 public print() { } public synchronized void printNumber(String s) { try { if(!flag) { wait(); } else//为真时进入 { System.out.print(s); flag=false; notifyAll(); } } catch(InterruptedException ex) { ex.printStackTrace(); } } public synchronized void printLetter(String s) { try { if(flag) { wait(); } else//为假时进入 { System.out.print(s); flag=true; notifyAll(); } } catch(InterruptedException ex) { ex.printStackTrace(); } } }
错误在于,打印方法里面,一旦进入if,那么进行wait阻塞,就算之后另一个线程唤醒了它,也没有打印操作了。如果要保留else的这种写法,那么循环次数必须是52次,而不是26次,因为进入if26次加上进入else26次,而且交换着进入。
2.有四个类,一个打印类,两个线程类,和一个测试类。打印类里面用的同步方法,方法还控制打印的字符串的起点和终点,线程类是继承thread,只控制循环次数。执行完毕并没有阻塞,因为设置了阻塞的终点。
package testTWO; class Print { //同步监视器是Print类 private int i = 1; private char j = 'A'; public Print() { } public synchronized void printNumber() {//同步方法 System.out.print(String.valueOf(i) + String.valueOf(i + 1)); i += 2; notifyAll(); //先唤醒其他进程,再阻塞本进程,如果顺序颠倒了,进程阻塞后不能再唤醒其他进程 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void printWord() { System.out.print(j); j++; notifyAll(); try { if (j <= 'Z')//输出Z之后就不用再等待了。 { wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package testTWO; class PrintNumber extends Thread {//打印数字线程 private Print p; public PrintNumber(Print p) { this.p = p; } public void run() { for (int i = 0; i < 26; i++) { p.printNumber(); } } }
package testTWO; class PrintWord extends Thread {//打印字母线程 private Print p; public PrintWord(Print p) { this.p = p; } public void run() { for (int i = 0; i < 26; i++) { p.printWord(); } } }
package testTWO; public class PrintTest { public static void main(String[] args) { Print p = new Print(); Thread t1 = new PrintNumber(p); Thread t2 = new PrintWord(p); t2.start();t1.start(); //同步监视器设置好了数据的初值,而且设置好了每次运行方法的方法体 //而线程只是控制了循环次数和执行同步监视器的方法 //t1,t2.start这两条语句交换顺序,则t1线程在最后会一直阻塞,因为程序没有为t1设置不阻塞的终点 //因为只有两个线程,所以没有用到flag } }
感觉这样好像更符合面对对象的思想,线程最好还是只控制循环次数,当然这是指同步监视器有方法可以用的时候,如果只是用无意义的object作为同步监视器,那么线程还得控制每次循环的操作。
3.有三个类,两个线程类,一个测试类。线程类用的同步代码块,不仅控制循环次数,还控制每次循环的输出。同步监视器是object,当然这里也可以新建个打印类,把打印类作为监视器,然后线程执行打印类的方法就行。但是感觉原来这样更方便了,毕竟只要满足题意就够了。执行完毕没有阻塞因为有终点。
package testTHRRE; class Thread1 extends Thread { private Object obj; public Thread1(Object obj) { this.obj = obj; } public void run() { synchronized (obj) { // 打印1-52 for (int i = 1; i < 53; i++) { System.out.print(i + " "); if (i % 2 == 0) { // 不能忘了 唤醒其它线程 obj.notifyAll(); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }}
package testTHRRE; class Thread2 extends Thread { private Object obj; public Thread2(Object obj) { this.obj = obj; } public void run() { synchronized (obj) //同步监视器是obj类,同步代码块是写在run方法里面的。 { // 打印A-Z for (int i = 0; i < 26; i++) { System.out.print((char)('A' + i) + " "); // 不能忘了 唤醒其它线程 obj.notifyAll(); try { // 最后一个就不要等了 if (i != 25) { obj.wait(); } } catch (InterruptedException e) 4000 { e.printStackTrace(); } } } } }
package testTHRRE; public class ThreadDemo { // 测试 public static void main(String[] args) throws Exception { Object obj = new Object(); // 启动两个线程 Thread1 t1 = new Thread1(obj); Thread2 t2 = new Thread2(obj); t1.start(); t2.start(); //虽然同步监视器只是个无意义的object,但是有了监视器却保证了有了监视,两个线程可以交替执行, //因为是两个,所以不需要用到flag //t1,t2.start这两条语句交换顺序,则t1线程在最后会一直阻塞,因为程序没有为t1设置不阻塞的终点 } }
4.这个版本相当于是第一版本的错误版,保留else,所以循环次数为52。但是有一点不一样的是,线程只控制循环次数,打印类控制输出操作是什么。
package testFour; class Print { private boolean flag = false; public int num = 1; public char chr = 'A'; public synchronized void printNumber() { try { if(flag) { if(num <= 52) { wait(); } } else { //进入else进入了26次 System.out.print(num); System.out.print(num + 1); num += 2; flag = true; notify(); } } catch(InterruptedException ie) { ie.printStackTrace(); } } public synchronized void printWord() { try { if(!flag) { if(chr <= 'Z') { wait(); } } else { System.out.print(chr); chr += 1; flag = false; notify(); } } catch(InterruptedException ie) { ie.printStackTrace(); } } }
package testFour; class PrintNumber extends Thread { Print p; PrintNumber(Print p) { this.p = p; } public void run() { for(int i = 0; i < 52; i ++) { p.printNumber(); } } }
package testFour; class PrintWord extends Thread { Print p; PrintWord(Print p) { this.p = p; } public void run() { for(int i = 0; i < 52; i ++) { p.printWord(); } } }
package testFour; public class test1 { public static void main(String[] args) { Print p = new Print(); new PrintNumber(p).start(); new PrintWord(p).start(); } }
4.5 在4版本上升级下,用实现runnable接口来实现线程,新建一个匿名类。
package testFour; class Print { private boolean flag = false; public int num = 1; public char chr = 'A'; public synchronized void printNumber() { try { if(flag) { if(num <= 52) { wait(); } } else { //进入else进入了26次 System.out.print(num); System.out.print(num + 1); num += 2; flag = true; notify(); } } catch(InterruptedException ie) { ie.printStackTrace(); } } public synchronized void printWord() { try { if(!flag) { if(chr <= 'Z') { wait(); } } else { System.out.print(chr); chr += 1; flag = false; notify(); } } catch(InterruptedException ie) { ie.printStackTrace(); } } }
package testFour; public class test2 { public static void main(String[] args) { Print p = new Print(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i = 0; i < 52; i ++) { p.printNumber(); } } }).start(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i = 0; i < 52; i ++) { p.printWord(); } } }).start(); } }
5.两个类,打印类和测试类。为了线程实现同步,不用同步方法或者同步代码块了,而是用lock和condition来实现加锁解锁和等待唤醒。线程是用runnable接口实现,而且不是用的普通建立匿名类的方法,而是用的lambda表达式,因为runnable也是个函数式接口。用线程池newCachedThreadPool来调度线程。
package testSix; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Print { private boolean flag = false; public int num = 1; public char chr = 'A'; private final Lock lock=new ReentrantLock(); private final Condition cond=lock.newCondition(); public void printNumber() { lock.lock(); try { if(flag) { if(num <= 51) { cond.await(); } } else { //进入else进入了26次 System.out.print(num); System.out.print(num + 1); num += 2; flag = true; cond.signalAll(); } } catch(InterruptedException ie) { ie.printStackTrace(); } finally { lock.unlock(); } } public void printWord() { lock.lock(); try { if(!flag) { if(chr <= 'Z') { cond.await(); } } else { System.out.print(chr); chr += 1; flag = false; cond.signalAll(); } } catch(InterruptedException ie) { ie.printStackTrace(); } finally { lock.unlock(); } } }
package testSix; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class printTest { public static void main(String[] args) { // TODO Auto-generated method stub Print p = new Print(); ExecutorService service = Executors.newCachedThreadPool(); service.execute(()->{ for(int i = 0; i < 52; i ++) { p.printNumber(); } }); service.execute(()->{ for(int i = 0; i < 52; i ++) { p.printWord(); } }); } }
总结一下:1.其实两个线程不需要设置flag,但是应该熟悉这种编程思路,不用flag的话,那么就在代码块最后先notifyall,再wait,就好了,顺序不能反。
2.哪个线程先start,就先执行。而此题过程很明确,很方便设置堵塞的终点。
3.为了更符合面对对象,尽量只让线程类控制循环次数,把实际的方法写在同步监视器的实际方法,最后只是让线程调用就行。为了少新建类,可以多用lamdba表达式创建runnable对象。
相关文章推荐
- 写两个线程,其中一个线程打印1-52,另一个打印A-Z,打印顺序为12A34B56C....5152Z。(《疯狂java讲义》第12章课后题一)
- 写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z。要求用线程间的通信
- 写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z。要求用线程间的通信
- 写两个线程,一个线程打印1-52,另一个线程打印A-Z,打印顺序为12A34B56C......5152Z。
- 写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z
- 写两个线程,一个线程打印1-52,另一个线程打印字母A-Z。打印 顺序为12A34B56C……5152Z
- 写两个线程,一个线程打印1-52,另一个线程打印字母A-Z。打印 顺序为12A34B56C……5152Z
- 写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z
- 写2个线程,其中一个线程打印1~52,另一个线程打印A~z,打印顺序应该是12A34B45C……5152Z
- 两个线程交替执行,一个打印1-52;一个打印A-Z;打印顺序为12A34B56C.....5152Z
- 用java写两个线程,一个线程打印1-52,另一个线程打印字母A-Z
- 用java写两个线程,一个线程打印1-52,另一个线程打印字母A-Z
- 写2个线程,一个打印1-52,一个打印A-Z,打印顺序是12A34B。。。(采用同步代码块和同步方法两种同步方法)
- 输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序
- 两个整数序列,其中一个序列表示栈的push顺序, 判断另一个序列有没有可能是对应的pop顺序。
- 写两个线程,一个线程打印1-52,另…
- PHP 输入两个整数序列。其中一个序列表示栈的push 顺序, 判断另一个序列有没有可能是对应的pop 顺序
- 输入两个整数序列。其中一个序列表示栈的push顺序, 判断另一个序列有没有可能是对应的pop顺序。 为了简单起见,我们假设push序列的任意两个整数都是不相等的
- 每天学习一算法系列(27)(输入两个整数序列。其中一个序列表示栈的push 顺序,判断另一个序列有没有可能是对应的pop 顺序)
- 【练习】输入两个整数序列。其中一个序列表示栈的push 顺序,判断另一个序列有没有可能是对应的pop 顺序