Thread is the basic unit of operating system operation. It is encapsulated in a process. A process can contain multiple threads. Even if we do not create a thread manually, the process will have a default thread running.
For the JVM, when we write a single-threaded program to run, there are at least two threads running in the JVM, one is the program we created, and the other is garbage collection.
Thread basic information
We can obtain some information about the current thread through the Thread.currentThread() method and modify it.
Let's look at the following code:
Thread.currentThread().setName("Test");
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
name = Thread.currentThread().getName();
priority = Thread.currentThread().getPriority();
groupName = Thread.currentThread().getThreadGroup().getName();
isDaemon = Thread.currentThread().isDaemon();
System.out.println("Thread Name:" + name);
System.out.println("Priority:" + priority);
GroupName , each thread will be in a thread group by default. We can also explicitly create a thread group. A thread group can also contain sub-thread groups, so that threads and thread groups form a tree structure.
Name , each thread will have a name. If not specified explicitly, the name rule is "Thread-xxx".
Priority , each thread will have its own priority, and the JVM's way of handling priority is "preemptive". When the JVM finds a thread with a high priority, it immediately runs the thread; for multiple threads with equal priorities, the JVM polls them. Java's thread priority ranges from 1 to 10, with the default being 5. The Thread class defines two constants: MIN_PRIORITY and MAX_PRIORITY to represent the highest and lowest priorities.
We can look at the following code, which defines two threads with different priorities:
Thread thread2 = new Thread("high")
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println("Thread 2 is running.");
}
}
};
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
}
thread1.start();
}
How to create a thread
The above content all demonstrates some information in the default thread, so how to create a thread? In Java, we have 3 ways to create threads.
Threads in Java either inherit the Thread class or implement the Runnable interface. Let's go through them one by one.
Use inner classes to create threads
We can use inner classes to create threads. The process is to declare a variable of type Thread and override the run method. The sample code is as follows:
We can derive a class from Thread and override its run method in a similar way to the above. The sample code is as follows:
public static void createThreadBySubClass()
{
MyThread thread = new MyThread();
thread.start();
}
We can define a class to implement the Runnable interface, and then use an instance of this class as a parameter to build the Thread variable constructor. The sample code is as follows:
public static void createThreadByRunnable()
{
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
This involves the running mode of multi-threading in Java. For Java, when multi-threading is running, there are differences between "multi-object multi-threading" and "single-object multi-threading":
Multi-object multi-threading , the program creates multiple thread objects during running, and one thread runs on each object.
Single object multi-threading , the program creates a thread object during running and runs multiple threads on it.
Obviously, from a thread synchronization and scheduling perspective, multi-object multi-threading is simpler. Of the above three thread creation methods, the first two are "multi-object multi-thread", and the third can use either "multi-object multi-thread" or "single-object single-thread".
Let's look at the following sample code, which will use the Object.notify method. This method will wake up a thread on the object; and the Object.notifyAll method will wake up all threads on the object.
public static void main(String[] args) throws InterruptedException
{
notifyTest();
notifyTest2();
notifyTest3();
}
private static void notifyTest() throws InterruptedException
{
MyThread[] arrThreads = new MyThread[3];
for (int i = 0; i < arrThreads.length; i++)
{
arrThreads[i] = new MyThread();
arrThreads[i].id = i;
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
for (int i = 0; i < arrThreads.length; i++)
{
synchronized(arrThreads[i])
{
arrThreads[i].notify();
}
}
}
private static void notifyTest2() throws InterruptedException
{
MyRunner[] arrMyRunners = new MyRunner[3];
Thread[] arrThreads = new Thread[3];
for (int i = 0; i < arrThreads.length; i++)
{
arrMyRunners[i] = new MyRunner();
arrMyRunners[i].id = i;
arrThreads[i] = new Thread(arrMyRunners[i]);
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
for (int i = 0; i < arrMyRunners.length; i++)
{
synchronized(arrMyRunners[i])
{
arrMyRunners[i].notify();
}
}
}
private static void notifyTest3() throws InterruptedException
{
MyRunner runner = new MyRunner();
Thread[] arrThreads = new Thread[3];
for (int i = 0; i < arrThreads.length; i++)
{
arrThreads[i] = new Thread(runner);
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
synchronized(runner)
{
runner.notifyAll();
}
}
}
class MyThread extends Thread
{
public int id = 0;
public void run()
{
System.out.println("Thread " + id + " is ready to sleep for 5 minutes.");
try
{
synchronized(this)
{
this.wait(5*60*1000);
}
}
catch(InterruptedException ex)
{
ex.printStackTrace();
}
System.out.println("The "th" + id + "thread was awakened.");
}
}
class MyRunner implements Runnable
{
public int id = 0;
public void run()
{
System.out.println("Thread " + id + " is ready to sleep for 5 minutes.");
try
{
synchronized(this)
{
this.wait(5*60*1000);
}
}
catch(InterruptedException ex)
{
ex.printStackTrace();
}
System.out.println("The "th" + id + "thread was awakened.");
}
}
The notifyAll method is suitable for "single object multi-thread" scenario, because the notify method will only randomly wake up one thread on the object.
Thread state switching
For a thread, from the time we create it until the thread ends, the status of the thread during this process may be like this:
Creation: There is already a Thread instance, but the CPU still has resources and time slices allocated to it.
Ready: The thread has obtained all the resources needed to run and is just waiting for the CPU to schedule the time.
Running: The thread is located in the current CPU time slice and is executing related logic.
Sleep: Generally the state after calling Thread.sleep. At this time, the thread still holds various resources required for operation, but will not be scheduled by the CPU.
Suspend: Generally the state after calling Thread.suspend. Similar to sleep, the CPU will not schedule the thread. The difference is that in this state, the thread will release all resources.
Death: The thread ends or the Thread.stop method is called.
Next we will demonstrate how to switch thread states. First we will use the following method:
Thread() or Thread(Runnable): Construct a thread.
Thread.start: Start a thread.
Thread.sleep: Switch the thread to sleep state.
Thread.interrupt: Interrupt the execution of the thread.
Thread.join: Wait for a thread to end.
Thread.yield: Deprive the thread of its execution time slice on the CPU and wait for the next scheduling.
Object.wait: Lock all threads on the Object and will not continue running until the notify method.
Object.notify: Randomly wake up a thread on the Object.
Object.notifyAll: Wake up all threads on Object.
Now, it’s demonstration time! ! !
Thread waiting and waking up
The Object.wait and Object.notify methods are mainly used here, please see the notify instance above. It should be noted that both wait and notify must target the same object. When we create a thread by implementing the Runnable interface, we should use these two methods on the Runnable object instead of the Thread object.
Thread sleeping and waking up
public static void main(String[] args) throws InterruptedException
{
sleepTest();
}
private static void sleepTest() throws InterruptedException
{
Thread thread = new Thread()
{
public void run()
{
System.out.println("Thread" + Thread.currentThread().getName() + "It will sleep for 5 minutes.");
try
{
Thread.sleep(5*60*1000);
}
catch(InterruptedException ex)
{
System.out.println("Thread" + Thread.currentThread().getName() + "Sleep was interrupted.");
}
System.out.println("Thread" + Thread.currentThread().getName() + "Sleep ended.");
}
};
thread.setDaemon(true);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
Although there is a Thread.stop method, this method is not recommended. We can use the sleep and wake-up mechanism above to let the thread end the thread when processing IterruptedException.
public static void main(String[] args) throws InterruptedException
{
stopTest();
}
private static void stopTest() throws InterruptedException
{
Thread thread = new Thread()
{
public void run()
{
System.out.println("The thread is running.");
try
{
Thread.sleep(1*60*1000);
}
catch(InterruptedException ex)
{
System.out.println("Thread interrupt, end thread");
return;
}
System.out.println("The thread ended normally.");
}
};
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
When we create 10 sub-threads in the main thread, and then we expect that after all 10 sub-threads are completed, the main thread will execute the next logic. At this time, it is time for Thread.join to appear.
public static void main(String[] args) throws InterruptedException
{
joinTest();
}
private static void joinTest() throws InterruptedException
{
Thread thread = new Thread()
{
public void run()
{
try
{
for(int i = 0; i < 5; i++)
{
System.out.println("The thread is running.");
Thread.sleep(1000);
}
}
catch(InterruptedException ex)
{
ex.printStackTrace();
}
}
};
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
thread.join();
System.out.println("The main thread ended normally.");
}
}
We know that all threads under a process share memory space, so how do we transfer messages between different threads? When reviewing Java I/O, we talked about PipedStream and PipedReader, and here is where they come into play.
The following two examples have exactly the same functions. The difference is that one uses Stream and the other uses Reader/Writer.
Thread thread1 = new Thread()
{
public void run()
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try
{
while(true)
{
String message = br.readLine();
pos.write(message.getBytes());
if (message.equals("end")) break;
}
br.close();
pos.close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
};
Thread thread2 = new Thread()
{
public void run()
{
byte[] buffer = new byte[1024];
int bytesRead = 0;
try
{
while((bytesRead = pis.read(buffer, 0, buffer.length)) != -1)
{
System.out.println(new String(buffer));
if (new String(buffer).equals("end")) break;
buffer = null;
buffer = new byte[1024];
}
pis.close();
buffer = null;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
};
thread1.setDaemon(true);
thread2.setDaemon(true);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
Thread thread1 = new Thread()
{
public void run()
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try
{
while(true)
{
String message = br.readLine();
bw.write(message);
bw.newLine();
bw.flush();
if (message.equals("end")) break;
}
br.close();
pw.close();
bw.close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
};
Thread thread2 = new Thread()
{
public void run()
{
String line = null;
try
{
while((line = br.readLine()) != null)
{
System.out.println(line);
if (line.equals("end")) break;
}
br.close();
pr.close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
};
thread1.setDaemon(true);
thread2.setDaemon(true);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}