Concurrency and Multithreading

Keywords: Java Hibernate jvm

Basic concepts

Concurrency and Parallelism

  1. Concurrency: Two or more events occur at the same time interval.When multiple threads are operating, if the system has only one CPU, it is impossible for the system to actually run more than one thread at the same time. It can only divide the CPU run time into several time periods, then assign time periods to each thread to execute. When the thread code in one time period runs, other threads are suspended.This is called concurrency.
  2. Parallel: Two or more events occur at the same time.When the system has more than one CPU, the operation of threads may be non-concurrent.When a CPU executes one thread, another CPU can execute another thread. Two threads do not preempt CPU resources from each other, they can do so simultaneously, which is called Parallel.

Processes and Threads

  1. A program may have multiple processes, and a process consists of multiple threads and shared resources
  2. Process: The basic unit that owns resources
  3. Threads: The basic unit for dispatching independently

thread

Create Threads

Thread

  1. Inherit the Thread class (Thread implements the Runnable interface)

  2. Override run method

  3. The start() method starts the thread

Runnable

  1. Implement Runnable Interface
  2. Override run method
  3. new Thread(Runnable target),new Thread(Runnable target,String name)

Multiple Thread instances share a single Runnable, and these threads have the same run method and can share the same data

But there is a thread synchronization problem

public class RunnableTest implements Runnable
{
    private int ticket = 10;
    public void run()
    {
        while (true)
        {
            if (ticket > 0)
            {
                System.out.println(Thread.currentThread().getName() + "Sell" + ticket + "Number ticket");
                ticket--;
            }
            else System.exit(0);
        }
    }
    public static void main(String[] args)
    {
        RunnableTest rt = new RunnableTest();
        Thread t1 = new Thread(rt, "1 Number window");
        Thread t2 = new Thread(rt, "2 Number window");
        t1.start();
        t2.start();
    }
}

print

Window 1 sells Ticket 10
 Window 1 sells Ticket 9
 Window 1 sells Ticket 8
 Window 1 sells Ticket 7
 Window 2 Sells Ticket 7
 Window 2 sells ticket No. 5
 Window 1 sells ticket 6
 Window 2 sells ticket number 4
 Window 1 sells Ticket 3
 Window 2 sells ticket 2
 Window 1 sells Ticket 1

Anonymous Class

Anonymous classes can easily access local variables of a method, but must be declared final because the lifecycle of anonymous classes is inconsistent with that of ordinary local variables

jdk7 no longer needs to be declared final, but is actually automatically and implicitly declared by the virtual machine

public static void main(String[] args)
{
    new Thread()
    {
        public void run()
        {
            //content
        }
    }.start();
    new Thread(new Runnable()
    {
        public void run()
        {
            //content
        }
	}).start();
}

