[Logic of Java Programming] Concurrency Basics

Basic Concepts of Threads

A thread represents a separate flow of execution, it has its own program execution counter, and it has its own stack.
There are two ways to create a thread in Java: one is to inherit Thread, the other is to implement the Runnable interface

public class HelloThread extends Thread{

    @Override
    public void run() {
        System.out.println("Thread name: " + Thread.currentThread().getName());
        System.out.println("Hello Thread");
    }

}

public class HelloRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("Thread name:" + Thread.currentThread().getName());
        System.out.println("Hello Runnable");
    }

}

public class Test {

    public static void main(String[] args) {
        HelloThread helloThread = new HelloThread();
        helloThread.start();

        Thread helloRunnable = new Thread(new HelloRunnable());
        helloRunnable.start();
    }
}

Although it is relatively simple to implement threads by inheriting Thread, only single inheritance is supported in Java, and each class has at most one parent class. At this time, threads can be implemented by implementing the java.lang.Runnable interface.

Basic properties and methods of threads

Thread has some basic properties and methods, including id, name, priority, status, whether it is a daemo thread, sleep method, yield method, join method, etc.

id and name

id is an incrementing integer, incremented by one each time a thread is created.
The default value for name is Thread- followed by a number.
The name can be specified in the constructor of Thread or set by the setName method

// 获取当前线程name
Thread.currentThread().getName()
// 获取当前线程id
Thread.currentThread().getId()

priority

Thread has a concept of priority, in Java, the priority is from 1 to 10, the default is 5

public final void setPriority(int newPriority);
public final int getPriority();

This priority will be mapped to the priority of the thread in the operating system, but because the operating system is different, not necessarily all 10 priorities, different priorities in Java may be mapped to the same priority in the operating system .

condition

Thread has a method for getting the state of the thread:

public State getState();

The return value type is Thread.State, which is an enumeration type

public enum State {
    // 没有调用start的线程
    NEW,
    // 调用start后线程在执行run方法且没有阻塞时。不过,RUNNABLE不代表CPU一定在执行该线程的代码,可能正在执行也可能在等待操作系统分配时间片
    RUNNABLE,
    // 表示线程被阻塞了
    BLOCKED, WAITING, TIMED_WAITING,
    // 线程运行结束后
    TERMINATED;
}

daemon thread

public final void setDaemon(boolean on);
public final boolean isDaemon();

As mentioned at the beginning, starting a thread will start a separate execution flow, and the whole program will only exit when all threads are finished, but the daemon thread is an exception. When the rest of the whole program is daemon thread, the program will will exit.

What is the use of the daemon thread? It is generally an auxiliary thread of other threads. When the main thread it assists exits, it has no meaning to exist. When we run even the simplest program, Java will actually create multiple threads. In addition to the main thread, there is at least one thread responsible for garbage collection. This thread is the daemon thread. When the main program ends, the garbage collection program also quit

sleep

public static native void slepp(long millis) throws InterruptedException;

Calling this method will put the current thread to sleep for the specified time, in milliseconds.

During sleep, the thread will give out the CPU, but the sleep time is not necessarily the exact given number of milliseconds, and there will be a certain deviation.
During sleep, the thread can be interrupted, if interrupted, sleep will throw InterruptedException

yield

public static native void yield();

Call this method to tell the scheduler of the operating system: I am not in a hurry to use the CPU now, you can let other threads run first

join

In the initial usage example, it may be that HelloThread has not finished executing, but the main thread has finished executing. Thread has a join method that allows the thread calling join to wait for the thread to end.

public final void join() throws InterruptedException 

In the process of waiting for the thread to end, this wait may be interrupted, and if interrupted, an InterruptedException will be thrown

There is also a variant of the join method, which can limit the maximum time to wait, in milliseconds, if it is 0, it means infinite wait

public final synchronized void join(long millis) throws InterruptedException 

memory sharing

Each thread represents a separate execution flow, with its own program counter and its own stack.
But threads can share memory, they can access and operate on the same objects

public class ShareMemoryDemo {

    private static int shared = 0;
    private static void incrShared() {
        shared++;
    }
    static class ChildThread extends Thread {
        List<String> list;
        public ChildThread(List<String> list) {
            this.list = list;
        }
        @Override
        public void run() {
            incrShared();
            list.add(Thread.currentThread().getName());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        ChildThread t1 = new ChildThread(list);
        ChildThread t2 = new ChildThread(list);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(shared);
        System.out.println(list);
    }
}
  1. There are three execution flows in the sample code, one executes the main method, and the other two execute the run method of ChildThread
  2. Different execution flows can access and manipulate the same variables
  3. Different execution streams can execute the same program code
  4. When multiple execution flows execute the same program code, each execution flow has a separate stack, and the parameters and local variables in the method have their own copy

race condition

The so-called race condition means that when multiple threads access and operate the same object, the final execution result is related to the execution timing.

public class CounterThread extends Thread {
    private static int counter = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            counter++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Thread[] threads = new Thread[num];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new CounterThread();
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
        System.out.println(counter);
    }

}

The expected result is 10 million, but the actual execution will find that the result of each output is different, generally not 10 million.
Because counter++这个操作不是原子操作, it is divided into three steps
1. Take the current value of counter
2. Add 1 to the current value
3. Reassign the new value to counter

Two threads may execute the first step at the same time and get the same counter value, for example, both get 100. After the first thread finishes executing, the counter becomes 101, and after the second thread finishes executing, it is still 101.

Solutions:
1. Use the synchronized keyword
2. Use explicit locks
3. Use atomic variables

memory visibility

Multiple threads can share access to and operate on the same variable, but the modification of a shared variable by one thread may not be seen immediately by another thread, or even never seen

public class VisibilityDemo {

    private static boolean shutdown = false;
    static class HelloThread extends Thread {
        @Override
        public void run() {
            while(!shutdown) {
                // do something
            }
            System.out.println("exit hello");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new HelloThread().start();
        Thread.sleep(1000);
        shutdown = true;
        System.out.println("exit main");
    }
}

The expected result is that both threads exit, but when actually executed, it is likely that HelloThread will never exit. This is the memory visibility problem .
In a computer system, in addition to memory, data will also be cached in CPU registers and caches at all levels. When accessing a variable, it may be obtained directly from the register or CPU cache, not necessarily from memory. When modifying When a variable is written, it may be written to the cache first, and then updated to the memory synchronously later. Therefore, the modification of memory by one thread cannot be seen by another thread, a modification is not synchronized to memory in time, and the other thread does not read from memory at all.

Solution:
1. Use volatile keyword
2. Use synchronized keyword or display lock synchronization

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324607334&siteId=291194637
Recommended