1. Introduction
1.绪论
In this tutorial, we’re going to have a look at how we can print even and odd numbers using two threads.
在本教程中,我们将看看如何使用两个线程来打印偶数和奇数。
The goal is to print the numbers in order, while one thread only prints the even numbers and the other thread only prints the odd numbers. We’ll be using the concepts of thread synchronization and inter-thread communication to solve the problem.
我们的目标是按顺序打印数字,而一个线程只打印偶数,另一个线程只打印奇数。我们将使用线程同步和线程间通信的概念来解决这个问题。
2. Threads in Java
2.Java中的线程
Threads are lightweight processes which can execute concurrently. Concurrent execution of multiple threads can be good regarding performance and CPU utilization since we can work on more than one task at once through different threads running in parallel.
线程是可以并发执行的轻量级进程。多个线程的并发执行在性能和CPU利用率方面是很好的,因为我们可以通过并行运行的不同线程同时处理一个以上的任务。
More information about threads in Java can be found in this article.
关于Java中线程的更多信息可以在这篇文章中找到。
In Java, we can create a thread by either extending the Thread class or by implementing the Runnable interface. In both the cases, we override the run method and write the implementation of the thread in it.
在Java中,我们可以通过扩展Thread类或实现Runnable接口来创建一个线程。在这两种情况下,我们都要覆盖run方法,并在其中写入线程的实现。
More information on how to use these methods to create a thread can be found here.
关于如何使用这些方法来创建线程的更多信息可以在这里找到。
3. Thread Synchronization
3.线程同步化
In a multi-threaded environment, it is possible that 2 or more threads are accessing the same resource at around the same time. This can be fatal and lead to erroneous results. To prevent this, we need to make sure that only one thread accesses the resource at a given point of time.
在多线程环境中,有可能有2个或更多的线程在大约同一时间访问同一资源。这可能是致命的,会导致错误的结果。为了防止这种情况,我们需要确保在一个特定的时间点上只有一个线程访问资源。
We can achieve this using thread synchronization.
我们可以使用线程同步来实现这一点。
In Java, we can mark a method or block as synchronized, which means that only one thread will be able to enter that method or block at a given point of time.
在Java中,我们可以将一个方法或块标记为同步,这意味着在某个时间点只有一个线程能够进入该方法或块。
More details on thread synchronization in Java can be found over here.
关于Java中线程同步的更多细节可以在这里找到。
4. Inter-Thread Communication
4.线程间通信
Inter-thread communication allows synchronized threads to communicate with each other using a set of methods.
线程间通信允许同步线程使用一组方法相互通信。
The methods used are wait, notify, and notifyAll, which are all inherited from the Object class.
使用的方法是wait、notify和notifyAll,它们都继承自Object类。
Wait() causes the current thread to wait indefinitely until some other thread calls notify() or notifyAll() on the same object. We can call notify() to waking up threads that are waiting for access to this object’s monitor.
Wait() 导致当前线程无限期地等待,直到其他线程对同一对象调用notify()或notifyAll()。我们可以调用notify()来唤醒那些等待访问该对象监视器的线程。
More details about the working of these methods can be found here.
关于这些方法工作的更多细节可以在这里找到。
5. Printing Odd and Even Numbers Alternatively
5.替代性地打印奇数和偶数
5.1. Using wait() and notify()
5.1.使用wait()/em>和notify()/em>
We will use the discussed concepts of synchronization and inter-thread communication to print odd and even numbers in ascending order using two different threads.
我们将使用讨论过的同步和线程间通信的概念,使用两个不同的线程按升序打印奇数和偶数。
In the first step, we’ll implement the Runnable interface to define the logic of both threads. In the run method, we check if the number is even or odd.
第一步,我们将实现Runnable接口来定义两个线程的逻辑。在run方法中,我们检查数字是偶数还是奇数。
If the number is even, we call the printEven method of the Printer class, else we call the printOdd method:
如果数字是偶数,我们调用Printer类的printEven方法,否则我们调用printOdd方法。
class TaskEvenOdd implements Runnable {
private int max;
private Printer print;
private boolean isEvenNumber;
// standard constructors
@Override
public void run() {
int number = isEvenNumber ? 2 : 1;
while (number <= max) {
if (isEvenNumber) {
print.printEven(number);
} else {
print.printOdd(number);
}
number += 2;
}
}
}
We define the Printer class as follows:
我们定义Printer类如下。
class Printer {
private volatile boolean isOdd;
synchronized void printEven(int number) {
while (!isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = false;
notify();
}
synchronized void printOdd(int number) {
while (isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = true;
notify();
}
}
In the main method, we use the defined class to create two threads. We create an object of the Printer class and pass it as the parameter to the TaskEvenOdd constructor:
在main方法中,我们使用定义的类来创建两个线程。我们创建一个Printer类的对象,并将其作为参数传递给TaskEvenOdd构造器。
public static void main(String... args) {
Printer print = new Printer();
Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
t1.start();
t2.start();
}
The first thread will be the odd thread, hence we pass false as the value of the parameter isEvenNumber. For the second thread, we pass true instead. We set the maxValue to 10 for both threads, so that only the numbers from 1 through 10 are printed.
第一个线程将是奇数线程,因此我们传递false作为参数isEvenNumber的值。对于第二个线程,我们传递true来代替。我们将两个线程的maxValue设置为10,这样就只有1到10的数字被打印。
We then start both the threads by calling the start() method. This will invoke the run() method of both threads as defined above wherein we check if the number is odd or even and print them.
然后我们通过调用start()方法启动两个线程。这将调用上面定义的两个线程的run()方法,其中我们检查数字是奇数还是偶数并打印它们。
When the odd thread starts running, the value of the variable number will be 1. Since it is less than the maxValue and the flag isEvenNumber is false, printOdd() is called. In the method, we check if the flag isOdd is true and while it is true we call wait(). Since isOdd is false initially, wait() is not called, and the value is printed.
当奇数线程开始运行时,变量number的值将是1。由于它小于maxValue,并且标志isEvenNumber为假,所以调用printOdd()。在该方法中,我们检查标志isOdd是否为真,当它为真时,我们调用wait()。由于isOdd最初是假的,wait()没有被调用,值被打印。
We then set the value of isOdd to true, so that the odd thread goes into the wait state and call notify() to wake up the even thread. The even thread then wakes up and prints the even number since the odd flag is false. It then calls notify() to wake up the odd thread.
然后我们将isOdd的值设置为true,这样奇数线程就会进入等待状态,并调用notify()来唤醒偶数线程。然后,偶数线程被唤醒并打印出偶数,因为odd标志是假的。然后它调用notify()来唤醒奇数线程。
The same process is carried out until the value of the variable number is greater than the maxValue.
同样的过程,直到变量number的值大于maxValue。
5.2. Using Semaphores
5.2.使用半吊子
A semaphore controls access to a shared resource through the use of a counter. If the counter is greater than zero, then access is allowed. If it is zero, then access is denied.
信号灯通过使用一个计数器来控制对共享资源的访问。如果计数器大于0,那么允许访问。如果它为零,则拒绝访问。
Java provides the Semaphore class in the java.util.concurrent package and we can use it to implement the explained mechanism. More details about semaphores can be found here.
Java在java.util.concurrent包中提供了Semaphore类,我们可以用它来实现上述机制。关于信号灯的更多细节可以在这里找到。
We create two threads, an odd thread, and an even thread. The odd thread would print the odd numbers starting from 1, and the even thread will print the even numbers starting from 2.
我们创建两个线程,一个奇数线程,一个偶数线程。奇数线程将打印从1开始的奇数,而偶数线程将打印从2开始的偶数。
Both the threads have an object of the SharedPrinter class. The SharedPrinter class will have two semaphores, semOdd and semEven which will have 1 and 0 permits to start with. This will ensure that odd number gets printed first.
两个线程都有一个SharedPrinter类的对象。SharedPrinter类将有两个信号,semOdd和semEven,它们将有1和0的许可开始。这将确保奇数首先被打印出来。
We have two methods printEvenNum() and printOddNum(). The odd thread calls the printOddNum() method and the even thread calls the printEvenNum() method.
我们有两个方法printEvenNum()和printOddNum()。奇数线程调用printOddNum()方法,偶数线程调用printEvenNum()方法。
To print an odd number, the acquire() method is called on semOdd, and since the initial permit is 1, it acquires the access successfully, prints the odd number and calls release() on semEven.
为了打印一个奇数,acquire()方法在semOdd上被调用,由于初始许可是1,它成功获取了访问权,打印了奇数,并在semEven.上调用release()。
Calling release() will increment the permit by 1 for semEven, and the even thread can then successfully acquire the access and print the even number.
调用release()将使semEven的许可增加1,然后偶数线程可以成功获得访问权并打印偶数。
This is the code for the workflow described above:
这是上述工作流程的代码。
public static void main(String[] args) {
SharedPrinter sp = new SharedPrinter();
Thread odd = new Thread(new Odd(sp, 10),"Odd");
Thread even = new Thread(new Even(sp, 10),"Even");
odd.start();
even.start();
}
class SharedPrinter {
private Semaphore semEven = new Semaphore(0);
private Semaphore semOdd = new Semaphore(1);
void printEvenNum(int num) {
try {
semEven.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semOdd.release();
}
void printOddNum(int num) {
try {
semOdd.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semEven.release();
}
}
class Even implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructor
@Override
public void run() {
for (int i = 2; i <= max; i = i + 2) {
sp.printEvenNum(i);
}
}
}
class Odd implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructors
@Override
public void run() {
for (int i = 1; i <= max; i = i + 2) {
sp.printOddNum(i);
}
}
}
6. Conclusion
6.结语
In this tutorial, we had a look at how we can print odd and even numbers alternatively using two threads in Java. We had a look at two methods to achieve the same results: using wait() and notify() and using a Semaphore.
在本教程中,我们看了如何在Java中使用两个线程交替打印奇数和偶数。我们看了两种方法来实现同样的结果。使用wait()和notify()以及使用Semaphore。
And, as always, the full working code is available over on GitHub.
而且,像往常一样,完整的工作代码可以在GitHub上找到。