Java multithreading (with exercises and partial parsing)

Java multithreading and parallel programming

1, Basic knowledge points

1. Thread concept (understand)

Program: represents a static object. It is a file containing instructions and data, which is stored on disk or other storage devices

Process: represents a dynamic object. It is an execution process of a program. It exists in the memory of the system. A process corresponds to a program

Thread: a sequence control process that runs in a process and is used to complete a specific task. It is sometimes called a light process

The overhead of process switching and inter process communication is large. The data exchange between processes can only be through mechanisms such as shared memory, pipeline, message queue and Socket communication. However, the overhead of thread switching in a process is much smaller.

When a process is created, a main thread is automatically created. A process has at least one main thread.

Multiple threads share one CPU: only one thread can use the CPU at a certain time.

Modern operating systems regard threads as the minimum scheduling unit and processes as the minimum unit of resource allocation. The resources allocated to processes can be used by threads in processes.

Role of threads: multiple sub threads of a process can run concurrently. Multithreading can make the program respond faster, interact more and execute more efficiently.

2. Runnable interface and Thread class Thread (mandatory)

Create Thread method: the Thread execution logic (later called Thread task) must implement Java The only run method of lang.Runnable interface. In addition, since Thread implements the Runnable interface, Thread classes can also be derived from Thread.

Therefore, there are two ways to run the same thread or threads:

(1) Define subclasses of Thread class and override run method;

(2) Implement the run method of the interface Runnable. (recommended, because java only supports single inheritance, but can implement multiple interfaces)

  1. Create a thread by implementing the Runnable structure
  • To implement the Runnable interface, you need to implement the unique interface method run
    • void run() defines the functions that the thread performs
  • Create an object of a class that implements the Runnable interface
  • Using the constructor of Thread class to create Thread object
    • public Thread (Runnable target): the Runnable interface instance needs to be passed in when the new Thread object
  • Start the thread through the start() method of the thread object

Any thread can only be started once and generated by multiple calls.

class PrintChar implements Runnable //Implement Runnable interface
{
      private char charToPrint;  // The character to print
      private int times;  // The times to repeat
      public PrintChar(char c, int t){  charToPrint = c;  times = t; }
      public void run(){ //Implement the run method declared in Runnable
          for (int i=1; i < times; i++) System.out.print(charToPrint);
      }
}
public class RunnableDemo
{
    public static void main(String[] args){
        //Construct Thread object with PrintChar object instance as parameter 
        Thread printA = new Thread(new PrintChar('a',100));
        Thread printB = new Thread(new PrintChar('b',100));
        printA.start();//Start thread
        printB.start();
    }
}
  1. Create a Thread by inheriting the Thread class
  • Define the subclass of Thread class - Thread task class
  • Creating thread objects through classes
  • Start the thread through the thread object thread (inheriting the start method of thread)
class PrintChar extends Thread //Inherit Thread class
{
	private char charToPrint;  //Characters to print
	private int times;  //Number of times to print
	public PrintChar(char c, int t){ charToPrint = c; times = t; }
	public void run() {//Override the run method and define the functions to be completed by the thread
		for (int i=1; i < times; i++)
			System.out.print(charToPrint);
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
	    Thread printA = new PrintChar('a',100); //Create a second thread object
	    Thread printB = new PrintChar('b',100);
	    printA.start(); //Start thread
	    printB.start();	//Start another thread
	}
}
  • Status of thread:
    • Ready: waiting for CPU scheduling
    • Run: the thread takes ownership of the CPU and runs on it
    • Call the yield() method to actively give up the ownership of the CPU and turn to the ready state
    • Blocking: waiting for resource lock
    • Die: when run() is completed, the thread dies.

The following conditions cause the thread to go from running state to blocking state:
1: sleep called
2: The Object wait() method, the await method of the condition object, and the join method of the Thread are called to wait for other threads or wait for resource locks
3: Sends a blocking IO operation request and waits for the IO operation result (such as waiting for the data of the blocking Socket to arrive)

After the thread is awakened from the blocking state, it returns to the ready state. Reasons for awakening
1: sleep time is up
2: The thread calling wait (await) is notified by other threads. The thread calling join method waits until other threads finish and the thread gets the resource lock
3: Blocking IO complete

sleep, wait, await, join, etc. enter the blocking state. sleep enters the ready state after waiting for the time. Wait and await need to be awakened before entering the ready state. What the join method needs to do is that when a new thread is added, the main thread will enter the waiting state until the execution of the thread calling the join method ends.

When the yield enters the ready state, there is no need to wake up. As soon as the CPU is free, it is possible to execute

  • Thread priority (understand)

