thread

Implementing a threaded approach

  • Implement the Runable interface

public class RunnableThread implements Runnable {

    @Override

    public void run(a) {

        System.out.println('Implementing threads with implementing Runnable interface'); }}Copy the code

The first way to implement multithreading is by implementing the Runnable interface. As shown in the code, first implement the RunnableThread interface through the RunnableThread class, and then override the run() method. You can then implement multithreading by simply passing the instance of the run() method to the Thread class

  • Thread class inheritance

public class ExtendsThread extends Thread {

    @Override

    public void run(a) {

        System.out.println('Thread implementation with Thread class'); }}Copy the code

The second method inherits the Thread class, as shown in the code. Unlike the first method, it does not implement an interface. Instead, it inherits the Thread class and overwrites its run() method. You must be familiar with these two methods and use them frequently in your work.

  • Thread pools create threads

Create a thread from the thread pool. If we set the number of threads in the thread pool to 10, then 10 child threads will work for us. Next, we will parse the source code in the thread pool to see how the thread pool implements threads.


static class DefaultThreadFactory implements ThreadFactory { DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s ! =null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix ="pool-" +

            poolNumber.getAndIncrement() +

            "-thread-";

    }

 

    public Thread newThread(Runnable r) {

        Thread t = new Thread(group, r,

                    namePrefix + threadNumber.getAndIncrement(),

0);

        if (t.isDaemon())

            t.setDaemon(false);

        if(t.getPriority() ! = Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);returnt; }}Copy the code

For a thread pool, threads are essentially created through a thread factory. DefaultThreadFactory is used by default, which sets default values for threads created by the thread pool, such as the name of the thread, whether it is a daemon thread, and the priority of the thread. No matter how you set these properties, it still ends up creating threads through new Thread(), but the constructor here takes a few more arguments, so creating threads through a Thread pool is not a step away from the two basic ways of creating threads. Because it’s still essentially implemented through new Thread().

  • Callable creation thread with return value

class CallableTask implements Callable<Integer{

    @Override

    public Integer call(a) throws Exception {

        return newRandom().nextInt(); }}// Create a thread pool

ExecutorService service = Executors.newFixedThreadPool(10);

// Submit the task and return the result with a Future submission

Future<Integer> future = service.submit(new CallableTask());

// FutureTask

Task task = new Task();

FutureTask<Integer> futureTask = newFutureTask<Integer>(task); Future<? > submit = executorService.submit(futureTask);Copy the code

The fourth way to create a thread is through a Callable that has a return value. Runnable creates a thread that has no return value. Callable and its associated Future and FutureTask can return the result of a thread’s execution as a return value, as shown in the code. You implement the Callable interface and set its generic type to Integer, and then it returns a random number.

However, both Callable and FutureTask, like Runnable, are first and foremost a task that needs to be executed, rather than threads themselves. They can be executed in a thread pool, as shown in the code. Submit () puts the task into a thread pool, and the thread pool creates the thread. Whatever method is used, the thread ultimately executes the task. Implementing the Runnable interface and inheriting the Thread class.

Other Creation methods

  • The Timer Timer

class TimerThread extends Thread {

// Implement it

}

Copy the code

Analyze the

There is only one way to implement threads

Instead of focusing on why there is only one way to create threads, let’s say there are two ways to create threads, and other ways to create threads, such as Thread pools or timers, are just layers wrapped around new threads (). If we call these new ways, For example, when the JDK is updated, it may add several classes and rewrap new threads (), ostensibly a new way to implement threads. You’ll find that they are ultimately implemented based on the Runnable interface or inheriting the Thread class.

Why are these two ways essentially the same?


@Override

public void run(a) {

    if(target ! =null) { target.run(); }}Copy the code
  • Implementing the Runnable interface is better than implementing threads by inheriting the Thread class

Why is it better to implement the Runnable interface than to inherit the Thread class? What’s good about it?

First, consider the architecture of the code. In fact, there is only one run() method in Runnable, which defines what needs to be executed. In this case, Runnable is decoupled from the Thread class, which is responsible for starting the Thread and setting properties.

