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.
Basic thread information
We can get some information about the current thread through the Thread.currentThread() method and modify it.
Let’s look at the following code:
查看并修改当前线程的属性 String name = Thread.currentThread().getName(); int priority = Thread.currentThread().getPriority(); String groupName = Thread.currentThread().getThreadGroup().getName(); boolean isDaemon = Thread.currentThread().isDaemon(); System.out.println("Thread Name:" + name); System.out.println("Priority:" + priority); System.out.println("Group Name:" + groupName); System.out.println("IsDaemon:" + isDaemon); 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);
The attributes listed are as follows:
GroupName, each thread will be in a thread group by default, we can also explicitly create a thread group, a thread group It 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 explicitly specified, 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:
线程优先级示例 public static void priorityTest() { Thread thread1 = new Thread("low") { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread 1 is running."); } } }; 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(); }
As can be seen from the running results, the low-priority thread runs only after the high-priority thread completes running.
isDaemon, this attribute is used to control the relationship between parent and child threads. If set to true, when the parent thread ends, all child threads under it will also end. On the contrary, the life cycle of the child thread is not affected by the parent thread.
Let’s look at the following example:
IsDaemon 示例 public static void daemonTest() { Thread thread1 = new Thread("daemon") { public void run() { Thread subThread = new Thread("sub") { public void run() { for(int i = 0; i < 100; i++) { System.out.println("Sub Thread Running " + i); } } }; subThread.setDaemon(true); subThread.start(); System.out.println("Main Thread end."); } }; thread1.start(); }
Comparing the running results of the above code with those after deleting subThread.setDaemon(true);, we can find that in the latter, the sub-thread will complete execution and then end, while in the former, The child thread ends quickly.
How to create a thread
The above content all demonstrates some information in the default thread, so how should you 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:
使用内部类创建线程 public static void createThreadByNestClass() { Thread thread = new Thread() { public void run() { for (int i =0; i < 5; i++) { System.out.println("Thread " + Thread.currentThread().getName() + " is running."); } System.out.println("Thread " + Thread.currentThread().getName() + " is finished."); } }; thread.start(); }
Inherit Thread to create a thread
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:
派生Thread类以创建线程 class MyThread extends Thread { public void run() { for (int i =0; i < 5; i++) { System.out.println("Thread " + Thread.currentThread().getName() + " is running."); } System.out.println("Thread " + Thread.currentThread().getName() + " is finished."); } } public static void createThreadBySubClass() { MyThread thread = new MyThread(); thread.start(); }
Implement Runnable interface to create threads
We can define a class so that it implements 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:
实现Runnable接口以创建线程 class MyRunnable implements Runnable { public void run() { for (int i =0; i < 5; i++) { System.out.println("Thread " + Thread.currentThread().getName() + " is running."); } System.out.println("Thread " + Thread.currentThread().getName() + " is finished."); } } public static void createThreadByRunnable() { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); }
Threads can be created in the above three methods, and from the sample code, the functions performed by threads are the same, so what are the differences between these three creation methods?
This involves the running mode of multi-threading in Java. For Java, when multi-threading is running, there is a difference 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 take a look at the sample code below, 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.
notify示例 public class NotifySample { 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("第" + id + "个线程准备休眠5分钟。"); try { synchronized(this) { this.wait(5*60*1000); } } catch(InterruptedException ex) { ex.printStackTrace(); } System.out.println("第" + id + "个线程被唤醒。"); } } class MyRunner implements Runnable { public int id = 0; public void run() { System.out.println("第" + id + "个线程准备休眠5分钟。"); try { synchronized(this) { this.wait(5*60*1000); } } catch(InterruptedException ex) { ex.printStackTrace(); } System.out.println("第" + id + "个线程被唤醒。"); } }
In the sample code, notifyTest() and notifyTest2() are "multi-object multi-threading". Although the thread in notifyTest2() implements the Runnable interface, when defining the Thread array in it, each element uses a new Runnable instance. notifyTest3() belongs to "single object multi-threading" because we only define one Runnable instance, and all threads will use this instance.
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 status switching
For a thread, from the time we create it to the end of the thread running, during this process, the status of the thread may be like this:
创建:已经有Thread实例了, 但是CPU还有为其分配资源和时间片。
就绪:线程已经获得了运行所需的所有资源,只等CPU进行时间调度。
运行:线程位于当前CPU时间片中,正在执行相关逻辑。
休眠:一般是调用Thread.sleep后的状态,这时线程依然持有运行所需的各种资源,但是不会被CPU调度。
挂起:一般是调用Thread.suspend后的状态,和休眠类似,CPU不会调度该线程,不同的是,这种状态下,线程会释放所有资源。
死亡:线程运行结束或者调用了Thread.stop方法。
下面我们来演示如何进行线程状态切换,首先我们会用到下面方法:
Thread()或者Thread(Runnable):构造线程。
Thread.start:启动线程。
Thread.sleep:将线程切换至休眠状态。
Thread.interrupt:中断线程的执行。
Thread.join:等待某线程结束。
Thread.yield:剥夺线程在CPU上的执行时间片,等待下一次调度。
Object.wait:将Object上所有线程锁定,直到notify方法才继续运行。
Object.notify:随机唤醒Object上的1个线程。
Object.notifyAll:唤醒Object上的所有线程。
下面,就是演示时间啦!!!
线程等待与唤醒
这里主要使用Object.wait和Object.notify方法,请参见上面的notify实例。需要注意的是,wait和notify都必须针对同一个对象,当我们使用实现Runnable接口的方式来创建线程时,应该是在Runnable对象而非Thread对象上使用这两个方法。
线程的休眠与唤醒
Thread.sleep实例 public class SleepSample { 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.currentThread().getName() + "将要休眠5分钟。"); try { Thread.sleep(5*60*1000); } catch(InterruptedException ex) { System.out.println("线程 " + Thread.currentThread().getName() + "休眠被中断。"); } System.out.println("线程 " + Thread.currentThread().getName() + "休眠结束。"); } }; thread.setDaemon(true); thread.start(); Thread.sleep(500); thread.interrupt(); } }
线程在休眠过程中,我们可以使用Thread.interrupt将其唤醒,这时线程会抛出InterruptedException。
线程的终止
虽然有Thread.stop方法,但该方法是不被推荐使用的,我们可以利用上面休眠与唤醒的机制,让线程在处理IterruptedException时,结束线程。
Thread.interrupt示例 public class StopThreadSample { 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("线程运行中。"); try { Thread.sleep(1*60*1000); } catch(InterruptedException ex) { System.out.println("线程中断,结束线程"); return; } System.out.println("线程正常结束。"); } }; thread.start(); Thread.sleep(500); thread.interrupt(); } }
线程的同步等待
当我们在主线程中创建了10个子线程,然后我们期望10个子线程全部结束后,主线程在执行接下来的逻辑,这时,就该Thread.join登场了。
Thread.join示例 public class JoinSample { 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("线程在运行。"); Thread.sleep(1000); } } catch(InterruptedException ex) { ex.printStackTrace(); } } }; thread.setDaemon(true); thread.start(); Thread.sleep(1000); thread.join(); System.out.println("主线程正常结束。"); } }
我们可以试着将thread.join();注释或者删除,再次运行程序,就可以发现不同了。
线程间通信
我们知道,一个进程下面的所有线程是共享内存空间的,那么我们如何在不同的线程之间传递消息呢?在回顾 Java I/O时,我们谈到了PipedStream和PipedReader,这里,就是它们发挥作用的地方了。
下面的两个示例,功能完全一样,不同的是一个使用Stream,一个使用Reader/Writer。
PipeInputStream/PipedOutpueStream 示例 public static void communicationTest() throws IOException, InterruptedException { final PipedOutputStream pos = new PipedOutputStream(); final PipedInputStream pis = new PipedInputStream(pos); 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(); }
PipedReader/PipedWriter 示例 private static void communicationTest2() throws InterruptedException, IOException { final PipedWriter pw = new PipedWriter(); final PipedReader pr = new PipedReader(pw); final BufferedWriter bw = new BufferedWriter(pw); final BufferedReader br = new BufferedReader(pr); 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(); }
以上就是基于Java回顾之多线程详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!