How many different ways do you create threads? Which one do you like? Why?
There are three ways to create threads:
-
Inherit Thread class
-
Implement Runnable interface
-
Applications can use the Executor framework to create thread pools
Implementing the Runnable interface is more popular because it does not need to inherit the Thread class. When other objects have been inherited in the application design, this requires multiple inheritance (Java does not support multiple inheritance) and can only implement the interface. At the same time, Thread pool is also very efficient and easy to implement and use.
Explain several available states of threads
1. New: creates a new thread object;
2. Runnable: after a thread object is created, other threads (such as the main thread) call the start () method of the object. The thread in this state is located in the runnable thread pool, waiting to be selected by thread scheduling to obtain CPU usage rights;
3. running: the runnable thread obtains the CPU time slice and executes the program code;
4. block: the blocking state refers to that the thread gives up the CPU usage right for some reason, that is, it gives up the CPU timeslice and temporarily stops running. It is not possible to get the CPU timeslice to the running state again until the thread enters the runnable state.
There are three types of blocking:
- Wait blocking: the running thread executes the o.wait () method, and the JVM will put the thread into the waiting queue.
- Synchronization blocking: when a running thread obtains the synchronization lock of an object, if the synchronization lock is occupied by another thread, the JVM will put the thread into the lock pool.
- Other blocking: when a running thread executes the Thread.sleep(long ms) or t.join() method, or makes an I/O request, the JVM will set the thread to the blocking state. When the sleep() state times out, the join() waits for the thread to terminate or time out, or the I/O processing is completed, the thread will return to the runnable state.
5. Dead: when the execution of the run() and main() methods of a thread ends, or the run() method exits due to an exception, the thread ends its life cycle. A dead thread cannot be reborn again.
What is the difference between synchronous methods and synchronous code blocks?
-
The synchronization method uses this or the current class object as the lock by default;
-
The synchronization code block can choose what to lock, which is more granular than the synchronization method. We can choose to synchronize only part of the code that will have synchronization problems rather than the whole method;
How is thread synchronization done inside the monitor? What level of synchronization should the program do?
Monitors and locks are used together in the Java virtual machine. Monitors monitor a synchronized code block to ensure that only one thread executes the synchronized code block at a time. Each monitor is associated with an object reference. Threads are not allowed to execute synchronized code until they acquire the lock.
java also provides two locking schemes: explicit monitor (Lock) and implicit monitor (synchronized).
What is a deadlock?
A deadlock occurs when two or more threads are waiting for each other to finish executing before proceeding. As a result, these threads are trapped in an infinite wait.
A classic deadlock Code:
public class DeadLock { public static final String LOCK_1 = "lock1"; public static final String LOCK_2 = "lock2"; public static void main(String[] args) { Thread threadA = new Thread(() -> { try { while (true) { synchronized (DeadLock.LOCK_1) { System.out.println(Thread.currentThread().getName() + " lock up lock1"); Thread.sleep(1000); synchronized (DeadLock.LOCK_2) { System.out.println(Thread.currentThread().getName() + " lock up lock2"); } } } } catch (Exception e) { e.printStackTrace(); } }); Thread threadB = new Thread(() -> { try { while (true) { synchronized (DeadLock.LOCK_2) { System.out.println(Thread.currentThread().getName() + " lock up lock2"); Thread.sleep(1000); synchronized (DeadLock.LOCK_1) { System.out.println(Thread.currentThread().getName() + " lock up lock1"); } } } } catch (Exception e) { e.printStackTrace(); } }); threadA.start(); threadB.start(); } }
As shown in the above code, we started two threads. In each thread, we need to obtain DeadLock.LOCK_1 and DeadLock.LOCK_2, where
- threadA, obtain DeadLock.LOCK_1 first, and then DeadLock.LOCK_2
- threadB, obtain DeadLock.LOCK_2 first, and then DeadLock.LOCK_1
In this way, when threadA obtains DeadLock.LOCK_1, it needs to obtain DeadLock.LOCK_2, and DeadLock.LOCK_2 is obtained by threadB first. Therefore, threadA needs to wait for threadB to release DeadLock.LOCK_2 before continuing to execute; however, threadB waits for threadB to release DeadLock.LOCK_1 after obtaining DeadLock.LOCK_2, so this is formed "Cycle waiting condition", thus forming a deadlock. To solve this deadlock, we just need to make threadA and threadB obtain DeadLock.LOCK_1 and DeadLock.LOCK_2 in the same order, for example:
public class DeadLock { public static final String LOCK_1 = "lock1"; public static final String LOCK_2 = "lock2"; public static void main(String[] args) { Thread threadA = new Thread(() -> { try { while (true) { synchronized (DeadLock.LOCK_1) { System.out.println(Thread.currentThread().getName() + " lock up lock1"); Thread.sleep(1000); synchronized (DeadLock.LOCK_2) { System.out.println(Thread.currentThread().getName() + " lock up lock2"); } } } } catch (Exception e) { e.printStackTrace(); } }); Thread threadB = new Thread(() -> { try { while (true) { synchronized (DeadLock.LOCK_1) { System.out.println(Thread.currentThread().getName() + " lock up lock1"); Thread.sleep(1000); synchronized (DeadLock.LOCK_2) { System.out.println(Thread.currentThread().getName() + " lock up lock2"); } } } } catch (Exception e) { e.printStackTrace(); } }); threadA.start(); threadB.start(); } }
In addition, another solution is to make the values of DeadLock.LOCK_1 and DeadLock.LOCK_2 the same, for example:
public static final String LOCK_1 = "lock"; public static final String LOCK_2 = "lock";
Why? Because strings have a constant pool, if different threads hold string locks with the same characters, the two locks are actually the same lock.
How to ensure that N threads can access N resources without causing deadlock?
Four necessary conditions for multithreading to generate Deadlock:
-
Mutex condition: a resource can only be used by one thread at a time.
-
Hold and request conditions: when a thread is blocked by requesting resources, it will hold on to the obtained resources.
-
Unalienability: the thread has obtained resources and cannot be deprived until the use is completed.
-
Loop waiting condition (closed loop): a loop waiting resource relationship is formed between several threads.
Deadlock can be avoided by breaking any of these conditions
A very simple way to avoid deadlock is to specify the order of obtaining locks and force threads to obtain locks in the specified order. Therefore, if all threads add and release locks in the same order, there will be no deadlock.