The second point is that it can improve performance in some cases by inheriting the Thread class. Each time a task is executed, a separate Thread needs to be created, and the Thread is destroyed at the end of its life cycle. If you want to execute the task, you must create another class that inherits the Thread class. If the execution of the content is less, such as just on the run () method simply print a line in the text, it brought about by the overhead is not large, compared to the whole thread was destroyed from the beginning to create has been completed, a series of operation than the run () method to print text itself bring spending far more, equivalent to pick up the sesame lost watermelon, the loss outweighs the gain If we implement the Runnable interface, we can pass the task directly to the thread pool and use fixed threads to complete the task without having to create and destroy a new thread every time, greatly reducing the performance overhead.

Third advantage is that the Java language does not support dual inheritance, if we once the class inherits the Thread class, then it further, there is no way to inherit the other classes, so that if the future we need to inherit this class on other classes implement some functions of expansion, there was no way to do it, is to limit code can expand sex in the future.

Above all, threads should be created by implementing the Runnable interface in a preferred way.

conclusion

There is only one way to create a Thread, and that is to construct a Thread class; There are two ways to implement thread execution: 1. Implement the Runnable interface, 2. Inherit the Thread class and override the Run method. The benefits of implementing Runnable interfaces are as follows: 1. Decoupling 2. Improving performance 3

Threads have six states

Just as a living thing goes from birth to maturity to death, threads have their own life cycle, and there are six states in the life cycle of threads in Java.

  • New (New created)

  • Runnable

  • Blocked.

  • Waiting for

  • Timed Waiting

  • Terminated

  1. new

New represents the state in which a Thread has been created but not started: when we create a Thread with New Thread(), the state is New if the Thread has not started running the start() method and therefore has not started executing the code in the run() method. Once a thread calls start(), its state changes from New to Runnable, which is the big box in the middle of the state transition diagram.

2. A Runnable can run

The Runable state corresponds to two states of the operating system thread state, Running and Ready. That is, a Java thread in the Runnable state may or may not be executing and is waiting for CPU resources to be allocated.

So, if a running thread is in a Runnable state, and it runs halfway through the task, the CPU executing the thread is scheduled to do something else, causing the thread to stop running temporarily, its state remains the same as Runnable, because it can be scheduled back to continue executing the task at any time.

3. Blocked status

Blocked, wait, and timed wait are all referred to as blocked state

  • Blocked by blocking

First, Blocked, the simplest of all, is Blocked. As you can see from the direction of the arrow, the only way to get Blocked from Runnable is to enter synchronized protected code without capturing the monitor lock. Whether you enter a synchronized block of code or a synchronized method, it’s the same.

To the right, when the Blocked thread grabs the Monitor lock, it returns from the Blocked state to the Runnable state.

  • Waiting Waiting

Waiting state. There are three possibilities for a thread to be in Waiting state.

1. There is no object.wait () method with Timeout;

2. Thread.join() method without Timeout parameter;

3. LockSupport. Park () method;

Blocked is specific to synchronized monitor locks, but there are many other locks in Java, such as ReentrantLock, that a thread will enter a Waiting state if it does not grab the lock when acquiring it. Because it essentially executes the locksupport.park () method, it enters a Waiting state. Similarly, object.wait () and thread.join () leave threads in a Waiting state;

Blocked is Waiting for another thread to release the monitor lock. Waiting is Waiting for a condition such as completion of the join thread, or notify()/notifyAll().

-Timed Waiting deadline

Waiting refers to the Timed Waiting state, the two states are very similar, the only difference is that there is no time limit. The Timed Waiting state will wait for timeout and be woken up automatically by the system or be woken up by the wake up signal before timeout.

The following conditions cause a thread to enter a Timed Waiting state

Thread.sleep(long millis);

2. Object. Wait (long timeout) method with time parameter set;

Thread.join(long millis);

4. The locksupport. parkNanos(Long Nanos) method and the locksupport. parkUntil(long deadline) method are set for the time parameter.

How do we get into these three states, and let’s look at how do we get from these three states to the next state.

1. To move from Blocked to Runnable requires the thread to acquire the Monitor lock, and going from Waiting to another state is special because Waiting is infinite, meaning that it will not actively recover after any time

2. The Runnable state can be entered only when locksupport.unpark () is executed, the join thread finishes, or is interrupted.

