您的位置:首页 > 编程语言 > Java开发

写两个线程,其中一个线程打印1-52,另一个打印A-Z,打印顺序为12A34B56C....5152Z

2017-06-22 12:21 676 查看
这是疯狂java讲义的一道题。在网上能搜到各种正确答案,有各种不同版本。在此我整理了一下,然后把其中的道理归纳总结一下。下面列出了各个版本:

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对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程
相关文章推荐