Java Concurrent Programming

Posted by scliburn on Sat, 11 Dec 2021 02:08:38 +0100

Concurrency / high concurrency

tomcat default 150 concurrent connection (socket)

RT: corresponding time

QPS: throughput

Hardware

cpu, memory, disk, network

Software

Maximize the use of hardware resources

Number of threads, JVM memory size, network communication mechanism (BIO, NIO, AIO), disk IO

How to increase the number of concurrent threads on the server

What is a thread

Road Lane

Thread is the smallest scheduling unit for cpu execution

The number of parallel threads is determined by the cpu (number of cpu cores or number of cores * 2)

Concurrency and parallelism

Single core cpu also supports multithreading -- > cpu time slice switching

Concurrency: simultaneous connection requests

Parallelism: simultaneous processing of executed requests

Characteristics of multithreading

synchronization

You need to block and wait for other threads to complete processing

asynchronous

There is no need to block the current processing

parallel

Multithreaded simultaneous processing

Threads in Java

  • Runnable excuse
  • Thread class
  • Callable/Future with return value
public class ThreadDemo extends Thread {
  @Override
	public void run(){
  	//Instructions executed by threads
	}

  public static void main(String[] args){
    ThreadDemo t = new ThreadDemo();
    t.start();//Start thread
  }
}
public class CallableDemo implements Callable<String> {
  @Override
  public String call(){
    //call() is equivalent to run(), the instruction executed by the thread
    return null;
  }
  
  public static void main(String[] args){
    ExecutorService exe = Executors.newFixedThreadPool(1);
    CallableDemo c = new CallableDemo();
    Future<String> f = exe.submit(c);
    f.get();//The get() method is blocked
  }
}

How to use Java threads

  • Network request distribution
  • File import
  • SMS sending

Thread Foundation

Thread life cycle

Start -- > end of thread

Thread state

block

  • TIMED_WAITING
  • WAITING
  • BLOCKED
public class Demo {
  public static void main(String[] args){
    //Blocking status TIMED_WAITING
  	new Thread(() -> {
    	while(true){
      	try {
        	TimeUnit.SECONDS.sleep(100);
      	} catch (InterruptedException e){
        	e.printStackTrace();
      	}
    	}
  	}).start();
  
    //Blocking state WAITING
  	new Thread(() -> {
    	while(true) {
      	synchronized (Demo.class) {
          try {
            Demo.class.wait();
      		} catch (InterruptedException e){
        		e.printStackTrace();
      		}
        }
    	}
  	}).start();
    
    //Preemptive lock TIMED_WAITING
    new Thread(new BlockedDemo()).start();
    
    //Lock not preempted BLOCKED
    new Thread(new BlockedDemo()).start();
	}
  
  static class BlockedDemo extends Thread {
    @Override
    public void run() {
      while(true) {
      	synchronized (Demo.class) {
          try {
            TimeUnit.SECONDS.sleep(100);
      		} catch (InterruptedException e){
        		e.printStackTrace();
      		}
        }
    	}
    }
  }
}

jps view the Pid of the current thread

jstack pid view the stack information of pid

Thread states in Java: 6

  • new (thread initialization)

  • runnable (CPU is ready before OS scheduling)

  • waiting

    sleep(0);

    wait();

    join();

    LockSupport.parkUnitl();

    notify

    notifyAll

    unpark

  • timed_waiting

    sleep(long);

    wait(long);

    join(long);

    LockSupport.parkUnitl(xx);

    notify

    notifyAll

    unpark

  • Blocked (lock blocked)

  • Termination (end of thread run)

Thread states at the operating system level: 5

No new status

Thread startup

new Thread().start();//Start thread

new Thread().run();//Call instance method
  1. Thread. The start method calls the local method start0 to start the thread

  2. The JVM calls creation threads at different system levels through the local method start0

  3. The OS calls the JVM's thread Run method (OS calls CPU to execute run method instruction through CPU scheduling algorithm)

  4. JVM calls thread. In Java run

  5. After execution, the JVM destroys the thread

Thread termination

When does a thread terminate

End of run method execution

Thread. The stop method is forced to terminate and is not recommended

Send termination signal to notify termination, interrupt

Thread.currentThread().isInterrupted() indicates the interrupt flag, which is false by default

Thread.interrupt(); Set interrupt = true

Thread.interrupted(); Reset, resume interrupt = false

interrupt(); --> Communication between threads is realized through a shared variable

Set the value of a shared variable to true. volatile jint _interrupted;

Wake up blocked threads

Thread safety

problem