Callable

  1. Create an implementation class for Callable and write the call() method, which is a thread executor with a return value

  2. Create an instance of the Callable implementation class and wrap the Callable object with the FutuerTask class, which encapsulates the return value of the Callable object call() method

  3. Instantiate the FutuerTask class with the FutuerTask interface implementing the class object to start the thread

  4. Gets the return value after the thread ends by the get() method of the object of the FutuerTask class

    public class CallableTest implements Callable<Integer>
    {
        //Override executable call()
        public Integer call() throws Exception
        {
            int i = 0;
            for (; i < 10; i++)
            {
               //
            }
            return i;
        }
        public static void main(String[] args)
        {
            Callable call = new CallableTest();
            FutureTask<Integer> f = new FutureTask<Integer>(call);
            Thread t = new Thread(f);
            t.start();
            //Get Return Value
            try
            {
                System.out.println("Return value:" + f.get());
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    

    print

    Return value: 10
    

Thread method

  1. Thread executor: run()

  2. Start thread: start()

  3. Thread class method

    Method describe
    public final void setName(String name) Change Thread Name
    public final void setPriority(int priority) set priority
    public final void setDaemon(boolean on) Set as a daemon thread and end automatically when only daemon threads are left
    public final boolean isAlive( ) Test if the thread is active
    public static void yield( ) Pause the current thread (back to ready)
    public static void sleep(long millisec) Enter hibernation
    public final void join( ) Pause the current thread until the thread calling this method finishes execution
    public final void join(long millisec) Pause the current thread for a specified time
    public static Thread currentThread() Returns a reference to the currently executing thread object

Thread state

  1. Ready state:

    • The start() method is ready to wait for the virtual machine to schedule
    • Run-state call yield method will enter ready state
    • Threads in the lock pool are ready after acquiring a lock
  2. Run state: Ready state is dispatched by the thread to run state

  3. Blocking state:

    • Hibernate: Call the sleep method
    • Object wait pool: Call wait or join method and enter lock pool after notify
    • Object lock pool: No locks were acquired
  4. Death status: run method execution completed

    graph TB T (New Thread) -- start Method - > A (Ready State) A--Thread Scheduling-->B (Running State) B--yield method-->A B--sleep method-->D (blocking: hibernating) B--wait or join method-->E (blocking: wait pool) B--No lock Acquired-->F (Blocking: lock Pool) B--run Method Executed-->C (Death Status) D--Time to come-->A E--notify method-->F F--Acquire Lock-->A

Thread Synchronization

The process of ensuring program atomicity, visibility, and order

Blocking Synchronization

Pessimistic concurrency policy based on lock contention

synchronized

  1. synchronized Meaning

    • Use synchronized to lock an object, and when other threads want to lock the object to execute a piece of code, they must wait for the thread that already has the lock to release it

    • Locks are released by mutex execution, exception throwing, lock object calling wait method

  2. Different ways of using represent different lock granularities

    • Modify Common Method= synchronized(this)
    • Modify Static Method= Synnized (X.class)
    • Modifier code block (object extends Object)

ReentrantLock

  1. Create Lock Lock

    ReentrantLock implements the Lock interface, Lock lock = new ReentrantLock()

  2. Lock Meaning

    • Use the lock() method to indicate that the current thread owns the lock object

    • Releasing the object shows that unlock() methods are dropped, mostly in finally blocks

  3. trylock method

    • synchronized waits for locks all the time, and Lock provides a trylock method that attempts to occupy within a specified time
    • With trylock, it is important to judge when releasing a lock, unlock throws an exception if the occupation fails
  4. Lock's thread interaction

    • Get a Condition object from the lock object, Condition condition = lock.newCondition()

    • Calling this Condition object: await, signal, signalAll method

  5. Example

    public class LockTest
    {
        public static void log(String msg)//Logging Method
        {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date();
            String dateStr = sdf.format(date);
            System.out.println(dateStr + " " + Thread.currentThread().getName() + " " + msg);
        }
        public static void main(String[] args)
        {
            Lock lock = new ReentrantLock();
            new Thread("t1")
            {
                public void run()
                {
                    boolean flag = false;
                    try
                    {
                        log("Thread started");
                        log("Attempt to possess lock");
                        flag = lock.tryLock(1, TimeUnit.SECONDS);
                        if (flag)
                        {
                            log("Successful possession lock");
                            log("Perform 3 seconds business operation");
                            Thread.sleep(3000);
                        }
                        else
                        {
                            log("After a second of trying, possess lock Fail, give up possession");
                        }
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    finally
                    {
                        if (flag)
                        {
                            log("release lock");
                            lock.unlock();
                        }
                    }
                    log("Thread End");
                }
            }.start();
            try
            {
                //Let t1 execute for two seconds first
                Thread.sleep(2000);
            }
            catch (InterruptedException e1)
            {
                e1.printStackTrace();
            }
            new Thread("t2")
            {
                public void run()
                {
                    boolean flag = false;
                    try
                    {
                        log("Thread Start");
                        log("Attempt to possess lock");
    
                        flag = lock.tryLock(1, TimeUnit.SECONDS);
                        if (flag)
                        {
                            log("Successful possession lock");
                            log("Perform a 3-second business operation");
                            Thread.sleep(3000);
                        }
                        else
                        {
                            log("After a second of trying, possess lock Fail, give up possession");
                        }
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    finally
                    {
                        if (flag)
                        {
                            log("release lock");
                            lock.unlock();
                        }
                    }
                    log("Thread End");
                }
            }.start();
        }
    }
    

    print

    2019-11-07 15:50:01 t1 thread started
     2019-11-07 15:50:01 t1 Attempt to Occupy lock
     2019-11-07 15:50:01 t1 successfully occupied lock
     2019-11-07 15:50:01 t1 for 3 seconds of business operation
     2019-11-07 15:50:03 t2 Thread Start
     2019-11-07 15:50:03 t2 Attempt to Occupy lock
     2019-11-07 15:50:04 t2 After a one-second attempt to seize lock failed, giving up possession
     2019-11-07 15:50:04 t2 thread end
     2019-11-07 15:50:04 t1 release lock
     2019-11-07 15:50:04 t1 thread end
    
  6. The difference between synchronized and Lock

    • Synchronized is the keyword, Lock is the interface, synchronized is the built-in language implementation, and Lock is the code-level implementation
    • synchronized execution completes automatic lock release, Lock needs to display unlock()
    • synchronized waits until it tries to occupy a lock. Lock can use trylock and try to occupy for a period of time. When it fails, it gives up

Non-blocking synchronization

Non-blocking synchronization is an optimistic concurrency strategy based on conflict detection and data updates

actomic class

  1. Atomic Operation

    • Atomic operations are uninterruptible and must be performed at once
    • Assignment is an atomic operation, but a++ is not an atomic operation, but a three-step evaluation, addition and assignment
    • Thread security issues arise when one thread has not yet had time to add one to its i value and the second thread has to do the same.
  2. Use of actomic classes

    • After jdk6, a new package, java.util.concurrent.atom, was added with a variety of atomic classes, such as AtomicInteger
    • AtomicInteger offers a variety of self-increasing, self-decreasing methods that are atomic.In other words, the self-incrementAndGet method is thread-safe
    • 10,000 threads do the value plus one operation, get inaccurate results in a++ mode, and get correct results using the addAndGet() method of Atomic Integer
    public class ThreadTest
    {
        static int value1 = 0;
        static AtomicInteger value2 = new AtomicInteger(0);//Atomic Integer Classes
        public static void main(String[] args)
        {
            for (int i = 0; i < 100000; i++)
            {
                new Thread()
                {
                    public void run()
                    {
                        value1++;
                    }
                }.start();
                new Thread()
                {
                    public void run()
                    {
                        value2.addAndGet(1);//Atomic operations in value++.
                    }
                }.start();
            }
            while (Thread.activeCount() > 2)
            {
                Thread.yield();
            }
            System.out.println(value1);
            System.out.println(value2);
        }
    }
    

    print

    99996
    100000
    

No Synchronization Scheme

If a method does not involve sharing data, it is inherently thread-safe

Reentrant code

You can interrupt the code at any time you execute it, and then execute another piece of code without any errors in the original program after control returns

  1. A method that returns predictable results by entering the same data returns the same results, which is reentrant and thread safe

  2. Stack closure is a reusable code

    Thread security issues do not occur when multiple threads access local variables for the same method, because local variables are stored on the virtual machine stack and belong to the private area of the thread, so thread security does not occur.

    public class ThreadTest
    {
        static void add()
        {
            int value = 0;
            for (int i = 0; i < 1000; i++)
            {
                value++;
            }
            System.out.println(value);
        }
    
        public static void main(String[] args)
        {
            ExecutorService threadPool = Executors.newCachedThreadPool();
            threadPool.execute(() -> add());
            threadPool.execute(() -> add());
            threadPool.shutdown();
        }
    }
    

    print

    1000
    1000
    

Thread Local Storage

  1. Limit the visibility of shared data to the same thread, even without synchronization, to avoid data contention

  2. Using the java.lang.ThreadLocal class for thread local storage

    • ThreadLocal variable is a variable where different threads can have different values and all threads can share a ThreadLocal object
    • ThreadLocal value of any thread changes without affecting other threads
    • Assigning and viewing ThreadLocal variables using set() and get() methods
    public class ThreadLocalDemo
    {
        public static void main(String[] args)
        {
            ThreadLocal threadLocal1 = new ThreadLocal();
            Thread t1 = new Thread(() ->
            {
                threadLocal1.set(1);
                try
                {
                    Thread.sleep(3000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println(threadLocal1.get());
            });
            Thread t2 = new Thread(() -> threadLocal1.set(2));
            t1.start();
            t2.start();
        }
    }
    

    print

    1
    
  3. ThreadLocal principle

    • Each thread has a ThreadLocal.ThreadLocalMap object, and when the threadLocal1.set(T value) method is called, threadLoacl1 and value pairs are saved in the map
    • The underlying data structure of ThreadLocalMap may cause memory leaks, call the remove() method when possible after using ThreadLocal

deadlock

Deadlock condition

  1. mutual exclusion
  2. Request and Hold Conditions
  3. Indeprivable Conditions
  4. Cyclic Waiting Conditions (Loop Conditions)

Java Deadlock Example

public static void main(String[] args)
{
    Object o1 = new Object();
    Object o2 = new Object();

    Thread t1 = new Thread()
    {
        public void run()
        {
            synchronized (o1)//Possession o1
            {
                System.out.println("t1 Occupied O1");
                try
                {
                    Thread.sleep(1000);//Pause 1000ms, another thread has enough time to occupy o1
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("t1 Attempt to possess o2");
                System.out.println("t1 Waiting");
                synchronized (o2)
                {
                    System.out.println("t1 Occupied O2");
                }
            }
        }
    };
    Thread t2 = new Thread()
    {
        public void run()
        {
            synchronized (o2)  //Occupy o2
            {
                System.out.println("t2 Occupied o2");
                try
                {
                    Thread.sleep(1000);//Pause 1000ms, another thread has enough time to occupy o2
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("t2 Attempt to possess o1");
                System.out.println("t2 Waiting");
                synchronized (o1)
                {
                    System.out.println("t2 Occupied O1");
                }
            }
        }
    };
    t1.start();
    t2.start();
}

print

t1 already owns O1
 t2 already owns o2
 t1 tries to occupy o2
 t1 Waiting
 t2 tries to occupy o1
 t2 Waiting

Thread Communication

  1. Object Class Method

    Method describe
    wait( ) Threads enter the waiting pool
    notify( ) Wake up a thread waiting for the current thread lock
    notifyAll( ) Wake up all threads, priority wake up

    Why are these methods set on Object objects?

    Apparently, because any object can be locked

    At the bottom, the Object Monitor mechanism for java multithreaded synchronization has a collection-like data structure set on each object that stores the thread currently getting the lock, the thread waiting to get the lock, and the thread waiting to be waked up.

  2. Producer-consumer model

    • sleep method, yields the cpu but does not drop the lock
    • wait method, enters the waiting pool of the lock object, drops the lock
public class ProducerAndConsumer
{
    public static void main(String[] args)
    {
        Goods goods = new Goods();
        Thread producer = new Thread()//Producer Threads
        {
            public void run()
            {
                while (true) goods.put();
            }
        };
        Thread consumer = new Thread()//Consumer Threads
        {
            public void run()
            {
                while (true) goods.take();
            }
        };
        consumer.start();
        producer.start();
    }
}
class Goods//Commodity class
{
    int num = 0;//Number of Goods
    int space = 10;//Total number of vacancies
    public synchronized void put()
    {
        if (num < space)//There is space available for production
        {
            num++;
            System.out.println("Put in a commodity, existing" + num + "Commodities," + (space - num) + "Empty spaces");
            notify();//Wake up the thread waiting for the lock
        }
        else//No vacancy, wait for vacancy
        {
            try
            {
                System.out.println("No vacancy left, waiting to be taken out");
                wait();//Enter the wait pool for the lock object
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    public synchronized void take()
    {
        if (num > 0)//Goods available
        {
            num--;
            System.out.println("Take out a product, existing" + num + "Commodities," + (space - num) + "Empty spaces");
            notify();//Wake up the thread waiting for the lock
        }
        else///Waiting for product to be produced
        {
            try
            {
                System.out.println("No merchandise to take, waiting to put in");
                wait();//Enter the wait pool for the lock object
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

print

No merchandise to take, waiting to put in
 Place one item, 1 item available, 9 empty spaces
 Place one item, 2 items available, 8 empty spaces
 Take out one item, 1 item available, 9 empty spaces
 Place one item, 2 items available, 8 empty spaces
 Place one item, 3 items available, 7 empty spaces
 Place one item, 4 items available, 6 empty spaces
 Take out one item, have 3 items, 7 empty spaces
 Place one item, 4 items available, 6 empty spaces
···

Thread Pool

Threads start and end are time-consuming and resource-consuming. If many threads are used in the system, a large number of start and end actions can seriously affect performance.

Thread pools are much like producer-consumer mode, where the object of consumption is a task that can be run

  1. Design Ideas

    • Prepare task containers, use List, store tasks
    • Create multiple executor threads in the thread pool class construction method
    • All threads wait when the task container is empty
    • When an external thread adds a task to the task container, an executor thread is notify
    • When the task is finished, return to the waiting state without receiving a new task
  2. Implement a thread pool

    public class ThreadPool
    {
        int poolSize;// Thread pool size
        LinkedList<Runnable> tasks = new LinkedList<Runnable>();// Task Container
        public ThreadPool(int poolSize)
        {
            this.poolSize = poolSize;
            synchronized (tasks)//Start poolSize task executor threads
            {
                for (int i = 0; i < poolSize; i++)
                {
                    new ExecuteThread("Executor Thread " + i).start();
                }
            }
        }
        public void add(Runnable r)//Add Task
        {
            synchronized (tasks)
            {
                tasks.add(r);
                System.out.println("Join a new task");
                tasks.notifyAll();// Wake up the waiting task executor thread
            }
        }
        class ExecuteThread extends Thread//Threads waiting to execute tasks
        {
            Runnable task;
            public ExecuteThread(String name)
            {
                super(name);
            }
            public void run()
            {
                System.out.println("Start:" + this.getName());
                while (true)
                {
                    synchronized (tasks)
                    {
                        while (tasks.isEmpty())
                        {
                            try
                            {
                                tasks.wait();
                            }
                            catch (InterruptedException e)
                            {
                                e.printStackTrace();
                            }
                        }
                        task = tasks.removeLast();
                        tasks.notifyAll(); // Threads that allow adding tasks can continue adding tasks
                    }
                    System.out.println(this.getName() + " Task Received");
                    task.run();//Execute Tasks
                }
            }
        }
        public static void main(String[] args)
        {
            ThreadPool pool = new ThreadPool(3);
            for (int i = 0; i < 5; i++)
            {
                Runnable task = new Runnable()//Create Task
                {
                    public void run()//Task Content
                    {
                        System.out.println(Thread.currentThread().getName()+" Execute Tasks");
                    }
                };
                pool.add(task);//Join Task
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    print

    main joins a new task
     Start: Executor Thread 0
     Executor Thread 0 Receives Tasks
     Executor Thread 0 Executes Tasks
     Start: Executor Thread 1
     Start: Executor Thread 2
     main joins a new task
     Executor Thread 2 receives the task
     Executor Thread 2 Executes Tasks
     main joins a new task
     Executor Thread 2 receives the task
     Executor Thread 2 Executes Tasks
    
    
  3. java thread pool class

    • The default thread pool class ThreadPoolExecutor is under the java.util.concurrent package

      ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
      /*
      The first parameter, int type, is 10, which means that this thread pool initializes 10 threads to work inside
       The second parameter, int type, 15 indicates that if 10 threads are not enough, they will automatically increase to a maximum of 15 threads
       The third parameter, 60, combined with the fourth parameter, TimeUnit.SECONDS, indicates that after 60 seconds, the extra threads will be recycled before they receive the task, and finally 10 remain in the pool
       The fifth parameter, BlockingQueue type, is the set of new LinkedBlockingQueue() used to place tasks
      */
      
      
    • The execute() method adds a new task

      public class TestThread 
      {   
          public static void main(String[] args) throws InterruptedException 
          {
              ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
              threadPool.execute(new Runnable()
              {//Add Task
                  public void run() 
                  {
                      System.out.println("Execute Tasks");
                  }    
              });
          }
      }
      
      
  4. Several thread pools in java

    The top-level interface of the java thread pool is Executor, and the subinterface is ExecutorService, which is more widely used

    The Executors class provides a series of factory methods for creating thread pools that implement the ExecutorService interface

    • newCachedThreadPool has a buffered thread pool, number of threads controlled by JVM, and no new threads are created when threads are available
    • newFixedThreadPool, fixed-size thread pool, tasks are queued when the number of threads exceeds
    • newScheduledThreadPool, which creates a pool of threads that can be scheduled to run commands after a given delay or to execute them regularly
    • newSingleThreadExecutor, which has only one thread and executes multiple tasks sequentially, will be created if terminated unexpectedly
    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//Buffer Thread Pool
    threadPool = Executors.newFixedThreadPool(3);//Fixed-size thread pool
    threadPool = Executors.newScheduledThreadPool(2);//Timed Task Thread Pool
    threadPool = Executors.newSingleThreadExecutor();//Single-threaded thread pool
    threadPool = new ThreadPoolExecutor(···);//Default thread pool, multiple controllable parameters
    
    

Thread Security Class

  1. StringBuffer: Internal method decorated with synchronized
  2. Vetort: inherited from AbstractList
  3. Stack: Inherited from Vector
  4. HashTable: inherits from Dictionary and implements the Map interface
  5. Property: inherits from HashTable and implements the Map interface
  6. concurrentHashMap: Segmented locking mechanism

Posted by rekha on Sun, 22 Dec 2019 18:03:37 -0800