The thread priority ranges from 1 to 10. The higher the number, the more priority can be given to execution. However, high priority does not mean that it can occupy the execution time slice alone. It may be that high priority gets more execution time slices. On the contrary, low priority gets less execution time, but it will not allocate less execution time.

Each thread is created with the default priority thread NORM_ PRIORITY.

Assign priority to threads through * * setPriority(int priority) * *

Use the * * getPriority() * * method to get the priority of the thread

Java thread scheduling is preemptive, which means that the scheduling mechanism will periodically interrupt the thread and switch the context to another thread; This provides a time slice for each thread.

Multiple threads can only be "parallel on the macro level and serial on the micro level".

Do not write programs that depend on thread priority.

  • yield and sleep methods

    • Use the yield() method to give up CPU time for other threads

      Thread.yield(); Suspend and enter ready to give other processes scheduling opportunities

      The sleep(long mills) method sets the thread to sleep state to ensure that other threads execute:

      public void run() {
      Try {/ / the sleep method is used in the loop, and the loop is placed in the try catch block
      for (int i = 1; i < times; i++) {
      System.out.print(charToPrint);
      if (i >= 50) Thread.sleep(1);
      }
      }
      //Mandatory exception: this exception will be thrown by the interupt method when other threads call the current thread (sleeping)
      catch (InterruptedException ex { }
      }

  • join method

    Function of the join method: when the join() method of the B thread (object) is called in the A thread, it means that the A thread gives up control (is blocked). Only when the B thread finishes executing, the A thread is awakened to continue executing.

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException{
        Thread printA = new Thread(new PrintChar('a',100));
        Thread printB = new Thread(new PrintChar('b',100));
        printA.start();  //Start the printA thread in the main thread first
        printA.join(); //The main thread is blocked and waits for printA to finish executing
        printB.start(); //The main thread is awakened and the printB thread is started
    }
}
class PrintChar implements Runnable
{
    private char charToPrint;  // The character to print
    private int times;  // The times to repeat
    public PrintChar(char c, int t){  charToPrint = c;  times = t; }
    public void run(){ //Implement the run method declared in Runnable
        for (int i=1; i < times; i++) 
            System.out.print(charToPrint);
    }
}

When the program calls the join method of printA thread (object) in the main thread, the main thread gives up cpu control (blocked) until the execution of printA is completed and the main thread is awakened to execute printb start();

That is, block the calling thread (which can be the main thread) until the execution of the called thread is completed.

3. Thread pool

When the Thread task is executed, that is, after the run method is completed, the Thread object will die out, and then new Thread objects will be added for new Thread tasks... When there are a large number of Thread tasks, new Thread objects will be added continuously, the Thread object will die out, and then new Thread objects will be added. (this is not efficient enough)

Thread pool improves performance by effectively managing threads and "reusing" threads.

From jdk1 5 start to use the Executor interface (Executor) to execute the characters in the thread pool, and the ExecutorService sub interface of Executor to manage and control tasks.

java. util. concurrent. There are methods in the executor: execute (runnable object), thread executor interface, which is responsible for executing thread characters.

Thread tasks are managed and controlled through ExecutorService, a subclass of Executor.

java. util. concurrent. There are methods in executorservice: shutdown(), close the actuator; Shutdown now(), immediately close the actuator and return a list of unfinished tasks; isShutdown()´╝îisTerminated()

We don't need to worry about the creation of Thread, just give the instantiated Thread task object to the Executor. Threads are created and maintained within the Thread pool.

Thread pool creation

java.util.concurrent.Executors: newFixedThreadPool(int numberOfThread)´╝înewCachedThreadPool()

Executors also support the creation methods of other types of thread pools: newScheduledThreadPool and newSingleThreadPool

import java.util.concurrent.*;
public class ExecutorDemo {
  public static void main(String[] args) {
    // Create a fixed thread pool with maximum three threads
    ExecutorService es= Executors.newFixedThreadPool(3);
    // Submit runnable tasks to the executor
 	es.execute(new PrintChar('a', 100));
 	es.execute(new PrintChar('b', 100));
 	es.execute(new PrintNum(100));
    // Shut down 
    es.shutdown();//New thread characters are not accepted, and existing tasks will continue to execute until they are completed
  }
}

A thread task is an instance of a class that implements the Runnable interface.

Thread is the instance of thread class and the running carrier of task.

4. Thread synchronization (*****)

give an example:

