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
- Create status
- Ready status
- running state
- Blocking state
- 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.
- NEW
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
- The first thread accesses, locks the synchronization monitor, and executes its code
- The second thread accesses and finds that the synchronization monitor is locked and cannot be accessed
- After the first thread is accessed, unlock the synchronization monitor
- 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); } }