Thread implementation, status and several common processing methods

Posted by tawevolution on Fri, 24 Dec 2021 00:20:46 +0100

Thread introduction

  • Threads are independent execution paths
  • When the program runs, even if it does not create its own thread, there will be multiple threads in the background, such as main thread and gc thread (garbage collection)
  • Main () is called the main thread, which is the entry of the system and is used to execute the whole program
  • In a process, if multiple threads are opened up, the operation of threads is scheduled by the scheduler. The scheduler is closely related to the operating system, and the sequence can not be interfered by human beings
  • When operating on the same resource, there will be a problem of resource grabbing, and concurrency control needs to be added
  • Threads will bring additional overhead, such as cpu scheduling time and concurrency control overhead.
  • Each thread interacts in its own working memory. Improper memory control will cause data inconsistency

Thread implementation

How?

  • The custom Thread class inherits the Thread class
  • Rewrite the = = run() = = method to write the thread execution body
  • Create a thread object and call the = = start() = = method to start the thread

There are two methods to create threads (3. Create threads through Callable and FutureTask, and 4. Create threads through thread pool)

  • One is to declare a class as a subclass of Thread. This subclass should override the method Thread of the run class. You can then assign and start instances of subclasses.

  • class PrimeThread extends Thread {
             long minPrime;
             PrimeThread(long minPrime) {
                 this.minPrime = minPrime;
             }
    
             public void run() {
                 // compute primes larger than minPrime
                  . . .
             }
         }
     
    // Then, the following code will create a thread and start it to run: 
         PrimeThread p = new PrimeThread(143);
         p.start();
    
    
  • Another way to create a Thread is to declare the implementation class Runnable interface. That class then implements the run method. You can then assign an instance of the class, pass it as a parameter when creating the Thread, and start it.

  • // Runnable is recommended 
    class PrimeRun implements Runnable {
             long minPrime;
             PrimeRun(long minPrime) {
                 this.minPrime = minPrime;
             }
    
             public void run() {
                 // compute primes larger than minPrime
                  . . .
             }
         }
     //Then, the following code will create a thread and start it to run: 
    
         PrimeRun p = new PrimeRun(143);
         new Thread(p).start();
     
    
  • Inherit Thread class

    • The subclass inherits the Thread class and has multithreading capability
    • Start thread, subclass object start()
    • Not recommended: avoid the limitation of OPP single inheritance
  • Implement Runnable interface

    • The implementation interface Runnable has multithreading capability
    • Start Thread: pass in the target object + Thread object stat()
    • Recommended: it avoids the limitation of single inheritance, is flexible and convenient, and is convenient for the same object to be used by multiple threads
// Simulated tortoise rabbit race
public class Race implements Runnable {
    // winner
    private static String winner;
    
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            
            // Simulated rabbit sleep
            if(Thread.currentThread().getName.equals("rabbit") && i%10 == 0) {
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            
            // Judge whether the game is over
            boolean flag = gameOver(i);
            // If the game is over, stop the program
            if(flag) {
                break;
            }
            
            
            System.out.println(Thread.currentThread().getName()+ "--->Run away" + i + ""Step");
        }
    }
    
    // Judge whether to complete the game
    private boolean gameOver(int step) {
        if (winner != null) {
            return true;
        }else {
            if (step >= 100) {
                winner = Thread.currentThread().getName();
           		System.out.println("winner is" + winner);
                return true;
            }
        }
        return false;
    }
    
    public static void main(Stirng[] args) {
        Race rece = new Race();
        
        new Thread(race, "rabbit").start();
        new Thread(race, "tortoise").start();
    }
    
    
}
  • Implement Callable interface (understand)

Thread state

  1. Create status
  2. Ready status
  3. running state
  4. Blocking state
  5. Death state
  • public static enum Thread.State
    extends Enum<Thread.State>
    

    Thread status. A thread can be in one of the following states:

    • NEW
      Threads that have not been started are in this state.
    • RUNNABLE
      The thread executing in the Java virtual machine is in this state.
    • BLOCKED
      Threads that are blocked waiting for a monitor lock are in this state.
    • WAITING
      A thread that is waiting for another thread to perform a specific action is in this state.
    • TIMED_WAITING
      The thread that is waiting for another thread to perform the action for the specified waiting time is in this state.
    • TERMINATED
      The exited thread is in this state.

    A thread can be in a state at a given point in time. These states are virtual machine states that do not reflect the state of any operating system threads.

setPriority