public class AccountWithoutSync {	
	private static class Account{//Internal static class Account
		private int balance = 0;
		public int getBalance() {
			return balance;  
		}	
		public void deposit(int amount){
			int newBalance = balance + amount;   //Read balance			
			try{ Thread.sleep(5); }				//Hibernation is to amplify the possibility of data inconsistency
				catch(InterruptedException e){	}
			balance = newBalance;		//Write balance
		}	
	}
	private static class AddPennyTask implements Runnable{
		public void run() {  account.deposit(1); }
		
	}
	private static Account account = new Account();
    public static void main(String[] args){
        //Create thread pool executor
        ExecutorService executor = Executors.newCachedThreadPool();
        for(int i = 0; i < 100; i++){
            executor.execute(new AddPennyTask());
        }
        executor.shutdown();//Close thread pool
        while(!executor.isTerminated()){  }//Wait for all threads in the thread pool to end
        System.out.println("What is balance?" + account.getBalance());
    }
}

The top-level interface of thread pool in Java is Executor, but strictly speaking, Executor is not a thread pool, but just a tool for executing threads. The real thread pool interface is ExecutorService.

newCachedThreadPool

Create a cacheable thread pool. If the size of the thread pool exceeds the threads required to process the task.

Multiple threads accessing public resources at the same time will lead to contention (modifying public resources at the same time). In order to avoid contention, multiple threads should be prevented from entering a specific part of the program at the same time, which is called critical area. The deposit method of the Account class is the critical area. You can synchronize with the synchronized keyword to ensure that only one thread can access this method at a time. When a method is modified by synchronized, the method is atomic (when a thread starts executing the method, it cannot be interrupted).

synchronized can be used to synchronize methods

Use the keyword synchronized to modify the method: public synchronized void deposit(double amount)

For the synchronized instance method, lock the object (this object) calling the method. After locking, other methods of the object cannot be accessed.
For synchronized static methods, lock the class that owns the static method.

The statement blocks synchronized by the synchronized keyword are called synchronized blocks

synchronized (expr) { statements; }
The expression expr evaluation result must be a reference to an object, so you can synchronize the statement block by locking any object
If the object pointed to by expr is not locked, the first thread executing to the synchronization block locks the object, the thread executes the statement block, and then unlocks it;
If the object pointed to by expr has been locked, other threads executing to the synchronization block will be blocked
After the object pointed to by expr is unlocked, all threads waiting for the lock of the object are awakened
The synchronization statement block allows to synchronize part of the code in the method instead of the whole method, which enhances the concurrency of the program

private static class Account{
	private int balance = 0;
	public int getBalance() {
		return balance;
	}	
	public synchronized void deposit(int amount){
		int newBalance = balance + amount;			
		try{ Thread.sleep(5); }//Because the synchronized keyword is used, the lock is added after entering, and the lock is not released after the sleep method is called. So that threads can still be controlled synchronously, and sleep will not give up system resources.
		catch(InterruptedException e){	}
		balance = newBalance;
	}		
}
private static class Account{
	private int balance = 0;
	public int getBalance() {
		return balance;
	}		
	public  void deposit(int amount){
		synchronized(this){			//Lock this account, which is equivalent to the code above
			int newBalance = balance + amount;			
			try{ Thread.sleep(5); }
			catch(InterruptedException e){	}
			balance = newBalance;
		}		
	}		
} 

The following synchronization is wrong

private static class AddPennyTask implements Runnable{
	public void run() {
		synchronized(this){//If you change this to account, you are right
			account.deposit(1);	
		}		
	}	
} 

Tell us: we should pay attention to the locked objects and know who should be locked in order to achieve synchronization.

Thread synchronization - lock synchronization

The synchronization with synchronized keyword should implicitly lock the object instance or class, which has a large granularity and affects the performance.
JDK 1.5 can explicitly lock, and can synchronize threads at a smaller granularity.

A Lock is an instance of a Lock interface.

Class ReentrantLock is a concrete implementation of Lock: reentrant Lock.

java.util.concurrent.locks.Lock: lock, get a lock; unlock to release the lock; newCondition returns a Condition instance bound to the lock instance.

java.util.concurrent.locks.ReentrantLock: ReentrantLock.

Reentrant lock describes the problem of whether a thread can apply for a lock again (multiple times) when it holds it. If a thread has acquired a lock and it can acquire the lock again without deadlock, we call the lock a reentrant lock.

void methodA(){    
	lock.lock(); // Acquire lock    
	methodB();    
	lock.unlock() // Release lock
}
void methodB(){    
	lock.lock(); // Acquire the lock again   
	 // Other business    
	lock.unlock();// Lock release
}

synchronized supports reentry

