线程之间的通信二(面试题)

线程之间的通信二

要求用线程顺序打印AaBbCc…Zz
这道题一开始想到的就是sync加wait跟notify解法。这也是最经典的解法,同时也是这道题最想要的答案,但是但我们学了神奇的JUC并发包之后,解法就变得多种多样了,以下是我目前能想到的解法,分享给大家!

解法一:sync加wait跟notify

public class T01_00_sync_wait_notify {
	//定义两个数组,一个小写字母,一个大写字母
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
    //提前初始化数组的值
        for(int i = 1;i<=26;i++){
       		 //将26个字母存入数组
            lowers[i-1]=(char)(96+i);
            //默认是小写用char包装类转换成大写的
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }
    public static void main(String[] args) {
    	//定义一个唯一的对象作为锁
        final Object o=new Object ();
        new Thread (()->{
            synchronized (o){
            	//我是先优先启动了打印大写字母的线程,所以开头是大写的
            	//要是强制要求它永远是第一个打印大写的加个布尔值在线程2进行判断
            	//这个布尔值是要保证可见性的加volatile关键字来保证
                for (char a:uppers) {
                        System.out.print(a);
                        try {
                        	//唤醒等待中的线程,notify 并不会释放锁
                            o.notify ();
                            //将线程一等待,同时释放锁
                            o.wait ();
                        } catch (InterruptedException e) {
                            e.printStackTrace ( );
                        }
                }
                //这里为啥要用notify ?
                //是为了防止线程一跟线程二其中还有线程在等待中的情况
                o.notify ();
            }

        },"t1").start ();
        new Thread (()->{
            synchronized (o){
                for (char a:lowers) {
                    System.out.print (a);
                    o.notify ();
                    try {
                        Thread.sleep (1);
                        o.wait ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ( );
                    }
                }
                o.notify ();
            }
        },"t2").start ();
    }

}

解法二:sync加wait跟notify

强制让那个线程先输出,加一个布尔值来控制

public class T02_00_sync_wait_notify {
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    //增加了一个布尔值来作为一个打印的标准
    static volatile boolean state=false;
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }
    public static void main(String[] args) {
        final Object o=new Object ();
        new Thread (()->{
            synchronized (o){
                for (char a:uppers) {
                    System.out.print(a);
                    //这里是优先让线程一打印大写字母
                    //在这里将state设置为true之后线程二马上能知道,这就是volatile保证线程可见性的功能
                    state=true;
                    try {
                        o.notify ();
                        o.wait ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ( );
                    }
                }
                o.notify ();
            }

        },"t1").start ();
        new Thread (()->{
            synchronized (o){
            //判断state状态是否为false如果是则让线程二进入等待状态
                while (!state){
                    try {
                        o.wait ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ( );
                    }
                }
                for (char a:lowers) {
                     System.out.print (a);
                     o.notify ();
                     try {
                         Thread.sleep (1);
                         o.wait ();
                     } catch (InterruptedException e) {
                         e.printStackTrace ( );
                     }
                }
                o.notify ();
            }
        },"t2").start ();
    }

}

解法三:Lock锁跟Condition

public class LockConditionTest {
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }

    public static void main(String[] args) {
    	//创建lock锁
        Lock lock=new ReentrantLock ();
        //这里为啥要创建二个condition呢,这样可以精准指定执行那个线程
        Condition upperCondition = lock.newCondition ( );
        Condition lowerCondition = lock.newCondition ( );
        new Thread (()->{
            try {
            //用lock锁必须要手动释放锁,他没有sync那样可以自动释放锁
                lock.lock ();
                for (char c:uppers) {
                    System.out.print(c);
                   	//唤醒小写线程锁
                    lowerCondition.signal ();
                    //让大写线程等待
                    upperCondition.await ();
                }
                //循环结束后怕等待队列还有为唤醒的线程
                lowerCondition.signal ();
            } catch (Exception e) {
                e.printStackTrace ( );
            } finally {
                lock.unlock ();
            }
        },"t1").start ();

        new Thread (()->{
            try {
                lock.lock ();
                for (char c:lowers) {
                    System.out.print(c);
                    upperCondition.signal ();
                    lowerCondition.await ();
                }
                upperCondition.signal ();
            } catch (Exception e) {
                e.printStackTrace ( );
            } finally {
                lock.unlock ();
            }
        },"t2").start ();
    }
}

解法四:LockSupport的park与unpark