public static int count = 0;
public static incr() {
  try {
    Thread.sleep(1);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  count ++;
}
public static void main(String[] args) {
  for(int i = 0; i < 1000; i ++) {
    new Thread(APP::incr).start();
  }
  try {
    Thread.sleep(1000);
  } catch(InterruptedException e) {
    e.printStackTrace();
  }
}

The result is a random number less than or equal to 1000

Thread safety issues

  • Atomicity
  • Order
  • visibility

Data security problems caused by cpu time slice switching

The problem is count + +, which is not an atomic operation. It is compiled into three instructions:

  1. Load into register
  2. accumulation
  3. Write memory

Lock (Synchronized)

public class SynchronizedDemo {
  synchronized void demo1(){}//method
  
  synchronized static void demo2(){}//method
  
  Object o = new Object();
  void demo3() {
    synchronized(o) {
      //Code block
    }
  }
}
//Lock range

Instance lock / object lock

The scope of the lock is controlled within the object instance

SynchronizedDemo demo = new SynchronizedDemo();
new Thread(() -> {
  demo.demo1();
}).start();  
new Thread(() -> {
  demo.demo1();
}).start();
void demo() {
  synchronized(this) {
    //Code block
  }
}

Class lock

Static method, class object, class lock

synchronized static void demo(){}

void demo() {
    synchronized(SynchronizedDemo.class) {
      //Code block
    }
  }

The essence of mutex

shared resource

Achieve exclusivity by preempting shared resources

Storage of locks (object headers)

Object layout in Heap - > OOP hpp ->oopDesc

Object tag markoop_ mark

Class meta information markoop_ mark

Instance data_ metadata

[align fill]

Object tag (markOop.hpp)

hashcode

Generational age

Synchronization lock mark

Deflection lock mark

Lock holder thread ID

Monitor() - > implementation logic of objectmonitor competing for lock

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-zkaatadw-1639183807603) (/ users / muprince / library / Application Support / typera user images / image-20211124001751204. PNG)]

jol is a tool for viewing the layout of class object header information

-20: - usecompressedoops turns off the JVM object header compression pointer

<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.10</version>
</dependency>
System.out.println(ClassLayout.parseInstance(instance).toPrintable());

Bias lock. CAS replaces the thread ID of the object header once. It is closed by default

Lightweight lock, multiple CAS replacement of pointer to stack frame in mark word, spin lock, default 10 times before 1.6, adaptive spin lock after 1.6

Heavyweight lock, thread blocking

Optimistic lock

CAS compares whether the expected data is consistent with the original data. If it is consistent, it will be modified. If it is inconsistent, the modification will fail

object monitor

monitor, implementation of heavyweight lock

Thread communication (wait/notify)

Producer consumer

//consumer
public class Consumer implements Runnable {
  private Queue<String> msg;
  private int maxSize;
  
  public Consumer(Queue<String> msg, int maxSize) {
    this.msg = msg;
    this.maxSize = maxSize;
  }
  