import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class AccountWithSyncUsingLock {
    private static Account account = new Account();
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        // Create and launch 100 threads
        for (int i = 0; i < 100; i++) {executor.execute(new AddAPennyTask());}
        executor.shutdown();
        // Wait until all tasks are finished
        while (!executor.isTerminated()) { }
        System.out.println("What is balance ? " + account.getBalance());
    }
    // A thread for adding a penny to the account
    public static class AddAPennyTask implements Runnable {
        public void run() {account.deposit(1); }
    }
    public static class Account {// An inner class for account is mainly changed in the account class
        private static Lock lock = new ReentrantLock(); // Note that this is static and shared by all Account instances (apply for a reentrant Lock). The type is Lock
        private int balance = 0;   
        public int getBalance() {return balance;}
        public void deposit(int amount) {
            lock.lock( ); // Acquire the lock
            try {
                int newBalance = balance + amount;
                Thread.sleep(5);
                balance = newBalance;
            }
            catch (InterruptedException ex) {   }
            finally { lock.unlock(); // Release the lock: release the lock in finally
        }//Because finally, it will be executed in any case, so lock Unlock is safer here.
    }
}

synchronized scenario 1

Suppose a class has multiple synchronized instance methods decorated with synchronized. If multiple threads access the same object of this class, when a thread obtains the object lock and enters one of the synchronized methods, the lock will lock all the synchronized instance methods of this object.

Because a synchronized instance method is equivalent to synchronized(this) {}
Therefore, the granularity of the lock is this object. As long as this object is locked, all synchronized instance methods and synchronized(this) {} blocks in this object are locked.

Again: this example shows that when two threads access the same object, as long as one thread gets the object lock, all synchronous instance methods of the object are locked.

synchronized scenario 2

Suppose a class has multiple synchronized instance methods decorated with synchronized. If multiple threads access different objects of this class, the synchronized locks of different objects are different, and the locks of each object can only be synchronized with the threads accessing the object

Sync Scene 3 Lock

If Lock lock is used for synchronization, once the Lock is obtained by a thread, all critical areas controlled by this Lock are locked, and all other threads accessing these critical areas are blocked. (similar to synchronized scenario 1)

Lock synchronization scenario 4

If a class uses a lock lock to lock the critical zone, and the lock lock is also an instance member of the class (see the definition of the lock object in ResourceWithLock), the lock locks of the two instances of this class are different locks. The following animation demonstrates this scenario: the lock lock of object o1 and the lock lock of object o2 are different lock objects. (similar to synchronized scenario 2)

Thread synchronization summary

  • If the synchronized keyword is used to synchronize the instance methods of class A, it is equivalent to synchronized(this) {}
    • Once a thread enters the synchronized instance method of object o of class A, object o is locked, and all synchronized instance methods of object o are locked, blocking the thread to access the synchronized instance method of object o, but it has nothing to do with the thread to access other objects of class A
  • If the synchronized keyword is used to synchronize the static methods of class A, it is equivalent to synchronized(A.class) {}. Once a thread enters a static synchronization method of a, all static synchronization methods of a are locked (this lock is a class level lock). This lock is valid for all threads accessing this kind of static synchronization method, whether these threads access the static synchronization method through the class name or through different objects.
  • If you synchronize through the Lock object, first look at which critical areas the Lock object locks. Once the Lock is obtained by a thread, all critical areas controlled by this Lock will be locked (such as scenario 3); In addition, it is necessary to distinguish whether the Lock object itself is different: different Lock objects can block different threads (such as scenario 4).

Thread synchronization - collaboration between threads

There is resource competition between threads. synchronized and Lock lock synchronization mechanisms solve the problem of resource competition

Mutual cooperation between threads: it can be completed through await/signal/signalAll of Condition object.

  • A Condition object is an object created by calling the newCondition() method of a Lock instance
  • The Condition object can be used to coordinate the interaction between threads (use conditions to realize inter thread communication)
  • Once the condition object condition is created, you can call condition Await () makes the current thread enter the waiting state. Other threads call the signal and signalAll() methods through the same condition object to wake up the waiting thread, so as to realize the mutual cooperation between threads

java.util.concurrent.Condition: await, causing the current thread to wait until the condition signals signal and signalall are received; Signal, wake up a waiting thread; Signalall, wake up all waiting threads.

  • Use loop while instead of conditional if
  • If there is await, there must be signal() or signalAll(), or wait all the time
  • The condition object is created by the lock object. Its method await/signal/signalAll() is called through the condition object. In order to call these methods, you must first have the lock (that is, call the lock method first)
public class ThreadCooperation {
	private static class Account {// An inner class for account
        private static Lock lock = new ReentrantLock();//lock
        private static Condition newDeposit = lock.newCondition();//Create condition object from lock
        private int balance = 0;
        public int getBalance() {return balance;}
        public void withdraw(int amount) {
            lock.lock(); // Acquire the lock
            try {
                while (balance < amount) {
                    System.out.println("\t\t\t\tWait for a deposit");
                    newDeposit.await();//Wait for deposit. If you don't deposit enough, you will enter the waiting again
                }//Unlike sleep, wait releases the lock
                balance -= amount;
                System.out.println("\t\t\t\tWithdraw " + amount +"\t\t\t\t" + getBalance());
            }
            catch (InterruptedException ex) {ex.printStackTrace();}
            finally {lock.unlock(); }
        }
        public void deposit(int amount){
            lock.lock();
            try{
                balance+=amount;
                System.out.println("deposit " + amount + "\t\t\t\t\t\t\t\t" + getBalance( ));
                newDeposit.signalAll();//Wake up the thread task waiting for deposit
            }
            finally{ lock.unlock(); }
        }
    }
}

