Home  >  Article  >  Java  >  A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads

A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads

php是最好的语言
php是最好的语言Original
2018-07-26 14:42:391259browse

What is the difference between thread and process? Answer: A process is an independent running environment, which can be regarded as a program or an application. A thread is a task executed in a process. Threads are a subset of processes. A process can have many threads, and each thread performs different tasks in parallel. Different processes use different memory spaces, and all threads share the same memory space.

1. The difference between threads and processes:

1) Thread: the execution unit responsible for program execution in the process
The thread itself relies on the program to run
The thread is the program The sequential control flow in can only use the resources and environment assigned to the program

2) Process: the executing program
A process contains at least one thread

3) Single thread: There is only one thread in the program. In fact, the main method is a main thread

4) Multi-threading: running multiple tasks in one program
The purpose is to better use CPU resources

2. Thread implementation

1. Inherit the Thread class: Defined in the java.lang package, inheriting the Thread class must override the run() method

class MyThread extends Thread{
    private static int num = 0;    public MyThread(){
        num++;
    }

    @Override    public void run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}

Create After you have your own thread class, you can create a thread object and then start the thread through the start() method. Note that the run() method is not called to start the thread. The run method only defines the tasks that need to be performed. If the run method is called, it is equivalent to executing the run method in the main thread. There is no difference from ordinary method calls. At this time, there is no A new thread will be created to perform the defined tasks.

public class Test {
    public static void main(String[] args)  {
        MyThread thread = new MyThread();
        thread.start();
    }
}class MyThread extends Thread{
    private static int num = 0;    public MyThread(){
        num++;
    }
    @Override    public void run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}

In the above code, by calling the start() method, a new thread will be created. In order to distinguish the difference between start() method call and run() method call, please look at the following example:

public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyThread thread1 = new MyThread("thread1");
        thread1.start();
        MyThread thread2 = new MyThread("thread2");
        thread2.run();
    }
}class MyThread extends Thread{
    private String name;    public MyThread(String name){        this.name = name;
    }

    @Override    public void run() {
        System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
    }
}

Running results:
A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threadsThe following conclusions can be drawn from the output results:

1) Thread1 and thread2 have different thread IDs, thread2 has the same ID as the main thread, which means that calling the run method does not create a new thread, but runs the run method directly in the main thread, which is different from ordinary method calls. Any difference;

2) Although the start method call of thread1 is called before the run method of thread2, the information related to the run method call of thread2 is output first, indicating that the process of creating a new thread will not block the main thread. subsequent execution.

2. Implement the Runnable interface
In addition to inheriting the Thread class when creating a thread in Java, you can also implement similar functions by implementing the Runnable interface. Implementing the Runnable interface must override its run method.
The following is an example:

public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
} 
class MyRunnable implements Runnable{
    public MyRunnable() {
    }

    @Override    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

The Chinese meaning of Runnable is "task". As the name suggests, by implementing the Runnable interface, we define a subtask and then hand over the subtask to Thread for execution. Note that this method must use Runnable as a parameter of the Thread class, and then use Thread's start method to create a new thread to perform the subtask. If you call Runnable's run method, no new thread will be created. This ordinary method call makes no difference.

In fact, if you look at the implementation source code of the Thread class, you will find that the Thread class implements the Runnable interface.

In Java, these two methods can be used to create threads to perform subtasks. Which method to choose depends on your own needs. Directly inheriting the Thread class may look simpler than implementing the Runnable interface, but since Java only allows single inheritance, if a custom class needs to inherit other classes, it can only choose to implement the Runnable interface.

3. Use ExecutorService, Callable, and Future to implement multi-threading with returned results.

You will learn about multi-threading later. For now, let’s know this method. Can.

ExecutorService, Callable, and Future objects actually belong to functional classes in the Executor framework. If you want to learn more about the Executor framework, you can visit http://www.javaeye.com/topic/366591, where the framework is explained in detail. The thread that returns the result is a new feature introduced in JDK1.5. It is indeed very practical. With this feature, I no longer need to go through a lot of trouble to get the return value, and even if it is implemented, it may be full of loopholes.

Tasks that can return a value must implement the Callable interface. Similarly, tasks that do not return a value must implement the Runnable interface. After executing the Callable task, you can obtain a Future object. By calling get on the object, you can obtain the Object returned by the Callable task. Combined with the thread pool interface ExecutorService, you can implement the legendary multi-threading that returns results. A complete multi-thread test example with returned results is provided below. It has been verified under JDK1.5 and can be used directly without any problems. The code is as follows:

/**
* 有返回值的线程 
*/ @SuppressWarnings("unchecked")  
public class Test {  public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程序开始运行----");  
   Date date1 = new Date();  