public final void setPriority(int newPriority)

Change the priority of this thread.

First, call the checkAccess method of this thread without parameters. This may cause a SecurityException to be thrown.

Otherwise, the priority of the thread is set to the specified small newPriority and the priority of the thread group of the maximum allowed thread.

Thread sleep:

  • Sleep specifies the number of milliseconds the current thread is blocking
  • Exception InterruptedException in sleep
  • When the sleep time reaches, the thread enters the ready state
  • sleep can simulate network delay, countdown, etc
  • Each object has a lock, and sleep does not release the lock

Thread comity:

  • Comity thread, which suspends the currently executing thread without blocking
  • Converts a thread from a running state to a ready state
  • Let the cpu reschedule, comity is not necessarily successful! Look at your mood
public class TestYield {
    
    public static void main(Stirng[] args) {
        MyYield myYield = new MyYield();
        
        new Thread(myYield,"a").start;
        new Thread(myYield,"b").start;
    }
    
}

class MyYield implements Tunnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "Thread starts execution");
        Thread.yield(); // Comity
        System.out.println(Thread.currentThread().getName() + "Thread stop execution");
    }
}

Join

  • Join merge threads. After this thread completes execution, other threads are executed and blocked by other threads.

  • // join method, imagine jumping in line
    public class TestJoin implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Here comes the thread to jump the queue");
            }
        }
        
        public static void main (String[] args) {
            // Start thread
            TestJoin testJoin = new TestJOin();
            Thread thread = new Thead(testJoin);
            thread.start();
            
            // Main thread
            for (int i = 0 ; i < 1000; i++) {
                if (i == 200) {
                    thread.join(); // Queue jumping main thread blocking
                }
                
                System.out.println("main" + i);
                
            }
            
        }
        
    }
    

Daemon thread

  • Threads are divided into user threads and daemon threads
  • The virtual machine must ensure that the user thread has completed execution
  • The virtual machine does not have to wait for the daemon thread to finish executing
  • Such as: background recording operation log, monitoring memory, garbage collection waiting
  • When code running in a Thread creates a new Thread object, the priority of the new Thread is initially set to be equal to the priority of the creating Thread, and it is a daemon if and only if the creating Thread is a daemon.

    When the Java virtual machine starts, there is usually a non daemon thread (usually calling the method named main of some specified classes). The Java virtual machine will continue to execute the thread until any of the following occurs:

    • The exit method of the Runtime class has been called, and the security manager has allowed the exit operation.
    • All threads that are not daemon threads have died, whether returning to the run method from the call or throwing a run beyond the run method.
  • setDaemon

    public final void setDaemon(boolean on)
    

    Mark this thread as daemon Thread or user thread. When the only thread running is a daemon thread, the Java virtual machine exits.

    This method must be called before the thread starts.

    • parameter

      on - if true, mark this thread as a daemon

    • abnormal

      IllegalThreadStateException - if this thread is alive

      SecurityException - if checkAccess() Determines that the current thread cannot modify this thread

Thread synchronization

  • Because multiple threads of the same process share the same storage space, it brings convenience and access conflict. In order to ensure the correctness of data being accessed in the method, the lock mechanism synchronized is added during access. When one thread obtains the exclusive lock of the object and monopolizes resources, other threads must wait, and multiple threads operate the same resource

Release the lock after use The following problems exist:

  • A thread holding a lock will cause all other threads that need the lock to hang;
  • In multi thread competition, locking and releasing locks will lead to more context switching and scheduling delays, resulting in performance problems;
  • If a high priority thread waits for a low priority thread to release the lock, it will lead to priority inversion and performance problems

Concurrent

The same object is operated by multiple threads at the same time.

Unsafe cases

// Watching a small demo is not safe to buy tickets
public class UnsafaBuyTicket {
    BuyTicket buyTicket = new BuyTicket();
    
    new Thread(buyTicket,"User 1").start;
    new Thread(buyTicket,"User 2").start;
    new Thread(buyTicket,"User 3").start;
    
}

class BuyTicket implements Runnable {
    // Number of votes
    private int ticketNums = 10;
    // External stop mode
    boolean flag = true;
    
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    
    public void buy() {
        // Judge whether there are tickets
        if(ticketNums <= 0) {
            return;
        }    
        // Analog delay
        Thread.sleep(100);
        // Buy a ticket
        System.out.println(Thread.currentThread().getName() + "Get" + ticketNums --);       
        
    }
    
}
// Unsafe withdrawal two people go to the bank to withdraw money at the same time
public class UnsafeBank {
    