5. Semaphore

Semaphores are used to limit the number of threads accessing a shared resource. They are locks with counters

Before accessing a resource, the thread must obtain permission from the semaphore

After accessing the resource, the thread must return the permission to the semaphore

semaphore. After the thread is allowed to wait for the signal amount of - quire (), all threads must be waked up as long as the signal amount of - quire () is greater than the critical amount of - 1.

semaphore.release() is called before the thread exits the critical area. The semaphore +1. A garage with a parking space capacity of N can be managed by a semaphore. The semaphore counter is n

In order to create semaphores, the number of licenses (counter maximum) must be determined, and a fair policy can be selected

The task obtains the permission by calling the acquire() method of the semaphore, and the total number of available licenses in the semaphore is reduced by 1

The task releases the license by calling the release() method of the semaphore. Add 1 to the total number of available licenses in the semaphore

import java.util.concurrent.Semaphore;
  // An inner class for account
  private static class Account {
    // Create a semaphore
    private static Semaphore semaphore = new Semaphore(1);//A semaphore with a permission of 1 is equivalent to a mutually exclusive lock, which has the same effect as the previous lock.
    private int balance = 0;
    public int getBalance() {return balance;}
    public void deposit(int amount) {
      try {
    	semaphore.acquire();//Get permission, semaphore-1
    	int newBaance=balance+amount
         Thread.sleep(5);
    	balance=new Balance;
      }
      finally {
        semaphore.release(); //End, semaphore + 1
      }
    }
  }
}

Deadlock avoidance:

Assign an order to each object that needs to be locked, and ensure that each thread acquires locks in this order (a way to avoid deadlock)

public class Account {
    private int id;    // Used to control the sequence
    private String name;
    private double balance;
    public void transfer(Account from, Account to, double money){
        if(from.getId() > to.getId()){
            synchronized(from){
                synchronized(to){
                    // transfer
                }
            }
        }else{
            synchronized(to){
                synchronized(from){
                    // transfer
                }
            }
        }
    }
    public int getId() {
        return id;
    }
}

6. Synchronous collection

Java collection framework includes: List, Set, Map interface and its specific subclasses, which are not thread safe.

The classes in the collection framework are not thread safe. You can protect the data in the collection by locking or synchronizing the critical area of the code accessing the collection

The Collections class provides six static methods to convert a collection to a synchronous Version (that is, a thread safe version)

These synchronized versions of classes are thread safe, but iterators are not, so they must be synchronized when using iterators: synchronized (collection object to iterate) {/ / iterate}

2, Problem solving

Completion

  1. There are ways to create threads_ Implement Java Lang. runnable interface_ And__ Thread class _ is derived from thread.

  2. There may be a situation in the program: multiple thread types wait for each other to hold the lock, and they will not release their own lock until they get the other party's lock. This is__ Deadlock _.

  3. If the yield method is called in the thread's executing code, the thread will___ Actively give up the ownership of CPU and turn to ready state.

Note: it is different from sleep, wait, join and so on

  1. Thread programs can call_____ sleep()____________ Method to put the thread into sleep, which can be called____ setPriority()_____ Method to set the priority of the thread.

  2. The statement to get the current thread id is_______ Thread.currentThread().getId();_________.

Single choice questions

  1. It is possible for a thread to enter a dead state______ C_____.

A. Call the yield method of the Thread class

B. Call the sleep method of Thread class

C. The run method of the thread task ends

D. Thread deadlock

  1. The following procedure is given:
public class Holder {
	private int data = 0;
	public int getData () {return data;}
	public synchronized void inc (int amount) {
        int newValue = data + amount;
		try {Thread.sleep(5);
		} catch (InterruptedException e) {}
		data = newValue;
	}
	public void dec (int amount) {
		int newValue = data - amount;
		try {Thread.sleep(1);
		} catch (InterruptedException e) {}
		data = newValue;
	}
}
public static void main (String [] args) {
	ExecutorService es = Executors.newCachedThreadPool();
	Holder holder = new Holder ();
	int incAmount = 10, decAmount = 5, loops = 100;
	Runnable incTask = () -> holder.inc(incAmount);
	Runnable decTask = () -> holder.dec(decAmount);
	for (int i = 0; i < loops; i++) {
		es.execute(incTask);
		es.execute(decTask);
	}
	es. shutdown ();
	while (! es.isTerminated ()) {}
}