   int taskSize = 5;  
   // 创建一个线程池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 创建多个有返回值的任务  
   List list = new ArrayList();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 执行任务并获取Future对象  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 关闭线程池  
   pool.shutdown();  

   // 获取所有并发任务的运行结果  
   for (Future f : list) {  
    // 从Future对象上获取任务的返回值,并输出到控制台  
    System.out.println(">>>" + f.get().toString());  
   }  

   Date date2 = new Date();  
   System.out.println("----程序结束运行----,程序运行时间【" 
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  

class MyCallable implements Callable {  
private String taskNum;  

MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  

public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任务启动");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任务终止");  
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
}
}

Code description:
The Executors class in the above code provides a series of factory methods for creating thread pools, and the returned thread pools all implement the ExecutorService interface.
public static ExecutorService newFixedThreadPool(int nThreads)
Create a thread pool with a fixed number of threads.

public static ExecutorService newCachedThreadPool()
Create a cacheable thread pool, calling execute will reuse the previously constructed thread (if the thread is available). If no existing thread is available, a new thread is created and added to the pool. Terminate and remove from the cache those threads that have not been used for 60 seconds.

public static ExecutorService newSingleThreadExecutor()
Create a single-threaded Executor.

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Create a thread pool that supports scheduled and periodic task execution, which can be used to replace the Timer class in most cases.

ExecutoreService provides the submit() method, passing a Callable or Runnable and returning a Future. If the Executor background thread pool has not completed the calculation of Callable, this call returns the get() method of the Future object, which will block until the calculation is completed.

3. Thread status

Before formally learning the specific methods in the Thread class, let’s first understand the status of the thread. This will help us deal with the Thread class later. understanding of the method.

1) Create (new) state: A multi-threaded object is ready
2) Ready (runnable) state: The start() method is called, waiting for CPU to schedule
3) Run (running) state: Execute the run() method
4) Blocked state: Temporarily stop execution, and may give resources to other threads for use
5) Terminate (dead) state: The thread is destroyed
When a new thread needs to be started to perform a certain subtask, a thread is created. However, after the thread is created, it will not enter the ready state immediately, because the running of the thread requires some conditions (such as memory resources. In the previous blog post on JVM memory area division, we know that the program counter, Java stack, and local method stack are all private to the thread. , so a certain amount of memory space needs to be allocated for the thread). Only when all the conditions required for the thread to run are met, it will enter the ready state.

When a thread enters the ready state, it does not mean that the CPU execution time can be obtained immediately. Maybe the CPU is executing other things at this time, so it has to wait. When the CPU execution time is obtained, the thread actually enters the running state.

While the thread is in the running state, there may be multiple reasons why the current thread does not continue to run, such as the user actively letting the thread sleep (sleep for a certain period of time before re-executing), the user actively letting the thread wait, or Being blocked by a synchronization block corresponds to multiple states: time waiting (sleeping or waiting for a certain event), waiting (waiting to be awakened), and blocked (blocked).

When due to sudden interruption or subtask execution, the thread will be killed.

The following picture describes the state of a thread from creation to death:
A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threadsIn some tutorials, blocked, waiting, and time waiting are collectively referred to as blocking states. This is also possible, as long as But here I want to link the status of the thread with the method call in Java, so I separate the two states of waiting and time waiting.

Note: The difference between sleep and wait:

sleep is a method of Thread class, wait is a method defined in Object class.
Thread.sleep does not Causes changes in lock behavior. If the current thread owns the lock, Thread.sleep will not let the thread release the lock.
Thread.sleep and Object.wait will suspend the current thread. The OS will allocate execution time to other threads . The difference is that after calling wait, other threads need to execute notify/notifyAll to regain the CPU execution time.
Context switching

