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

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


  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值