    public static void main(String[] args) {
        // account
        Account account = new Account(100,"passbook");
        
        Drawing user1 = new Drawing(account,50,"User 1");
        Drawing user2 = new Drawing(account,100,"User 2");
        
        user1.start();
        user2.start();
        
    }
}
// account
class Account {
    int money;
    String name;
    
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}


// Bank: simulated withdrawal
class Drawing extends Thread {
    Account account; //account
    // How much did you withdraw
    int drawingMoney;
    // How much money do you have now
    int nowMoney;
    
    public Drawing (Account account,int drawingMoney , String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    
    // Withdraw money
    @Override
    public void run() {
        // Judge whether there is enough money
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "The money is not enough");
            return;
        }
        
        // The occurrence of sleep amplification problem
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // Card balance = balance - get money
        account.money =  account.money - drawingMoney;
        
        // Money in hand
        nowMoney = nowMoney + drawingMoney;
        
        System.out.println(account.name + "The balance is:" + account.money);
        // Thread.currentThread().getName() = this.getName();
        System.out.println(this.getName() + "Money in hand" + nowMoney);
        
        
    }
    
}
// Thread unsafe collection
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

Synchronization method

  • Because we can use the private keyword to ensure that data objects can only be accessed by methods, we only need to propose a mechanism for methods. This mechanism is the synchronized keyword, which includes two uses: synchronized methods and synchronized blocks

    Synchronization method: publicsynchronizedvoid method(int args)

  • Synchronized methods control access to "objects". Each object corresponds to a lock. Each synchronized method must obtain the lock of the object calling the method before execution. Otherwise, the thread will block. Once the method is executed, it will monopolize the lock until the method returns. The blocked thread can obtain the lock and continue to execute

    Defect: declaring a large method synchronized will affect efficiency

Synchronization block

synchronized

  • Synchronization block: synchronized (Obj) {}
  • Obj calls it a synchronization monitor
    • Obj can be any object, but it is recommended to use shared resources as synchronization monitors
    • There is no need to specify a synchronization monitor in the synchronization method, because the synchronization monitor of the synchronization method is this, the object itself, or class
  • Synchronization monitor execution
    1. The first thread accesses, locks the synchronization monitor, and executes its code
    2. The second thread accesses and finds that the synchronization monitor is locked and cannot be accessed
    3. After the first thread is accessed, unlock the synchronization monitor
    4. The second thread accesses, finds that the synchronization monitor has no lock, and then locks and accesses

Lock

class A{
    private final ReentrantLock lock = new ReenTrantLock();
    public void m() {
        lock.lock();
        try{
            // Thread safe code;
        } finally{
            lock.unlock();
            // If there is an exception in the synchronization code, write unlock() to the finally statement block
        }
    }
}

synchronized vs Lock

  • Lock is an explicit lock (manually open and close the lock, don't forget to close the lock). Synchronized is an implicit lock, which is automatically released out of the scope
  • Lock only has code block lock, and synchronized has code block lock and method lock
  • Using Lock lock, the JVM will spend less time scheduling threads and perform better. And it has better extensibility (providing more subclasses)
  • Priority:
    • Lock > synchronize code block (it has entered the method body and allocated corresponding resources) > synchronize method (outside the method body)

Thread communication

  • Application scenario: producer and consumer issues
    • Suppose only one product can be stored in the warehouse, the producer puts the produced products into the warehouse, and the consumer takes the products from the warehouse for consumption
    • If there is no product in the warehouse, the producer will put the product into the warehouse, otherwise stop production and wait until the product in the warehouse is taken away by the consumer
    • If there is a product in the warehouse, the consumer can take the product away for consumption, otherwise stop consumption and wait until the product is put in the warehouse again
  • analysis
  • This is a thread synchronization problem. Producers and consumers share the same resource, and producers and consumers are interdependent and conditional on each other
    • For producers, they should inform consumers to wait before producing products, and after producing products, they need to inform consumers to consume immediately
    • For consumers, after consumption, they should inform producers that they have finished consumption and need to produce new products for consumption
    • In the producer consumer problem, synchronized is not enough
      • synchronized prevents concurrent updates to the same shared resource, enabling synchronization
      • synchronized cannot be used for message passing (Communication) between different threads

Solution 1: pipe pass method

// Test: producer consumer model -- > using buffer solution: pipe process method

// Producer, consumer, product, buffer
public static void main(String[] args) {
    SynContainer synContainer = new SynContainer();
    
    new Productor(synContainer).start();
    new Consumer(synContainer).start();
    
}

// producer
class Productor extends Thread {
    SynContainer synContainer;
    
    public productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
    
    // production
    @Override
    public void run() {
        for (int i = 0; i<100; i++) {
            synContainer.push(new Chicken(i));
            System.out.println("Produced"+i+""One product");
        }
    }
    
}

// consumer
class Consumer extends Thread {
    SynContainer synContainer;
    
    public productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
    
    // consumption
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Consumer--->"+ synContainer.pop.id + "Piece product");
        }
    }
    
}