For single-core CPUs (for multi-core CPUs, this is understood (for one core), the CPU can only run one thread at a time. When running one thread, it switches to running another thread. This is called thread context switching (the same is true for processes).

Since the task of the current thread may not be completed, the running state of the thread needs to be saved when switching, so that the next time you switch back, you can continue to run in the previous state. To give a simple example: For example, a thread A is reading the contents of a file and is reading half of the file. At this time, thread A needs to be paused and transferred to execute thread B. When switching back to execute thread A again, we do not It is hoped that thread A will read from the beginning of the file again.

Therefore, it is necessary to record the running status of thread A. So what data will be recorded? Because you need to know which instruction the current thread has executed before next time, you need to record the value of the program counter. In addition, for example, if the thread is suspended while it is performing a certain calculation, then the execution will continue next time. Sometimes you need to know the value of the variable when it was suspended, so you need to record the status of the CPU register. So generally speaking, data such as the program counter and CPU register status will be recorded during the thread context switching process.

To put it simply: context switching for threads is actually the process of storing and restoring the CPU state, which enables thread execution to resume execution from the interruption point.

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

四、线程的常用方法

A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threadsA zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads1.静态方法

currentThread()方法

currentThread()方法可以返回代码段正在被哪个线程调用的信息。

public class Run1{    public static void main(String[] args){                 
    System.out.println(Thread.currentThread().getName());
    }
}

2.sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

sleep方法有两个重载版本:

sleep(long millis)     //参数为毫秒
sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:

public class Test {    private int i = 10;    private Object object = new Object();    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread1 = test.new MyThread();
        MyThread thread2 = test.new MyThread();
        thread1.start();
        thread2.start();
    } 

    class MyThread extends Thread{
        @Override        public void run() {
            synchronized (object) {
                i++;
                System.out.println("i:"+i);                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
                    Thread.currentThread().sleep(10000);
                } catch (InterruptedException e) {                    // TODO: handle exception
                }
                System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
                i++;
                System.out.println("i:"+i);
            }
        }
    }
}

A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

3.yield()方法

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

public class MyThread  extends Thread{
    @Override
    public void run() {        long beginTime=System.currentTimeMillis();        int count=0;        for (int i=0;i<50000000;i++){
            count=count+(i+1);            //Thread.yield();
        }        long endTime=System.currentTimeMillis();
        System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");
    }
}public class Run {
    public static void main(String[] args) {
        MyThread t= new MyThread();
        t.start();
    }
}

执行结果:
用时:3 毫秒!

如果将 //Thread.yield();的注释去掉,执行结果如下:
用时:16080 毫秒!

对象方法

4.start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

5.run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

5.getId()
getId()的作用是取得线程的唯一标识
代码:

public class Test {    public static void main(String[] args) {
        Thread t= Thread.currentThread();
        System.out.println(t.getName()+" "+t.getId());
    }
}

输出:
main 1

6.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态
代码:

public class MyThread  extends Thread{
    @Override
    public void run() {
        System.out.println("run="+this.isAlive());
    }
}public class RunTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        System.out.println("begin =="+myThread.isAlive());
        myThread.start();
        System.out.println("end =="+myThread.isAlive());
    }
}

程序运行结果:

begin ==falserun=trueend ==false

方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方
System.out.println(“end ==”+myThread.isAlive());

虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:

public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        System.out.println("begin =="+myThread.isAlive());
        myThread.start();
        Thread.sleep(1000);
        System.out.println("end =="+myThread.isAlive());
    }

则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。

7.join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

public class Thread4 extends Thread{
    public Thread4(String name) {        super(name);
    }    public void run() {        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + "  " + i);
        }
    }    public static void main(String[] args) throws InterruptedException {        // 启动子进程
        new Thread4("new thread").start();        for (int i = 0; i < 10; i++) {            if (i == 5) {
                Thread4 th = new Thread4("joined thread");
                th.start();
                th.join();
            }
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
}

执行结果:

main  0main  1main  2main  3main  4new thread  0new thread  1new thread  2new thread  3new thread  4joined thread  0joined thread  1joined thread  2joined thread  3joined thread  4main  5main  6main  7main  8main  9

由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:

main  0main  1main  2main  3main  4main  5main  6main  7main  8main  9new thread  0new thread  1new thread  2new thread  3new thread  4joined thread  0joined thread  1joined thread  2joined thread  3joined thread  4

8.其他方法
getName和setName
用来得到或者设置线程名称。

getPriority和setPriority
用来获取和设置线程优先级。

setDaemon和isDaemon
用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:
A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads

五、停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:

1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
2.使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
3.暂停线程

interrupt()方法

六、线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {            throw new IllegalArgumentException();
        }        if((g = getThreadGroup()) != null) {            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:

public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;

1.线程优先级特性:

1)继承性
比如A线程启动B线程,则B线程的优先级与A是一样的。
2)规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
3)随机性
优先级较高的线程不一定每一次都先执行完。

七、守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

八、同步与死锁

1.同步代码块
在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
同步代码块格式

synchronized(synchronized object){
Code blocks that need to be synchronized;
}
2. Synchronization method
In addition to code blocks that can be synchronized, methods can also be synchronized
Method synchronization format
1
synchronized void method name(){}

Related articles:

Java -- Multi-threading

Java Example-Thread Hanging

Related videos:

Java Multithreading and Concurrency Library Advanced Application Video Tutorial

The above is the detailed content of A zero-based understanding of the implementation of java threads and a collection of commonly used methods of threads. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn