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

来源:互联网 发布:mips linux gnu gcc 编辑:程序博客网 时间:2024/06/13 14:20

这是疯狂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 stubprint 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)                {                    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 stubPrint 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对象。


阅读全文
0 0