What is thread-safe for classes
Since today’s topic is thread safety, what is thread safety?
In fact, there is no clear definition of thread safety, Doug Lea gave the definition of the use of multithreading class, but how multithreading uses and schedules this class, this class always represents the correct behavior, this class is thread safe.
The thread-safe behavior of the class is:
- Atomicity of operations
- Visibility of memory
Thread insecurity occurs when state is shared between multiple threads without proper synchronization.
1. Atomicity of operations
Atomicity was covered in previous chapters, so I won’t go into detail. An operation is either uninterruptible or indivisible, and all operations succeed or fail.
2. Memory visibility
Multiple threads of the same variable (referred to as: Shared variables), but the multiple threads can be assigned to run multiple processors, then the compiler will optimize the code, when a thread to deal with the variable, multiple processors will be variable from a copy of main memory are stored in his memory, wait for after the operation, to assign back into main memory.
If threads T1 and T2 are assigned to different processors, changes to variable A by T1 and T2 will not be visible to each other. If T1 assigns A value to A, and t2 assigns A new value, The t2 operation overwrites the T1 operation, resulting in unexpected results. Therefore, variables need to be visible (changes made by one thread to the value of a shared variable can be seen by other threads in a timely manner).
Multithreaded operations share variables to implement visibility procedures The MEMORY model of the JVM is as follows:
Therefore, if we want to be thread-safe, we need to make sure that all of our threads read the correct values of shared variables, which is to keep memory visible.
Ways to make classes thread-safe
2.1. Stack closure
Those of you who know the JVM know that every method defined in Java is stored in the Java method stack, and each method is a stack frame. Stack enclosing means that variables are declared inside the method. So these variables are closed on the stack. (To simplify, define local variables)
Code:
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: Stack closed */ public class WorkTask {// unsafe private String name; Public Integer Call () {// The member variables inside the method are safe, String name ="safe"; this.name = name; int sleepTime = new Random().nextInt(1000); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } return sleepTime; }}Copy the code
2.2. Stateless
It’s an object that has no instance variables, no specific fields, can’t hold data, it’s an immutable class. For example, the DAO layer in our MVC pattern. Only methods are defined, no field variables are defined.
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: No variables, thread-safe */ public class WorkTask {public Integer Call () {String name ="safe"; int sleepTime = new Random().nextInt(1000); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } return sleepTime; }}Copy the code
2.3. Make classes immutable
In addition to making a class stateless, but most of the time, our classes are fields, stateful, so we need to make them safe by making them immutable. There are two ways
(1), add final keyword, but add final, it should be noted that if the member variable is an object, the corresponding class of the object should also be immutable, so as to ensure that the whole class is immutable.
code:
package com.xiangxue.ch7.safeclass; /** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @Description: Public class ImmutableFinalRef {// Since a and B are final variables, they are safe. private final int a; private final int b; // We can't guarantee thread-safety because user is a reference type, and fields in the user object can be changed. private final User user; public ImmutableFinalRef(int a, int b) { super(); this.a = a; this.b = b; this.user = new User(2); } public int getA() { return a; } public int getB() { return b; } public User getUser() { return user; } // Change all fields inside User to final to ensure thread-safe class changes. public static class User{ private int age; public User(int age) { super(); this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }} public static void main(String[] args) {ImmutableFinalRef = new ImmutableFinalRef(12,23); User u = ref.getUser(); }}Copy the code
(2) There is no place to modify a member variable at all, and the member variable is not returned by the method
Code:
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: ImmutetableToo {// Public class ImmutetableToo {// Changing the class is also thread-safe because there is no place to fetch or modify it. private List<Integer> list = new ArrayList<>(3); public ImmutetableToo() { list.add(1); list.add(2); list.add(3); } public boolean isContains(int i) { return list.contains(i); }}Copy the code
Java development advice: For a class, all member variables should be private, and if possible, all member variables should have the final keyword. This ensures thread safety.
2.4, volatile
The volatile keyword guarantees class visibility and is best suited for scenarios where one thread writes and multiple threads read.
2.5. Locking and CAS
Well, there’s no explanation for that…
2.6. Secure release
The member variables held in the class, especially the reference to the object, if the member object is not thread-safe, published by methods such as GET, will cause the data held by the member object itself to be incorrectly modified in multithreading, thus causing the whole class thread unsafe problem.
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: UnsafePublish */ public class UnsafePublish {// either replace with a thread-safe container // or provide a copy when published, Deep copy Private List<Integer> List = new ArrayList<>(3); public UnsafePublish() { list.add(1); list.add(2); list.add(3); } // List is not safe to publish, thread is not safe. public List<Integer> getList() { return list; } / / is also safe, added a lock -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- public synchronized int getList (int index) {return list. Get (index); } public synchronized void set(int index,int val) { list.set(index,val); }}Copy the code
2.7, TheadLocal
An instance of ThreadLocal represents a thread-local variable, and each thread only sees its own value, unaware that it exists in any other thread.
Third, some problems caused by thread safety
3.1, the dead lock
A deadlock is a phenomenon in which two or more processes are blocked during execution, either by competing for resources or by communicating with each other, and cannot proceed without external action.
When a deadlock occurs, the number of competing resources must be greater than one and less than or equal to the number of competing threads. When there is only one resource, a fierce competition will occur, but no deadlock will occur.
The root cause of deadlocks: The order in which locks are acquired is inconsistent.
Code:
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: Deadlock example */ public class NormalDeadLock {private static Object lockFirst = new Object(); Private static Object lockSecond = new Object(); // Take the first lock, Private static void fisrtToSecond() throws InterruptedException {String threadName = Thread.currentThread().getName(); synchronized (lockFirst) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (lockSecond) { System.out.println(threadName+" get second"); }}} // Take the second lock first, Private static void SecondToFisrt() throws InterruptedException {String threadName = Thread.currentThread().getName(); synchronized (lockFirst) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (lockSecond) { System.out.println(threadName+" get second"); }}} private static class extends Thread{private String name; public TestThread(String name) { this.name = name; } public void run(){ Thread.currentThread().setName(name); try { SecondToFisrt(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread.currentThread().setName("TestDeadLock"); TestThread testThread = new TestThread("SubTestThread"); testThread.start(); try { fisrtToSecond(); } catch (InterruptedException e) {e.printStackTrace(); }}}Copy the code
3.1.1 The way to find out whether a program is deadlocked
1. View the code logic
2. Older JDK versions can detect simple deadlocks directly by querying the application ID through JPS and the application lock holding status through jStack ID
Solution: Ensure the sequence of locking
3.1.2 Dynamic deadlock
Dynamic sequential deadlocks occur when an implementation locks in a certain order, but the order cannot be guaranteed because of an external call.
code:
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: Implementation of an insecure transfer action */ public class TrasnferAccount implements ITransfer {// If the transfer parameter is inconsistent, it may cause a deadlock. // a thread locks from and another thread passes to as from, and another thread passes from as to. The second thread locks to and then locks FROM. @override public void transfer(UserAccount from, UserAccount to, Throws InterruptedException {synchronized (from){system.out.println (thread.currentThread ().getName()) +" get"+from.getName()); Thread.sleep(100); Println (thread.currentThread ().getName() +" get"+to.getName()); synchronized (to){system.out.println (thread.currentThread ().getName() +" get"+to.getName()); from.flyMoney(amount); to.addMoney(amount); }}}}Copy the code
Solution:
- Through internal sorting, ensure the sequence of locking
- By trying to get the lock, you can do that.
3.2, active lock
For example, in our life, two people meet on a narrow road, avoid in one direction, then another direction, and so on and so on. So that both of them can’t get through.
Live lock is in the mechanism of trying to take the lock, the occurrence of multiple threads between each other, constantly take the lock, release the lock process.
Solution: Each thread sleeps the random number and staggers the lock picking time.
3.3. Thread hunger
Low priority threads always get no execution time.
3.4 performance and thinking
The goal of concurrency is to improve performance, but with the introduction of multithreading, there are actually additional overhead, such as coordination between threads, increased context switching, thread creation and destruction, thread scheduling, and so on. Overuse and improper use can cause multithreaded programs to be even lower than single-threaded programs.
Measures the performance of an application: service time, latency time, throughput, scalability, etc., where service time, latency time (how fast), throughput (a measure of processing power, how much work is done). How fast and how much, completely independent, even contradictory.
For server applications: how much (scalability, throughput) is more important than how fast, ensuring horizontal scaling before vertical scaling.
When we made the app:
- Make sure the program is correct first, and then increase the speed when it does not meet the requirements. (Golden Rule)
- Be sure to benchmark yourself against tests.
The serial part of an application is always there.
Amdahl’s law: 1/(F+(1-n)/N) F: must be serial part, the best result of the program is 1/F.
3.5 Factors affecting performance
3.5.1 Context switch
A CPU switch from one process or thread to another. A context switch takes 5,000 to 10,000 clock cycles, a few microseconds. During a context switch, the CPU stops processing the currently running program and saves the exact location of the current program for later execution. From this point of view, context switching is a bit like reading several books at the same time. We need to remember the current page number of each book as we switch back and forth.
Context switching is usually computationally intensive. In other words, it requires considerable processor time. Therefore, context switching means consuming a lot of CPU time for the system, in fact, it may be the most time consuming operation in the operating system.
3.5.2 Memory synchronization
It usually refers to locking. For locking, additional instructions need to be added, and these instructions need to refresh the cache and so on.
3.5.3, blocking
Causes the thread to hang. Hang in the operating system can be defined as the process of temporary were heading out of the memory process, the machine’s resources are limited, in the case of insufficient resources, the operating system to reasonable arrangement of program in memory, some process is temporarily out of memory, when conditions permit, will be back again by the operating system memory, Re-enter the ready state, where the system does not perform any action for more than a certain period of time. Obviously this operation involves two additional context switches.
3.6. How to reduce lock competition
3.6.1. Reduce lock granularity
When a lock is used, the object protected by the lock is multiple. When these multiple objects are actually independent changes, it is better to use multiple locks to protect these objects. However, if you have a business method that holds multiple locks at the same time, take care to avoid deadlocks
code
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @Description: */ public class FinenessLock { public final Set<String> users = new HashSet<String>(); Public void synchronized addUser(String u) {// system.out.println ("test"); // users.add(u); } // Lock only on the operated object. If the method weight has other methods, other services will not be affected. public void addUser(String u) { System.out.println("test"); synchronized (users) { users.add(u); }}}Copy the code
Lock principle, can lock objects do not lock methods, can lock methods do not lock classes. Do not use lock classes
3.6.2. Narrow the lock scope
The holding of the lock to achieve fast in and fast out, as far as possible to shorten the time of holding by the lock. Remove code that is not related to the lock from the lock, especially if it is time-consuming and may block
code:
/** * @Auther: DarkKing * @Date: 2019/5/12 12:09 * @description: Reduce the scope of locking */ public Class ReduceLock {private Map<String,String> matchMap = new HashMap<>(); Public Boolean isMatch(String name,String regexp) {synchronized(this){String key = "user."+name; String job = matchMap.get(key); if(job == null) { return false; }else { return Pattern.matches(regexp, job); Public Boolean isMatchReduce(String name,String regexp) {String key = "user."+name; String job ; synchronized(this) { job = matchMap.get(key); } if(job == null) { return false; }else { return Pattern.matches(regexp, job); }}}Copy the code
3.6.3. Avoid redundant reduction of lock scope
Lock coarsening – extending the scope of the lock – should be done when the statements between locks are so simple that they take longer to lock than to execute them.
3.6.4 lock segments
ConcurrrentHashMap is a typical lock fragment. The next article will analyze the ConcurrrentHashMap source code.
3.6.5. Replace the Exclusive Lock
Where business permits:
- Using read/write locks,
- Use the spin CAS
- Use the system’s concurrent container
This chapter focuses on understanding what thread safety is, how to implement thread-like safety, what problems thread safety may cause, and how to resolve and optimize performance bottlenecks. The next chapter introduces some concurrent containers in Java, as well as analyzing the source code.
Today send wave welfare ebook, everyone interested can download oh.