The following statement is correct_______ B___.

A. When a thread enters the inc method of the holder object, the holder object is locked, so other threads cannot enter the inc method and dec method

B. When a thread enters the inc method of the holder object, the holder object is locked, so other threads cannot enter the inc method, but can enter the dec method

C. When a thread enters the dec method of the holder object, the holder object is locked, so other threads cannot enter the dec method and inc method

D. When a thread enters the dec method of the holder object, the holder object is locked, so other threads cannot enter the dec method, but can enter the inc method

Only all synchronization methods are locked, and dec is not locked

  1. The following procedure is given:
public class Test2_3 {
  private static Object lockObject = new Object ();
  /**
   * Counter
   */
  public static class Counter {
    private int count = 0;
    public int getCount () {return count;}
    public void inc () {
      synchronized (lockObject) {
        int temp = count + 1;
       try {Thread.sleep(5);} catch (InterruptedException e) {}
       count = temp;
      }
    }
    public void dec () {
      synchronized (lockObject) {
       int temp = count - 1;
       try {Thread.sleep(5);} catch (InterruptedException e) {}
        count = temp;
      }
    }
  }
  public static void main (String [] args) {
    ExecutorService es = Executors.newCachedThreadPool();
    Counter counter1 = new Counter ();
    Counter counter2 = new Counter ();
    int loops1 = 10, loops2 = 5;
    Runnable incTask = () -> counter1.inc ();
    Runnable decTask = () -> counter2.dec ();
    for (int i = 0; i < loops1; i++) {es. execute(incTask);}
    for (int i = 0; i < loops2; i++) {es. execute(decTask);}
    es. shutdown ();
    while (! es. isTerminated ()) {}
  }
}

The following statement is correct____ C_______.

A. After the execution thread of inctask enters the inc method of counter1 object, the counter1 object is locked, which will block the execution thread of decTask from entering the dec method of counter2 object

B. After the execution thread of inctask enters the inc method of counter1 object, the counter1 object is locked, which will not block the execution thread of decTask from entering the dec method of counter2 object

C. After the execution thread of inctask enters the inc method of object counter1, the lockObject object is locked, which will block the execution thread of decTask from entering the method dec of object counter2

D. After the execution thread of inctask enters the inc method of object counter1, the lockObject object is locked, which will not block the execution thread of decTask from entering the method dec of object counter2

  1. The following procedure is given:
public class Test2_4 {
  public static class Resource {
    private int value = 0;
    public int sum (int amount) {
      int newValue = value + amount;
      try {Thread.sleep(5);} catch (InterruptedException e) {}
      return newValue;
    }
    public int sub (int amount) {
     int newValue = value - amount;
      try {Thread.sleep(5);} catch (InterruptedException e) {}
      return newValue;
    }
  }
  public static void main (String [] args) {
    ExecutorService es = Executors.newCachedThreadPool();
    Resource r = new Resource ();
    int loops1 = 10, loops2 = 5, amount = 5;
    Runnable sumTask = () -> r.sum(amount);
    Runnable subTask = () -> r.sub(amount);
    for (int i = 0; i < loops1; i++) {es. execute(sumTask);}
    for (int i = 0; i < loops2; i++) {es. execute(subTask);}
    es. shutdown ();
    while (! es. isTerminated ()) {}
  }
}

The following statement is correct_____ C______.

A. Since neither sum method nor sub method takes any synchronization measures, the execution threads of sumTask and subTask can enter the sum method or sub method of shared resource object r at the same time, resulting in inconsistent values of the instance member value of object R;

B. Since neither sum method nor sub method takes any synchronization measures, the execution threads of sumTask and subTask can enter the sum method or sub method of shared resource object r at the same time, resulting in inconsistent values of local variables newValue and formal parameter amount in the method;

C. Although the methods sum and sub do not take any synchronization measures, the local variables newValue and formal parameter amount in the sum and sub of the Resource class are located in the respective stacks of each thread and do not interfere with each other. At the same time, after multiple threads enter the sum method or sub method of the shared Resource object r, they can only read the value of the instance data member, so the Resource class is thread safe

D. None of the above is true

  1. The following procedure is given:
public class Test2_5 {
  public static class Resource {
    private static int value = 0;
    public static int getValue () {return value;}
    public static void inc (int amount) {
      synchronized (Resource.Class) {
        int newValue = value + amount;
        try {Thread.sleep(5);} catch (InterruptedException e) {}
        value = newValue;
      }
    }
    public synchronized static void dec (int amount) {
      int newValue = value - amount;
      try {Thread.sleep(2);} catch (InterruptedException e) {}
      value = newValue;
    }
  }
  public static void main (String [] args) {
    ExecutorService es = Executors.newCachedThreadPool();
    int incAmount = 10, decAmount = 5, loops = 100;
    Resource r1 = new Resource ();
    Resource r2 = new Resource ();
    Runnable incTask = () -> r1.inc(incAmount);
    Runnable decTask = () -> r2.dec(decAmount);
    for (int i = 0; i < loops; i++) {es. execute(incTask); es. execute(decTask);}
    es. shutdown ();
    while (! es. isTerminated ()) {}
  }
}

The following statement is wrong_____ B______.

A. The synchronized static method public synchronized static void dec (int amount) {} is equivalent to public static void dec (int amount) {synchronized (Resource. class) {}}

B. The execution thread of incTask accesses object r1 and decTask accesses object r2. Because different objects are accessed, the execution thread of incTask and the execution thread of decTask will not be synchronized

C. The thread that is called by task class and the thread that is executed by Resource class crdeinc are synchronized, although the thread that is called by task class and the thread that is executed by Resource class crdeinc are synchronized

D. After a thread enters the static synchronization method of Resource class, all static synchronization methods of this class are locked, and the locked object is Resource class. However, the scope of this lock is all instances of the Resource class, that is, no matter which instance of the Resource class the thread calls the static synchronization method, it will be blocked

Because it is a lock on a class, although it is a different object, it is still a lock.

  1. Suppose a critical area is synchronously controlled by Lock lock. When a thread gets the Lock lock of a critical area and enters the critical area, the critical area is locked. Now the following statement is correct_____ D______.

A. If the thread executes thread in the critical area The sleep method will cause the thread to enter the blocking state, and the Lock of the critical area will be released; If the thread executes the await method of the condition object of Lock lock in the critical area, the thread will enter the blocking state, and the Lock in the critical area will be released

B. If the thread executes thread in the critical area The sleep method will cause the thread to enter the blocking state, and the Lock of the critical area will not be released; If the thread executes the await method of the condition object of Lock lock in the critical area, the thread will enter the blocking state, and the Lock in the critical area will not be released

C. If the thread executes thread in the critical area The sleep method will cause the thread to enter the blocking state, and the Lock of the critical area will be released; If the thread executes the await method of the condition object of Lock lock in the critical area, the thread will enter the blocking state, and the Lock in the critical area will not be released

D. If the thread executes thread in the critical area The sleep method will cause the thread to enter the blocking state, and the Lock of the critical area will not be released; If the thread executes the await method of the condition object of Lock lock in the critical area, the thread will enter the blocking state, and the Lock in the critical area will be released

sleep will not release the lock, but await will release the lock

Question and answer

1: There are three threads T1, T2 and T3. How to ensure that they are executed in the specified order: first execute T1, T2 after T1, T3 after T2, and the main thread ends after T3. Please give the schematic code.

T1.start();//Start t1 thread
T1.join();//Block the main thread and return after t1 execution
T2.start();//Start t2 thread
T2.join();//Block the main thread and return after t2 execution
T3.start();//Start t3 thread
T3.join();//Block the main thread and return after t3 execution

join will block the calling thread of the thread where it is located. Here is the main thread

Programming problem

  1. The following program is the definition of a generic container container < T >, which is an encapsulation of ArrayList and implements four public methods: add, remove, size and get.
class Container<T> {
  private List<T> elements = new ArrayList<>();
  /**
   * Add element
   *
   * @param e Elements to add
   */
  public void add(T e) {
    elements.add(e);
  }
  /**
   * Deletes the element with the specified subscript
   *
   * @param index Specifies the element subscript
   * @return Deleted element
   */
  public T remove(int index) {
    return elements.remove(index);
  }
  /**
   * Gets the number of elements in the container
   *
   * @return Number of elements
   */
  public int size() {
    return elements.size();
  }
  /**
   * Gets the element of the specified subscript
   *
   * @param index Specify subscript
   * @return Specifies the element of the subscript
   */
  public T get(int index) {
    return elements.get(index);
  }
}
public class ContainerWithLock<T> {
  private final ReentrantLock lock=new ReentrantLock();
  private List<T> elements = new ArrayList<>();
  /**
   * Add element
   *
   * @param e Elements to add
   */
  public void add(T e) {
    lock.lock();
    try {
      elements.add(e);
    }
    finally {
      lock.unlock();
    }
  }
  /**
   * Deletes the element with the specified subscript
   *
   * @param index Specifies the element subscript
   * @return Deleted element
   */
  public T remove(int index) {
    lock.lock();
    try {
      return elements.remove(index);
    }
    finally {
       lock.unlock();
    }
  }
  /**
   * Gets the number of elements in the container
   *
   * @return Number of elements
   */
  public int size() {
    lock.lock();
    try {
      return elements.size();
    }
    finally {
      lock.unlock();
    }
  }
  /**
   * Gets the element of the specified subscript
   *
   * @param index Specify subscript
   * @return Specifies the element of the subscript
   */
  public T get(int index) {
    lock.lock();
    try {
      return elements.get(index);
    }
    finally {
      lock.unlock();
    }
  }
}
package homework.ch30.P1;

