JAVA-多线程练习-交替打印

146 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

问题

编写两个线程,一个线程打印1-26的整数,另一个线程打印字母A-Z。打印顺序为1A2B3C….26Z。即按照整数和字母的顺序从小到大打印,并且每打印一个整数后,打印一个字母,交替打印

测试执行代码

import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Practice {
    public static AtomicBoolean printNumber = new AtomicBoolean(false);
    public static AtomicInteger currentIndex = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("-------------使用Synchronized------------------");
        new PrintLetterBySync().start();
        new PrintNumBySync().start();
        Thread.sleep(1000);
        System.out.println("-------------使用AtomicInteger------------------");
        new PrintLetterByAtomic().start();
        new PrintNumByAtomic().start();
        Thread.sleep(1000);
        System.out.println("-------------使用TransferQueue------------------");
        LinkedTransferQueue<String> numTransfer = new LinkedTransferQueue<String>();
        LinkedTransferQueue<String> letterTransfer = new LinkedTransferQueue<String>();
        new PrintLetterByExchanger(numTransfer, letterTransfer).start();
        new PrintNumByExchanger(numTransfer, letterTransfer).start();
    }
}
  • 使用Synchronized:对同一资源加锁-等待/唤醒的方式达成交替执行的目的,换成Lock也是一样的思路。
  • 使用AtomicInteger:使用CAS思路查看当前索引余数是否为单双数来判断当前是否应该执行打印,否则自旋,打印完成后自增。
  • 使用TransferQueue:此队列长度为0,在消费者未消费之前会阻塞线程,一个队列存储数字,一个队列存储字符交替获取和生产保证交替执行。

下面是具体代码

使用Synchronized

class PrintNumBySync extends Thread {
    private List<Integer> nums = IntStream.range(1, 27).boxed().collect(Collectors.toList());

    @Override
    public void run() {
        try {
            Practice.printNumber.set(true);
            synchronized (Object.class) {
                for (Integer num : nums) {
                    System.out.print(num);
                    if (num == 26) {
                        Object.class.notify();
                        return;
                    }
                    Object.class.notify();
                    Object.class.wait();
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

class PrintLetterBySync extends Thread {
    private List<String> letters = IntStream.range(65, 91).boxed().map(integer -> String.valueOf((char) integer.intValue())).collect(Collectors.toList());

    @Override
    public void run() {
        try {
            synchronized (Object.class) {
                if (!Practice.printNumber.get()) {
                    Object.class.wait();
                }
                for (String letter : letters) {
                    System.out.println(letter);
                    if (letter.equals("Z")) {
                        Object.class.notify();
                        return;
                    }
                    Object.class.notify();
                    Object.class.wait();
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
  • IntStream.range不包含最后一个数字。
  • 最后一个字符不需要wait。
  • printNumber是为了保证先打印数字

使用AtomicInteger

class PrintNumByAtomic extends Thread {
    private List<Integer> nums = IntStream.range(1, 27).boxed().collect(Collectors.toList());

    @Override
    public void run() {
        for (Integer num : nums) {
            while (Practice.currentIndex.get() % 2 != 1) {
                Thread.yield();
            }
            System.out.print(num);
            Practice.currentIndex.incrementAndGet();
        }
    }
}

class PrintLetterByAtomic extends Thread {
    private List<String> letters = IntStream.range(65, 91).boxed().map(integer -> String.valueOf((char) integer.intValue())).collect(Collectors.toList());

    @Override
    public void run() {
        for (String letter : letters) {
            while (Practice.currentIndex.get() % 2 != 0) {
                Thread.yield();
            }
            System.out.println(letter);
            Practice.currentIndex.incrementAndGet();
        }
    }
}

理论上来说使用volatile 也是可以的,因为有自旋。

使用TransferQueue


class PrintNumByExchanger extends Thread {
    private List<String> letters = IntStream.range(65, 91).boxed().map(integer -> String.valueOf((char) integer.intValue())).collect(Collectors.toList());
    private LinkedTransferQueue<String> numTransfer;
    private LinkedTransferQueue<String> letterTransfer;

    public PrintNumByExchanger(LinkedTransferQueue<String> numTransfer, LinkedTransferQueue<String> letterTransfer) {
        this.numTransfer = numTransfer;
        this.letterTransfer = letterTransfer;
    }

    @Override
    public void run() {
        for (String letter : letters) {
            try {
                String num = numTransfer.take();
                System.out.print(num);
                letterTransfer.transfer(letter);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class PrintLetterByExchanger extends Thread {
    private List<Integer> nums = IntStream.range(1, 27).boxed().collect(Collectors.toList());
    private LinkedTransferQueue<String> numTransfer;
    private LinkedTransferQueue<String> letterTransfer;

    public PrintLetterByExchanger(LinkedTransferQueue<String> numTransfer, LinkedTransferQueue<String> letterTransfer) {
        this.numTransfer = numTransfer;
        this.letterTransfer = letterTransfer;
    }

    @Override
    public void run() {
        for (Integer num : nums) {
            try {
                numTransfer.transfer(String.valueOf(num));
                String temp = letterTransfer.take();
                System.out.println(temp);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

先生产数字,消费数字保障打印顺序,否则会出现相互等待的情况。