1. Thread safety concept
Concept: When multi-threaded concurrent access, the program may not get the correct data results, that is, the thread is not safe.
- Synchronous and asynchronous: for example, you and Zhao four together to do the same set of mathematical simulation, asynchronous is you will copy a set of papers, and then a person to do a half set of questions, and finally spell an answer, synchronous is you do a few, will be thrown to zhao four, zhao four do a few and then throw to you, cycle alternate, until the paper is done.
- Asynchronous: non-interference, high resource utilization, because the whole process no one in the waiting state for a long time, but not safe, because two people may brush again.
- Synchronization: it is safe, it will not brush again, but the efficiency is relatively lower, but sometimes, we have to sacrifice a little efficiency factor, to improve the safety factor.
- Thread safety problems are mostly caused by asynchrony:
- For example, Zhao Si’s account has a balance of 10,000 yuan
- Today someone promised to transfer 5000 yuan to him (Thread A)
- He also wants to transfer 2000 yuan to someone else (Thread B)
- Thread A executes, 10000 + 5000 = 15000 but enters the wait state before updating the account balance
- Thread B executes, 10000-2000 = 8000, but enters the wait state before updating the account balance
- Thread A continues, updating the account balance to 15000
- Thread B continues, updating the account balance to 8000
- Finally, after zhao Si’s operation, the balance was 8000 yuan
- Thread safety recommendations:
- Try to use synchronous operations on shared resources, such as code locking.
- Try to use atomic operations on shared resources, such as atomic classes provided by the JDK.
- Use less shared resources and more thread-private resources, such as ThreadLocal.
Source: / javase – advanced /
- src:
c.y.thread.sync.TicketSellTest
/ * * *@author yap
*/
public class TicketSellTest {
private static class Ticket implements Runnable {
private int ticketNo;
@SneakyThrows
@Override
public void run(a) {
while (true) {
TimeUnit.SECONDS.sleep(1L); sellTicket(); }}private void sellTicket(a) {
int maxNo = 100;
if (ticketNo < maxNo) {
ticketNo++;
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "Selling tickets:"+ ticketNo); }}}@Test
public void sellTicket(a) {
Ticket ticket = new Ticket();
new Thread(ticket, "thread-A").start();
new Thread(ticket, "thread-B").start();
}
@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code
2. synchronized
Concept: The keyword synchronized can isolate code for thread synchronization. The code in the synchronization isolation zone can only be queued by all threads, sacrificing efficiency to ensure data security.
- The essence of synchronized is unfair locking, at the bytecode instruction level:
- use
monitorenter
Commands to enter and leave the quarantine area, that is, to acquire the lock. - use
monitorexit
The command is to leave the quarantine area and release the lock.
- use
- Usage:
- You can add it in the method signature
synchronized
Modify to lock the entire method. - You can use
Synchronized {}
Synchronized code blocks lock parts of code. - In addition to String, Integer, and Long, these can be used as synchronized lock types.
- For lock instances, it is recommended to fill in final, because synchronization will disappear immediately if the lock instance is changed midway through the run.
- Synchronization occurs only when multiple threads use the same lock.
- You can add it in the method signature
- Rule: Keep granularity to a minimum, keep quantity to a minimum, and lock only where there is shared data.
synchronized
Visibility can also be guaranteed, but instruction reordering cannot be prohibited.
Source: / javase – advanced /
- src:
c.y.thread.sync.TicketSellProTest
Copy the code
2.1 Lock type of synchronization method
Concept:
- Synchronized modifies member methods with this lock.
- Synchronized modifies static methods using the bytecode lock of the current class.
Source: / javase – advanced /
- src:
c.y.thread.sync.LockTypeTest
/ * * *@author yap
*/
public class TicketSellProTest {
private static class Ticket implements Runnable {
private int ticketNo;
@SneakyThrows
@Override
public void run(a) {
while (true) {
TimeUnit.SECONDS.sleep(1L); sellTicket(); }}private /*synchronized*/ void sellTicket(a) {
int maxNo = 100;
synchronized (this) {
if (ticketNo < maxNo) {
ticketNo++;
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "Selling tickets:"+ ticketNo); }}}}@Test
public void sellTicket(a) {
Ticket ticket = new Ticket();
new Thread(ticket, "thread-A").start();
new Thread(ticket, "thread-B").start();
}
@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code
2.2 Reentrancy of locks
Synchronized methodA() can be called in synchronized methodA() because the lock is claimed by the same thread, allowing reentrant of the synchronized code area.
- Lock reentrant process: If a lock is acquired for 5 times due to reentrant, the corresponding 5 reentrant locks must be released, so the lock reentrant times must be recorded:
- In hotspot implementations, adding a non-OS Lock generates an LR (Lock Record) in the thread stack, backing up the Lock instance’s Mark-Word information (basically the instance’s Hashcode) because the Lock information overwrites it.
- For each re-entry, an empty LR will be pushed again, and for five re-entries, five empty LRS will be pushed.
- Every time the lock is released, one empty LR is popped up. If all five empty LRS are popped up, it means that all the re-entry locks are released.
- Finally, release the last LR with backup information (restore backup information before release) to complete the synchronization operation.
- OS locks can also be reentrant, but the number of reentrants is not recorded by LR, but by an underlying variable.
Source: / javase – advanced /
- src:
c.y.thread.sync.ReentryTest
/**yap
* @author JoeZhou
*/
public class ReentryTest {
private synchronized void methodA(a) {
System.out.println("methodA...");
// Found to be the same thread, allowing reentry
methodB();
}
private synchronized void methodB(a) {
System.out.println("methodB...");
}
@Test
public void reentry(a) {
newReentryTest().methodA(); }}Copy the code
2.3 Releasing locks Abnormally
Concept: Synchronized code in the quarantine releases the lock if an exception breaks, and other threads waiting to enter can acquire the lock and enter the synchronized code.
Source: / javase – advanced /
- src:
c.y.thread.sync.ExceptionTest
/ * * *@author yap
*/
public class ExceptionTest {
private static class ExceptionDemo implements Runnable {
private int count;
@Override
public synchronized void run(a) {
while (true) {
System.out.println(Thread.currentThread().getName() + ":" + count);
try {
TimeUnit.SECONDS.sleep(1L);
if (count++ == 3) {
throw newArithmeticException(); }}catch(InterruptedException e) { e.printStackTrace(); }}}}@Test
public void exception(a) {
ExceptionDemo exceptionDemo = new ExceptionDemo();
new Thread(exceptionDemo, "threadA").start();
new Thread(exceptionDemo, "threadB").start();
}
@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code
2.4 a deadlock
Concept:
- Assuming that when you eat, I have A stick, you have A stick, I need you to give me add up to A pair of, I eat, you need me to add up to A pair of you, you have A meal, this time will be deadlocked, A deadlock occurs, the thread is the same, A thread holds A lock B, B thread holds A lock, the two who also refused to release the lock, A deadlock occurs.
- We should actively avoid deadlock. We should arrange locks according to the hashcode of the lock. If the hashcode is large, lock first, and if the hashcode is small, lock later.
Source: / javase – advanced /
- src:
c.y.thread.sync.DeadLockTest
/ * * *@author yap
*/
public class DeadLockTest {
private static class DeadLockRunnable implements Runnable {
private final Object objA = new Object();
private final Object objB = new Object();
@SneakyThrows
@Override
public void run(a) {
String threadA = "threadA";
if (Thread.currentThread().getName().equals(threadA)) {
synchronized (objA) {
System.out.println("if: objA");
TimeUnit.SECONDS.sleep(1L);
synchronized (objB) {
System.out.println("if: objB"); }}}else {
synchronized (objB) {
System.out.println("else: objB");
TimeUnit.SECONDS.sleep(1L);
synchronized (objA) {
System.out.println("else: objA");
}
}
}
}
}
@Test
public void staticMethodLockType(a) {
Runnable runnable = new DeadLockRunnable();
new Thread(runnable, "threadA").start();
new Thread(runnable, "threadB").start();
}
@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code
2.5 DCL single case model
Concept:
- Hungry singletons are inherently thread-safe, and full singletons are inherently thread-unsafe.
- We can use the DCL (Double Check Lock) pattern to optimize the full singleton pattern to make it thread-safe.
- Volatile must be added to prevent instruction reordering, otherwise the following process may occur:
- Thread A enters quarantine, if passes, executes new instruction (new is not atomic instruction)
- Thread A allocates space for the instance and gets memory address 0x9527.
- Thread A assigns initial values: name=null, age=0, etc.
- (rearranged) called by thread A
astore
Associated variable: instance=0x9527. - Instance is not null on the first if, and instance is returned directly. In this case, the attributes in instance are all initial values (error).
- Thread A assigned the real value: name=” age “, age=58, but it was too late, thread B already got the wrong data.
Source: / javase – advanced /
- src:
c.y.thread.sync.DclSingletonTest
/ * * *@author yap
*/
public class DclSingletonTest {
private static class DclSingleton {
/** * why use volatile? * /
private volatile static DclSingleton singleton;
private DclSingleton(a) {}public static DclSingleton getInstance(a) {
// for improve efficiency
if (singleton == null) {
synchronized (DclSingleton.class) {
if (singleton == null) {
singleton = newDclSingleton(); }}}returnsingleton; }}@Test
public void dclSingleton(a) {
for (int i = 0, j = 10; i < j; i++) {
newThread(() -> { System.out.println(DclSingleton.getInstance()); }).start(); }}}Copy the code
3. Atomic classes
Concept: in the guarantee thread safety means, in addition to the method of locking the comparative cost performance, we can use the Java and Java. Contracting out the util. Concurrent. The offer of atoms in the atomic classes to use CAS spin finish is more simple and efficient atomic operation.
Source: / javase – advanced /
- src:
c.y.thread.sync.AtomicOperationTest
/ * * *@author yap
*/
public class AtomicOperationTest {
private int num;
private AtomicInteger atomicNum = new AtomicInteger(0);
@Test
public /*synchronized*/ void nonAtomicOperation(a) {
for (int i = 0, j = 100; i < j; i++) {
new Thread(() -> {
for (int m = 0, n = 100; m < n; m++) { num++; } System.out.println(num); }).start(); }}@Test
public void atomicOperation(a) {
for (int i = 0, j = 100; i < j; i++) {
new Thread(() -> {
for (int m = 0, n = 100; m < n; m++) { atomicNum.incrementAndGet(); } System.out.println(atomicNum); }).start(); }}@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code
3.1 Basic types Atomic classes
Concept: Thread-safe manipulation of primitive types can use the corresponding atomic classes, such as AtomicBoolean, AtomicInteger, and AtomicLong.
- Structure:
AtomicInteger
For example:AtomicInteger()
: The default initial value is 0.AtomicInteger(int initialValue)
: Specifies the initial value.
- Methods:
AtomicInteger
For example:int get()
: Gets the current variable value.int incrementAndGet()
: Returns after the value increases by 1.int decrementAndGet()
: Returns after the value is reduced by 1.int addAndGet(int delta)
: since the increasedelta
Negative numbers are supported.int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)
: Customizes the calculation process- Param1: the initial right value in the functional interface.
- Param2: A functional interface that takes two int arguments and returns an int result.
Source: / javase – advanced /
- src:
c.y.thread.sync.BaseAtomicTest
/ * * *@author yap
*/
public class BaseAtomicTest {
@Test
public void atomicInteger(a) {
AtomicInteger num = new AtomicInteger(0);
System.out.println("num:" + num.get());
System.out.println("+ + num." + num.incrementAndGet());
System.out.println("--num:" + num.decrementAndGet());
System.out.println("Num +=6 then return:" + num.addAndGet(6));
System.out.println(Accumulate then return: + num.accumulateAndGet(5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right - 9) / 3; })); System.out.println("num:" + num.get());
}
@Test
public void atomicLong(a) {
AtomicLong num = new AtomicLong(0L);
System.out.println("num:" + num.get());
System.out.println("num++:" + num.getAndIncrement());
System.out.println("num--:" + num.getAndDecrement());
System.out.println("return then num-=6:" + num.getAndAdd(-6));
System.out.println("return then accumulate:" + num.getAndAccumulate(5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right - 9) / 3; })); System.out.println("num:" + num.get());
}
@Test
public void atomicBoolean(a) {
AtomicBoolean flag = new AtomicBoolean(false);
System.out.println("flag:" + flag.get());
System.out.println("change to true:" + flag.getAndSet(true));
System.out.println("flag:"+ flag.get()); }}Copy the code
3.2 Array atomic classes
Concept: If you want to perform atomic operations on arrays, you can use AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray.
- Structure:
AtomicIntegerArray
For example:AtomicIntegerArray()
: The default initial value is 0.AtomicIntegerArray(int initialValue)
: Specifies the initial value.
- Methods:
AtomicIntegerArray
For example:int get(int i)
: Gets elements by corner markers.int incrementAndGet(int i)
: Returns after increasing by 1.int decrementAndGet(int i)
: Returns after subtracting by 1.int addAndGet(int i, int delta)
: Autoincrement through corner markersdelta
Negative numbers are supported.int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)
: Customizes the calculation process- Param1: array horn.
- Param2: indicates the initial right value.
- Param3: A functional interface that takes two int arguments and returns an int result.
Source: / javase – advanced /
- src:
c.y.thread.sync.ArrayAtomicTest
/ * * *@author yap
*/
public class ArrayAtomicTest {
@Test
public void atomicIntegerArray(a) {
AtomicIntegerArray arr = new AtomicIntegerArray(new int[] {3.2});
System.out.println("Arr [0]." + arr.get(0));
System.out.println("+ + (arr [0]) :" + arr.incrementAndGet(0));
System.out.println("- (arr [0]) :" + arr.decrementAndGet(0));
System.out.println("(arr[0])+=6 then return:" + arr.addAndGet(0.6));
System.out.println(Accumulate then return: + arr.accumulateAndGet(0.5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right + 9) / 3; })); System.out.println("Arr [0]." + arr.get(0));
}
@Test
public void atomicLongArray(a) {
AtomicLongArray arr = new AtomicLongArray(new long[] {3.2});
System.out.println("Arr [0]." + arr.get(0));
System.out.println("(arr [0]) + + :" + arr.getAndIncrement(0));
System.out.println("(arr[0])--:" + arr.getAndDecrement(0));
System.out.println("Return then (arr[0])-=6: + arr.getAndAdd(0, -6));
System.out.println("return then accumulate:" + arr.getAndAccumulate(0.5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right + 9) / 3; })); System.out.println("Arr [0]." + arr.get(0));
}
@Test
public void atomicReferenceArray(a) {
AtomicReferenceArray<String> arr = new AtomicReferenceArray<>(new String[]{"3"."2"});
System.out.println("Arr [0]." + arr.get(0));
System.out.println("update then return" + arr.updateAndGet(0, v -> v + "-"));
System.out.println("Arr [0]." + arr.get(0)); }}Copy the code
3.3 Upgrading atomic Classes
Concept: DoubleAdder and LongAdder optimize the atomic update performance of Double and Long, but they only have simple increment, add, and decrement methods. The bottom layer uses segmental locking technology, which can be more efficient in high concurrency. For example, if there are 1000 threads, divided into five segments, 200 threads are executed for each segment, and the five segments are summarized and returned.
- Structure:
LongAdder
For example:LongAdder()
: The default initial value is 0.
- Methods:
LongAdder
For example:void add(long x)
: since the increasex
Supports negative numbers.void increment()
: Increases by 1.void decrement()
: Minus 1.
Source: / javase – advanced /
- src:
c.y.thread.sync.AdderTest
/ * * *@author yap
*/
public class AdderTest {
@Test
public void longAdder(a) {
LongAdder num = new LongAdder();
System.out.println("current value: " + num);
num.add(5);
System.out.println("After + 5." + num);
num.increment();
System.out.println("After + 1." + num);
num.decrement();
System.out.println("After 1:" + num);
}
@Test
public void doubleAdder(a) {
DoubleAdder num = new DoubleAdder();
System.out.println("current value: " + num);
num.add(5);
System.out.println("After + 5."+ num); }}Copy the code
3.4 Reference Types Atomic classes
Concept: if you want to to atomic operation of properties of a class, you can use AtomicIntegerFieldUpdater, AtomicLongFieldUpdater or AtomicReferenceFieldUpdater.
- Considerations for using reference type atomic classes:
- Member attributes must be
volatile
Modifier, indicating that the property is immediately visible between threads. - Member attributes cannot be
private
.static
或final
Modification.
- Member attributes must be
- If you care about ABA in CAS, you can replace the following two:
AtomicMarkableReference
: Atomic reference type with version stamp. Version stamp is Boolean.AtomicStampedReference
: Atomic reference type with version stamp, version stamp of type int.
Source: / javase – advanced /
- src:
c.y.thread.sync.FieldUpdaterTest
/ * * *@author yap
*/
public class FieldUpdaterTest {
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Student implements Serializable {
volatile long id;
volatile String name;
volatile int age;
}
private Student student;
@Before
public void before(a) {
student = new Student(1L."zhao-si".58);
}
@Test
public void atomicLongFieldUpdater(a) {
AtomicLongFieldUpdater<Student> idUpdater =
AtomicLongFieldUpdater.newUpdater(
Student.class, "id");
System.out.println("+ + id." + idUpdater.incrementAndGet(student));
System.out.println("id:" + student.getId());
System.out.println("--id:" + idUpdater.decrementAndGet(student));
System.out.println("id:" + student.getId());
System.out.println("Id +=5 then return:"
+ idUpdater.addAndGet(student, 5));
System.out.println("id:" + student.getId());
System.out.println(Accumulate then return: +
idUpdater.accumulateAndGet(student, 5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right - 9) / 4;
}));
System.out.println("id:" + student.getId());
}
@Test
public void atomicIntegerFieldUpdater(a) {
AtomicIntegerFieldUpdater<Student> ageUpdater =
AtomicIntegerFieldUpdater.newUpdater(
Student.class, "age");
System.out.println("age++: " + ageUpdater.getAndIncrement(student));
System.out.println("age:" + student.getAge());
System.out.println("The age -." + ageUpdater.getAndDecrement(student));
System.out.println("age:" + student.getAge());
System.out.println("return then age+=5:"
+ ageUpdater.getAndAdd(student, 5));
System.out.println("age:" + student.getAge());
System.out.println("return then accumulate" +
ageUpdater.getAndAccumulate(student, 5, (left, right) -> {
System.out.print("left:" + left + "\t");
System.out.print("right:" + right + "\n");
return (left - 1) * (right - 9) / 4;
}));
System.out.println("age:" + student.getAge());
}
@Test
public void atomicReferenceFieldUpdater(a) {
AtomicReferenceFieldUpdater<Student, String> nameUpdater =
AtomicReferenceFieldUpdater.newUpdater(
Student.class, String.class, "name");
System.out.println("set name to fei-ji: " +
nameUpdater.getAndSet(student, "fei-ji"));
System.out.println("name:" + student.getName());
System.out.println("cas name to fei-ji: " +
nameUpdater.compareAndSet(student, "fei-ji"."da-pao"));
System.out.println("name:"+ student.getName()); }}Copy the code
4. ThreadLocal
Concept: ThreadLocal is called a local thread, and each ThreadLocal can store only one value.
- ThreadLocal maintains a ThreadLocalMap, which maintains a weak reference Entry:
- Key: this, the current ThreadLocal instance.
- Value: instance of the generic class specified when ThreadLocal is defined.
- Methods:
void set(T value)
: This is the keyvalue
Is stored in the ThreadLocalMap of the current thread and cannot be retrieved by other threads.T get()
: With this as the key, fetch the corresponding from the ThreadLocalMap of the current threadvalue
Value.void remove()
Remove from ThreadLocalMap of the current thread with this as keyvalue
Value.T setInitialValue()
ThreadLocal is not called when the current thread assigns a value or immediately after the current thread calls the remove methodget()
, returns the method value.
Source: / javase – advanced /
- src:
c.y.thread.sync.ThreadLocalTest
/ * * *@author yap
*/
public class ThreadLocalTest {
private static class Person {}private ThreadLocal<Person> threadLocal = new ThreadLocal<>();
@Test
public void threadLocal(a) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1L);
threadLocal.set(new Person());
System.out.println("set: over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2L);
// get null
System.out.println("get: " + threadLocal.get());
// prevent memory leaks
threadLocal.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@SneakyThrows
@After
public void after(a) { System.out.println(System.in.read()); }}Copy the code