import java.util.ArrayList;
import java.util.List;

public class SynchronizedContainer<T> {
  private List<T> elements = new ArrayList<>();
  /**
   * Add element
   *
   * @param e Elements to add
   */
  public synchronized void add(T e) {
    elements.add(e);
  }
  /**
   * Deletes the element with the specified subscript
   *
   * @param index Specifies the element subscript
   * @return Deleted element
   */
  public synchronized T remove(int index) {
    return elements.remove(index);
  }
  /**
   * Gets the number of elements in the container
   *
   * @return Number of elements
   */
  public synchronized int size() {
    return elements.size();
  }
  /**
   * Gets the element of the specified subscript
   *
   * @param index Specify subscript
   * @return Specifies the element of the subscript
   */
  public synchronized T get(int index) {
    return elements.get(index);
  }
}
  1. A thread safe synchronization queue syncqueue < T > is implemented to simulate the producer consumer mechanism in a multithreaded environment. The definition of syncqueue < T > is as follows:
/**
 * A thread safe synchronization queue simulates the producer consumer mechanism in a multithreaded environment
 * A producer thread generates elements to the queue through the produce method
 * A consumer thread consumes elements from the queue through the consume method
 * @param <T> Element type
 */
public class SyncQueue1<T> {
  /**
   * Save queue elements
   */
  private ArrayList<T> list = new ArrayList<>();
//TODO adds the required data members here
  private ReentrantLock lock=new ReentrantLock();
  private Condition newProduce=lock.newCondition();
  private Condition newConsume=lock.newCondition();
  /**
   * production data 
   *
   * @param elements The produced element list needs to be put into the queue
   * @throws InterruptedException
   */
  public void produce(List<T> elements) throws InterruptedException{
//TODO needs implementation code here
    lock.lock();
    try{
      while(!this.list.isEmpty()){//Queue is not empty, waiting
        newProduce.await();
      }
      System.out.println("Produce elements:"+elements.toString());
      this.list.addAll(elements);
      newConsume.signalAll();
    }
    finally {
      lock.unlock();
    }
  }
  /**
   * Consumption data
   *
   * @return Data taken from the queue
   * @throws InterruptedException
   */
  public List<T> consume() {
//TODO needs implementation code here
    lock.lock();
    try {
      while (this.list.isEmpty())
        newConsume.await();
      System.out.println("Consume elements:" + this.list.toString());
      List<T> tmpList = (List<T>) this.list.clone();
      this.list.clear();
      newProduce.signalAll();
      return tmpList;
    } catch (InterruptedException e) {
      e.printStackTrace();
      return null;
    } finally {
      lock.unlock();
    }
  }
}

There can be a return in try and catch, or a return in finally, or a return after finally.

  1. We know that JDK provides thread pool support. Thread pool can reduce the consumption caused by thread creation and destruction by reusing the created threads. However, once the thread is started, it cannot be started again after executing the thread task.
public class ReusableThread extends Thread{
  private Runnable runTask = null;  //Save accepted thread tasks
  //TODO joins required data members
  private ReentrantLock lock = new ReentrantLock();
  private Condition newTask = lock.newCondition();
  private Condition newSubmit = lock.newCondition();
  //Only constructors without parameters are defined
  public ReusableThread(){  }
  /**
   * Override the run method of Thread class
   */
  @Override
  public void run() {
    //This must be a cycle that never ends
    lock.lock();
    try{
      while(true){
        while(this.runTask==null)//Reminder: = = null cannot be saved. java is different from C + +
          newTask.await();
        this.runTask.run();
        this.runTask=null;
        newSubmit.signalAll();
      }
    }
    catch (InterruptedException e){
      e.printStackTrace();
    }
    finally {
      lock.unlock();
    }
  }
  /**
   * Submit a new task
   * @param task Tasks to submit
   */
  public void submit(Runnable task) {
    lock.lock();
    try {
      while(this.runTask!=null)
        newSubmit.await();
      this.runTask = task;
      newTask.signalAll();
    }
    catch (InterruptedException e){
      e.printStackTrace();
    }
    finally {
      lock.unlock();
    }
  }
}

A lock can have multiple condition objects. A condition object can only be executed again after being awakened by signal, and cannot obtain the lock.

Tags: Java Multithreading

Posted by Lord Sauron on Sat, 21 May 2022 02:19:26 +0300