3. Why is it Blocked if another thread calls notify() or notifyAll() to wake it up? Because a thread waking up Waiting requires that it first hold the monitor lock if it calls notify() or notifyAll(), the thread waking up Waiting will be Blocked if it cannot get the lock. Until the thread that woke it up with notify()/notifyAll() completes and releases the monitor lock, it is not in its turn to snatch the lock, and if it does, it returns from Blocked to Runnable.

4. Similarly, notify() and notifyAll() are executed in the Timed Waiting state. The notify() and notifyAll() are executed in the Blocked state at first, and return to the Runnable state after the lock snatch succeeds.

5. Of course, for the Timed Waiting, if its timeout time expires and the thread that can directly obtain lock /join finishes/is interrupted/calls locksupport.unpark (), it will directly return to the Runnable state, without experiencing the Blocked state.

  • Terminated termination

At the last state, Terminated state, there are two ways to enter it.

  • The run() method completes, and the thread exits normally.

  • An exception that is not caught terminates the run() method, resulting in unexpected termination.

Thread safety

Thread insecurity is often encountered in real development

  • Error running result;

  • Publishing and initialization lead to thread safety issues;

  • Activity problem

Error running result


public class WrongResult {

 

   volatile static int i;

   public static void main(String[] args) throws InterruptedException {

       Runnable r = new Runnable() {

           @Override

           public void run(a) {

               for (int j = 0; j < 10000; j++) { i++; }}}; Thread thread1 =new Thread(r);

       thread1.start();

       Thread thread2 = newThread(r); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); }}Copy the code

CPU scheduling is allocated in time slices, and each thread can get a certain amount of time slices. However, if a thread runs out of time chips, it will suspend execution and release CPU resources to other threads, which can lead to thread-safety issues. For example, the i++ operation, which appears to be a single line of code, is not actually an atomic operation. It is performed in three main steps, and can be interrupted between each step

  • The first step is to read;

  • The second step is increase;

  • The third step is preservation

Thread 1 gets the result of I =1, and then does the I +1 operation, but the result of I +1 is not saved, so thread 1 is switched off, and the CPU starts thread 2, which is doing the same I ++ operation as thread 1, but now let’s think about it, What is its I? It’s actually the same thing as thread 1 getting I. Why is that? Because thread 1 does +1 to I, but the result is not saved, thread 2 does not see the modified result.

Let’s say thread 2 does +1 to I, and then switch to thread 1, and let thread 1 do what it didn’t do, save 2 from I +1, and then switch to thread 2 to save I =2, even though both threads did +1 to I, But the result is that we end up saving I =2 instead of I =3, which is a thread-safe problem and results in data error, which is the most typical thread-safe problem

Publishing and initialization lead to thread safety issues


public class WrongInit {

 

    private Map<Integer, String> students;

 

    public WrongInit(a) {

        new Thread(new Runnable() {

            @Override

            public void run(a) {

                students = new HashMap<>();

                students.put(1."Wang Xiaomei");

                students.put(2."Qian Er Bao");

                students.put(3."On Wednesday");

                students.put(4."Zhao four");

            }

        }).start();

     }

 

    public Map<Integer, String> getStudents(a) {

        return students;

    }

 

    public static void main(String[] args) throws InterruptedException {

        WrongInit multiThreadsError6 = new WrongInit();

        System.out.println(multiThreadsError6.getStudents().get(1)); }}Copy the code

Only when the thread runs all the assignment operations in the run() method, can all the information of the 4 students be regarded as initialized. However, we can see that in the main function Mian (), the information of student No. 1 is directly printed without any rest after initializing the WrongInit class. Imagine what happens to the program at this point? A null-pointer exception actually occurs.

Because the member variable of STUDENTS is initialized and assigned in the newly created thread in the constructor, and it takes a certain amount of time to start the thread, but our main function does not wait for the data directly, resulting in the null result of getStudents. This is the thread-safety problem of publishing or initializing at the wrong time or place.

Activity problem

The third kind of thread safety problem is called activity problem, the most typical of which are deadlock, live lock and starvation.

What is an activity problem? An activity problem is a problem in which the program never gets the final result of running. Compared to the previous two kinds of thread-safety problems, the consequences of an activity problem can be more serious, such as a deadlock that causes the program to completely freeze and cannot run down.