  @Override
  public void run() {
    while(true) {
      synchronized(msg) {
        while(msg.isEmpty()) {//Queue is empty, unable to consume, blocked
          try {
            msg.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("Consumer message:" + msg.ramove());
        msg.notify();//Awaken producer production
      }
    }
  }
}

//producer
public class Producer implements Runnable {
  private Queue<String> msg;
  private int maxSize;
  
  public Producer(Queue<String> msg, int maxSize) {
    this.msg = msg;
    this.maxSize = maxSize;
  }
  
  @Override
  public void run() {
    int i = 0;
    while(true) {
      i ++;
      synchronized(msg) {
        while(msg.size() == maxSize) {//Queue full, no production, blocking
          try {
            msg.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("Producer production messages: messages" + i);
        msg.add("news" + i);
        msg.notify();//Awaken consumer consumption
      }
    }
  }
}

public class App {
  public static void main() {
    Queue<String> msg = new LinkedList<>();
    int maxSize = 10;
    new Thread(new Producer(msg, maxSize)).start();
    new Thread(new Consumer(msg, maxSize)).start();
  }
}

Thread deadlock

What is deadlock

A group of threads competing to share resources wait for each other, resulting in permanent blocking

The livelock thread is not blocked and keeps looping

Conditions for deadlock generation (both satisfied)

  • Mutually exclusive. Shared resources X and Y can only be occupied by one thread
  • Occupy and wait. Thread 1 has obtained shared resource X. when waiting for shared resource Y, shared resource x is not released
  • Cannot preempt. Other threads cannot forcibly preempt the resources occupied by thread 1
  • Loop waiting, thread 1 waits for the resources occupied by thread 2, and thread 2 waits for the resources occupied by thread 1

By expanding the granularity of the lock, it destroys possession and waiting

Via reentrantlock Trylock() is broken and cannot be preempted

Cycle waiting is broken by sequential locking

ThreadLocal

Thread isolation mechanism, unique to threads

Each thread holds a ThreadLocalMap

0x61c88647 magic number, golden section, Fibonacci hash

Linear exploration - > a strategy for resolving hash conflicts

  • Write to find the nearest free cell where the conflict occurred
  • Find, look back from where the conflict occurred
private static final int HASH_INCREMENT = 0x61c88647;

public static void main() {
  magicHash(16);
}

public static void magicHash(int size) {
  int hashCode = 0;
  for (int i = 0; i < size; i ++) {
    hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
    System.out.print((hashCode & (size - 1)) + " ");
  }
}

Thread safety – visibility (volatile)

problem

public class VolatileDemo {
  public static boolean stop = false;
  //public static volatile boolean stop = false;
  //public static volatile int i;
  
  public static void main() {
    new Thread(() -> {
      int i = 0;
      while(!stop) {
        i ++;
        //System.out.println("rs:" + i);  You can end the cycle
        //try {
        //	Thread.sleep(0);  You can end the cycle
    		//} catch (InterruptedException e) {
      	//	e.printStackTrace();
    		//}
      }
      System.out.println("rs:" + i);
    }).start();
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    stop = true;
  }
}

Unable to stop thread, unable to output rs + i

volatile keyword modification can solve the visibility problem

-Djava.compiler=NONE turn off the immediate compiler

The jdk of server version has been deeply optimized (JIT) --just in time

while(!stop){
  i ++;
}
//After optimization
if(!stop) {
  while(true) {
    i ++;
  }
}

print can cause the loop to end

Activity failure

print contains the synchronized modifier

The release of the lock forces the write operation of the working memory to be synchronized to the main memory

print is an IO operation

IO operations block threads

Thread.sleep() can end the cycle

Officially, the compiler is free to load this one or more times done

sleep will cause thread switching, invalidate the cache and reload

volatile keyword

hsdis tool for printing assembly instructions

lock assembly instruction – ensure visibility

The lock instruction loads the cache from the cpu core

What is visibility

Thread A is not visible to thread B after modifying the variable

Hardware level

cpu, memory, IO device

Speed CPU > memory > IO

  • cpu increase cache
  • Operating system, process, thread, cpu time slice switching
  • Compiler optimization, more rational use of cpu cache

cpu cache

  • L1d data cache, cpu core exclusive
  • L1i instruction cache, cpu core exclusive
  • L2 cpu core exclusive
  • L3 sharing

The existence of cache leads to cache consistency problems

Bus lock
Buffer lock

Does the cpu architecture support

Does the current data exist in the cache row

Cache consistency protocol

MSI,MESI,MOSI...

MESI represents four cache states

  • M modify modify
  • E exclusive
  • S shared
  • I invalid
Store Buffer

store buffer is introduced to solve the blocking of cpu notifying cache invalidation

The introduction of store buffer causes instruction reordering

Memory barrier

Instructions are not allowed to be reordered and written to main memory

volatile keywordprevents instruction reordering through a memory barrier

  • Read barrier #Load instruction
  • Write barrier #Store instruction
  • Full screen barrier #Fence command

Prevent instruction reordering

Disable cpu caching

#Lock instruction - > equivalent to memory barrier

Different cpu architectures, X86 is a strong consistency architecture

Software level

Java Memory Model (JMM)

The memory model defines the operation specification of multi-threaded read and write in shared memory

Provides solutions to visibility and ordering problems

JMM encapsulates memory barrier instruction types based on hardware level
  • LoadLoad Barriers sequentially loads Load1;LoadLoad;Load2
  • LoadStore Barriers read and then write Load1;LoadStore;Store2
  • StoreStore Barriers writes store1 in sequence; StoreStore; Store2
  • StoreLoad Barriers write first and then read Store1;StoreLoad;Load2

javap -v Xx.class view byte instruction

ACC_VOLATILE

bytecodeInterpreter.cpp

orderAccess.hpp

Happens before model

Program sequence rules (as if serial semantics)

  • The execution result of the program cannot be changed (in the single thread environment, the execution result remains unchanged)

  • Dependency problem. If there is a dependency between two instructions, reordering is not allowed

    void test() {
      int a = 1;
      int b = 1;
      int c = a * b;
    }
    

    a happens-before b ; b happens-before c

Transitive rule

a happens-before b ; b happens-before c --> a happens-before c

volatile variable rule

Writes to volatile variables must happen before subsequent reads to volatile variables

Prevent instruction reordering through memory barriers

public class VolatileExample{
  int a = 0;
  volatile boolean flag = false;
  
  //Thread1
  public void writer() {
    a = 1;										// 1 
    flag = true; //modify 				     two
  }
  //Thread2
  public void reader() {
    if (flag) { //flag = true		3
      int i = a; //i = 1				4
    }
  }
}

1 happens before 2 is established? yes

3 does happens before 4 hold? yes

2 happens before 3 -- > volatile rule

1 happens before 4 I = 1 established

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-i1az0a4d-1639183807604) (/ users / muprince / library / Application Support / typera user images / image-20211124052551123. PNG)]

Monitor lock rule

int x = 10;
synchronized(this) {
  //The value of x read by subsequent threads must be 12
  if (x < 12) {
    x = 12;
  }
}

The release of the lock must happen before the subsequent operation of locking the thread

start rule

public class StartDemo {
  int x = 0;
  Thread t = new Thread(() -> {
    //The value of x read must be 20
    if (x == 20){} //It must be established
  });
  x = 20;
  t.start();
}

The operation before thread start must be a happens before thread run operation

Join rule

public class JionDemo {
  public static void main() {
    int x = 0;
    Thread t = new Thread(() -> {
      x = 20;
    });
    t.start();
    t.jion();//Ensure visibility of results
    //The value of x read after jion must be 20
  }
}

Changes to shared variables in the t thread are visible to all operations after jion

wait/notify, Thread instance object lock

The fatally keyword provides rules for memory barriers

Topics: Java Multithreading Concurrent Programming