public class LockSupportTest {
	//为啥将线程定义在这里,为啥不定在main方法中,这是因为我用了lamda表达式的原因,lamda的匿名内部类实现不能用可变的外部变量,只能用final关键字修饰
    static Thread t1=null,t2=null;
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static volatile boolean state=false;
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }

    public static void main(String[] args) {
        t1=new Thread (()->{
            for (char b:uppers) {
                System.out.print(b);
                //叫醒线程二
                LockSupport.unpark (t2);
                //park线程一
                LockSupport.park ();

            }
        });
        t2=new Thread (()->{
            for (char a:lowers) {
                //进来先阻塞住
                //之后t1唤醒t2直接执行下面的打印跟唤醒t1线程
                LockSupport.park ();
                System.out.print(a);
                LockSupport.unpark (t1);


            }
        });
        t1.start ();
        t2.start ();
    }
}

解法五:用CAS的解法

首先定义两个唯一的变量,自己定义一个死循环,来检测变量是否变了,变了就打印,打印完就换另一个变量

public class CASTest {
	//这里为啥用枚举呢,因为枚举是不可变的,可以保证唯一性
    enum ThreadEnum{T1,T2}
    //加了volatile 保证线程之间的可见性
    static volatile ThreadEnum threadEnum=ThreadEnum.T1;

    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }
    public static void main(String[] args) {
        new Thread (()->{
            for (char c:uppers) {
            //假如ThreadEnum不是T1就让它无限循环直到时间片用完。或者cpu调度了其他线程
                while (threadEnum!=ThreadEnum.T1){}
                //如果是则打印
                System.out.print(c);
                //将枚举变量改变为T2的
                threadEnum=ThreadEnum.T2;
            }
        }).start ();
        new Thread (()->{
            for (char c:lowers) {
                while (threadEnum!=ThreadEnum.T2){ }
                System.out.print(c);
                threadEnum=ThreadEnum.T1;
            }
        }).start ();
    }
}

解法六:AtomicXXX类实现

理论上来说AtomicXXX类里的所有类型都是可以实现的,我这里只是举利了一种AtomicInteger来说明
其实解法跟前面的CAS是差不多的,也是通过原子性操作与死循环来保证线程的之间的通信
用CAS的解法会消耗CPU性能,过多的死循环,因为循环CPU不会释放执行权,还是该线程在执行

public class AtomicIntegerTest {
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }

    public static void main(String[] args) {
    	//创建AtomicInteger 类,给默认值为1
        AtomicInteger aint = new AtomicInteger (1);
        new Thread (()->{
            for (char c : uppers) {
            //判断线程是否为1
            //不是就无限循环等待
                while (aint.get ()!=1){}
                //是则修改以下操作
                System.out.print (c);
                aint.set (2);
            }
        }).start ();
        new Thread (()->{
            for (char c : lowers) {
                while (aint.get ()!=2){ }
                System.out.print (c);
                aint.set (1);
            }
        }).start ();

    }
}

解法七 BlockingQueue用对列的方式

public class BlockingQueueTest {
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }

    public static void main(String[] args) {
    //创建两个队列,思路跟之前的condition差不多精准唤醒线程
        BlockingQueue upperQueue=new ArrayBlockingQueue (1);
        BlockingQueue lowerQueue=new ArrayBlockingQueue (1);

        new Thread (()->{
            for (char c:uppers) {
            	//打印
                System.out.print (c);
                //给小写队列存值
                lowerQueue.offer (1);
                try {
                	//给大写队列取值,队列没值的情况下就会阻塞
                    upperQueue.take ();
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
            }
        },"t1").start ();
        new Thread (()->{
            for (char c:lowers) {
                
                try {
                    lowerQueue.take ();
                    System.out.print (c);
                    upperQueue.offer (1);
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
            }
        },"t1").start ();
    }
}

解法八 TransferQueue的方式

public class TransferQueueTest {
    static char[] lowers=new char[26];
    static char[] uppers=new char[26];
    static {
        for(int i = 1;i<=26;i++){
            lowers[i-1]=(char)(96+i);
            uppers[i-1]=Character.toUpperCase ((char)(96+i));
        }
    }

    public static void main(String[] args) {
    	//创建队列
        TransferQueue<Character> transferQueue = new LinkedTransferQueue ();
        new Thread (()->{
            for (char c:uppers){
                try {
                //将值存入队列,会直接阻塞,等待其他线程取值
                    transferQueue.transfer (c);
                    System.out.print(transferQueue.take ());
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
            }
        }).start ();
        new Thread (()->{
            for (char c:lowers){
                try {
                //将值取出来,之后
                    System.out.print(transferQueue.take ());
                    //将值存入并阻塞
                    transferQueue.transfer (c);
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
            }
        }).start ();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值