// product
class Chicken {
    
}

// buffer
class SynContainer {
    
    // A container size is required
    Chicken[] chickens = new Chicken[10];
    // Container counter
    int count = 0;
    
    // The producer puts in the product
    public synchronized void(Chicken chicken) {
        // If the container is full, it needs to wait for consumers to consume
        if(count == chickens.length) {
            // Inform consumers to consume and producers to wait
        }
        
        // If it is not full, we need to throw in the product
        chickens[counts] = chicken;
        counts ++;
        
        // Consumers can be informed of consumption
        this.notifyAll();
    }
    
    
    // Consumer products
    public synchronized Chicken pop() {
        // Judge whether it can be consumed
        if(count == 0) {
            // Wait for producers to produce, consumers to wait
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // If you can consume
        count --;
        Chicken chicken = chickensp[count];
        
        // After consumption, inform the producer of production
        this.notifyAll();
        return chicken;
        
    }
    
    
}

Solution 2: signal lamp method

// Test producer consumer problem 2: signal lamp method, flag bit solution

public class TestPc2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// Producer -- > actor
class Player extends Thread {
    TV tv;
    public player(TV tv) {
        this.tv = tv;
    }
    @Override
    public void run() {
        for (int i = 0; i<20; i++) {
            if (i%2 == 0){
            	this.tv.play("Happy camp playing");
        	} else {
                this.tv.play("Jitter: tiktok: record the good life")
            }
        }
        
    }
    
    
}

// Consumer -- > audience
class Watcher extends Thread {
    TV tv;
    public player(TV tv) {
        this.tv = tv;
    }
    @Override
    public void run() {
        for (int i = 0; i<20; i++) {
            tv.watch();
        }
        
    }
    
}
// Products -- > Programs
class TV {
    // The actors performed and the audience waited
    // The audience watched and the actors waited
    String voice; // A performance
    boolean flag = true;
    
    // perform
    public synchronized void play(String voice) {
        
        if(!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("The actors performed:" + voice);
        // Inform the audience to watch
        this.notifyAll();//Notification wake up
        this.voice = voice;
        
        this.flag = !this.flag;
    }
    
    // watch
    public synchronized void watch() {
        if(flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Watched:" + voice);
       	// Inform the actors to perform
        this.notifyAll();
        this.flag = !this.flag;
    }
    
    
    
}


Use thread pool

  • Background: resources that are often created and destroyed and used heavily, such as thread pools in concurrency, have a great impact on performance.
  • Idea: create many threads in advance, put them into the thread pool, get them directly when using them, and put them back into the pool after use. It can avoid frequent creation, destruction and reuse.
  • Benefits:
    • Improved response time (reduced time to create new threads)
    • Reduce resource consumption (reuse threads in the thread pool and do not need to be created every time)
    • Easy thread management
      • corePoolSize: the size of the core pool
      • maximumPoolSize: maximum number of threads
      • keepAliveTime: how long will the thread terminate when it has no task

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads)

Create a thread pool that reuses a fixed number of threads running from a shared unbounded queue. At any time, up to nThreads threads will be actively processing tasks. If all threads commit other tasks while they are active, they wait in the queue until the thread is available. If any thread terminates due to a failure during execution before shutdown, the new thread will occupy it if subsequent tasks need to be performed. The thread in the pool will exist until it is explicitly shutdown .

  • parameter

    nThreads - number of threads in the pool

  • result

    Newly created thread pool

  • abnormal

    IllegalArgumentException - if nthreads < = 0

  • public class TestPool {
        public static void main(String[] args) {
           	// 1. Create service, create thread pool
            // The newFixedThreadPool parameter is thread pool hero
            ExecutorService servie = Executors.newFixedThreadPool(10);
            
            // implement
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            
            // Close connection
            service.shutdown();
            
        }
    }
    
    
    class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }