Java 线程通信之输出奇偶数的几种写法

111 阅读1分钟

前言

合理的使用Java多线程可以更好地利用服务器资源。往往线程都是独立运行,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。 java 线程间的通信是面试常见的问题,对于两个线程分别输出奇偶数,则是经典中经典,下面来探讨输出奇偶数的几种写法。

1,synchronized 实现

代码如下:

public class PrintOddEvenUseSync {
   public static void main(String[] args) {
       Num num = new Num();
       Even even = new Even(num);
       Odd odd = new Odd(num);
       new Thread(even, "EVEN").start();
       new Thread(odd, "ODD").start();
   }

}

class Even implements Runnable {
   private Num num;
   public Even(Num num) {
       this.num = num;
   }

   @Override
   public void run() {
       while (num.num < 1000) {
           synchronized (num) {
               if (num.isEven()) {
                   System.out.println(Thread.currentThread().getName() + ":" + num.num++);
               }
               try {
                   Thread.sleep(200);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           }
       }
   }
}

class Odd implements Runnable {
   private Num num;

   public Odd(Num num) {
       this.num = num;
   }

   @Override
   public void run() {
       while (num.num < 1000) {
           synchronized (num) {
               if (num.isOdd()) {
                   System.out.println(Thread.currentThread().getName() + ":" + num.num++);
               }
               try {
                   Thread.sleep(200);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           }
       }
   }
}

这种方式来源于一位网友的写法:blog.csdn.net/x541211190/… 显然这种方式是不可取的,原因在于上面的方法不是线程交替打印,一个线程可以持续获取多次锁的,这里只不过用判断规避了这种情况而已。

2,使用 wait notify 经典写法

package oddeven;

/**
 * 使用wait notify
 * 线程间通讯
 * 推荐使用
 */
public class PrintOddEvenUseWait {
    public static void main(String[] args) {
        Num num = new Num();
        OddThread oddThread = new OddThread(num);
        EvenThread evenThread = new EvenThread(num);

        new Thread(oddThread,"ODD").start();
        try {
            Thread.sleep(400);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(evenThread,"EVEN").start();

    }

}

/**
 * 输出奇数
 */
class OddThread implements Runnable{

    private Num num;

    public OddThread(Num num){
        this.num = num;
    }

    @Override
    public void run() {
        while (num.num < 100){
            synchronized (num){
                if(!num.isOdd()){
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+":num ="+num.num++);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num.notify();
            }
        }
    }
}

/**
 * 输出偶数
 */
class EvenThread implements Runnable {
    private Num num;

    public EvenThread(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        while (num.num < 100) {
            synchronized (num) {
                if (!num.isEven()) {
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+":"+num.num++);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num.notify();
            }
        }
    }
}

这种写法体现了 线程通信的精髓,符合条件则直接输出,不符合条件则 wait 等待另外线程 notify ,交替进行。

3,使用 ReentrantLock

理论上来说只要 wait notify 能实现的线程间通信,ReentrantLock 都能实现,代码如下:

package oddeven;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock
 * 推荐使用
 */
public class PrintOddEvenUseReentrantLock {
    public static void main(String[] args) throws InterruptedException {
        NumShare numShare = new NumShare();
        OddPrint oddPrint = new OddPrint(numShare);
        EvenPrint evenPrint = new EvenPrint(numShare);
        new Thread(oddPrint, "odd").start();
        Thread.sleep(400);
        new Thread(evenPrint, "even").start();
    }


}

class NumShare {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public int num;


    /**
     * 是否是偶数
     *
     * @return
     */
    public boolean isEven() {
        return (num & 1) == 0;
    }

    /**
     * 是否是奇数
     *
     * @return
     */
    public boolean isOdd() {
        return (num & 1) == 1;
    }
}

/**
 * 打印奇数
 */
class OddPrint implements Runnable {

    private NumShare share;

    public OddPrint(NumShare numShare) {
        this.share = numShare;
    }

    @Override
    public void run() {
        while (share.num < 100) {
            share.lock.lock();
            try {
                if (!share.isOdd()) {
                    share.condition.await();
                }
                System.out.println(Thread.currentThread().getName() + ":num=" + share.num++);
                Thread.sleep(200);
                share.condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                share.lock.unlock();
            }
        }

    }
}

/**
 * 打印奇数
 */
class EvenPrint implements Runnable {
    private NumShare share;
    public EvenPrint(NumShare numShare) {
        this.share = numShare;
    }

    @Override
    public void run() {
        while (share.num < 100) {
            share.lock.lock();
            try {
                if (!share.isEven()) {
                    share.condition.await();
                }
                System.out.println(Thread.currentThread().getName() + ":num=" + share.num++);
                Thread.sleep(200);
                share.condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                share.lock.unlock();
            }
        }

    }
}

和 wait notify 是同样的思想,Lock 的通信的方式会更加直观一些。

4,使用 LinkedTransferQueue,代码如下:

package oddeven;

import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;

/**
 * LinkedTransferQueue
 */
public class PrintOddEvenUseQueue {
    public static void main(String[] args) {
        NumQueue numQueue = new NumQueue();
        OddQueue oddQueue = new OddQueue(numQueue);
        EvenQueue evenQueue = new EvenQueue(numQueue);
        new Thread(oddQueue, "Odd").start();
        new Thread(evenQueue, "EVEN").start();
    }
}

class NumQueue {

    TransferQueue<Integer> queue = new LinkedTransferQueue<>();

    public int num;


    /**
     * 是否是偶数
     *
     * @return
     */
    public boolean isEven() {
        return (num & 1) == 0;
    }

    /**
     * 是否是奇数
     *
     * @return
     */
    public boolean isOdd() {
        return (num & 1) == 1;
    }
}

/**
 * 打印奇数
 */
class OddQueue implements Runnable {

    private NumQueue numQueue;

    public OddQueue(NumQueue numQueue) {
        this.numQueue = numQueue;
    }

    @Override
    public void run() {
        while (numQueue.num < 100) {

            if (!numQueue.isOdd()) {
                try {
                    numQueue.queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":num=" + numQueue.num++);

            try {
                Thread.sleep(200);
                numQueue.queue.transfer(numQueue.num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }

    }
}

/**
 * 打印奇数
 */
class EvenQueue implements Runnable {

    private NumQueue numQueue;

    public EvenQueue(NumQueue numQueue) {
        this.numQueue = numQueue;
    }

    @Override
    public void run() {
        while (numQueue.num < 100) {
            if (!numQueue.isEven()) {
                try {
                    numQueue.queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":num=" + numQueue.num++);
            try {
                Thread.sleep(200);
                numQueue.queue.transfer(numQueue.num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

线程间通信比较推荐2,3两种,比较常见。

源码:github.com/ThirdPrince…