Multithreading Basics (3) - Initial Lock

1. The concept of lock

    Locks are used to control the way multiple threads access shared resources. Generally speaking, a lock can prevent multiple threads from accessing shared resources at the same time (but some locks can allow multiple threads to access shared resources concurrently, such as read-write locks). When a resource is operated by a thread, a lock will be added to the resource, and other operating threads will be blocked while the lock is not released.

2. Why do we need locks?

    When multiple threads access the same resource, there will be thread safety issues. For example, when you are sitting at the table with chopsticks and you are about to pick up the last piece of food, you are suddenly picked up by the person next to you. Food, you can no longer operate at this time, chopsticks are equivalent to CPU time slices, and food is equivalent to shared resources, so some unforeseen exceptions will occur in the program at this time.

    Take the following ticket purchase procedure as an example, if ticket=1, when thread t0 executes to the switch point, it loses the CPU time slice and switches to t1, but when t1 executes to the switch point, it also loses the CPU time slice, and the thread switches to t2 smoothly Running, ticket=0, after t2 runs, switch back to t0 to continue running, ticket=-1, switch to t1 thread, ticket=-2, the final result will make the number of tickets negative, which is obviously wrong. Although this does not necessarily happen, it must happen, so thread safety is also called thread hidden danger.

public class Demo6 {
	//线程安全例子,以售票为例
	public static void main(String[] args) {
		sell s=new sell();
		Thread t0=new Thread(s, "线程1");
		Thread t1=new Thread(s, "线程2");
		Thread t2=new Thread(s, "线程3");
		t0.start();
		t1.start();
		t2.start();
	}
}
class sell implements Runnable{
	private int ticket=100;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			if(ticket>0){
                //切换点
				System.out.println("当前"+Thread.currentThread().getName()+"以出售1张票,"+"剩余"+(--ticket));
			}else{
				break;
			}
		}
		
	}
	
}

3. Implementation of the lock

    3.1 synchronized

        The code form is: synchronized(obj){ do work }, the above ticket code can be changed to

public class Demo11 {
	public static void main(String[] args) {
		TicketSell t=new TicketSell();
		Thread t1=new Thread(new Run(t),"线程1");
		Thread t2=new Thread(new Run(t),"线程2");
		Thread t3=new Thread(new Run(t),"线程3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketSell{
	private Integer num=100;
	public void sell(){
		num--;
	}
	public int getNum(){
		return num;
	}
}
class Run implements Runnable{
	private TicketSell t;
	//private Object obj;//也可以通过这个对象来获取锁
	public Run(TicketSell t){
		this.t=t;
	}
	@Override
	public void run() {
		while (true) {
			// synchronized (obj)
			synchronized (t) {
				if (t.getNum() > 0) {
					t.sell();
					System.out.println(Thread.currentThread().getName() + "出售一张");
					System.out.println("当前剩余" + t.getNum());
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else {
					break;
				}
			}
		}

	}
	
}

    The obj in synchronized(obj) can be any object, but it must be an object and not a basic type of data, obj is called a synchronization lock, and in terms it should be an object monitor. But we can encapsulate the shared resource as an object, then acquire the lock through the object, and access the resource object by calling the method in the synchronized code block.

        3) Synchronization method:

        Just add synchronized before the return type of the method. The lock in the synchronization method is the lock of the current instance object, which is this. However, for static synchronization methods, the lock is the lock of the Class object of the current class. If a thread task calls this method, another thread cannot call other static synchronization methods of the same class. The lock of the static synchronization method can only come from the class where it belongs. The Class object, that is, can only be a static synchronized method or synchronized code block synchronized(classname.class) { do work }.

public class Demo12 {
	public static void main(String[] args) {
		TicketSell1 t=new TicketSell1();
		Thread t1=new Thread(new Run1(t),"线程1");
		Thread t2=new Thread(new Run1(t),"线程2");
		Thread t3=new Thread(new Run1(t),"线程3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketSell1{
	private Integer num=100;
	public synchronized void sell(){
		if(num>0){
			num--;
			System.out.println(Thread.currentThread().getName() + "出售一张");
			System.out.println("当前剩余" + num);
			try {
				Thread.sleep(100);// 可以增大线程切换的概率
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	public synchronized int getNum(){
		return num;
	}
}
class Run1 implements Runnable{
	private TicketSell1 t;
	public Run1(TicketSell1 t){
		this.t=t;
	}
	@Override
	public void run() {
		while (true) {
			t.sell();
		}

	}
	
}

        Note: For the object that needs to be locked, it must be the packaging type and cannot be a basic type, because we need to obtain the lock it holds through the object, and it should be set to private, because the lock cannot prevent thread tasks from directly accessing the domain object to modify the value.

    3.2 Lock interface explicit lock

    It provides synchronization functionality similar to the synchronized keyword, but requires explicit acquisition and release of locks when used. Although it lacks the convenience of implicitly acquiring and releasing locks (provided by synchronized blocks or methods), it has a variety of synchronized keys such as the operability of lock acquisition and release, interruptible acquisition of locks, and timeout acquisition of locks. Synchronization characteristics that words do not have.

        1) How to use the lock lock:

public class Demo13 {
	public static void main(String[] args) {
		TicketSell2 t=new TicketSell2();
		Thread t1=new Thread(new Run2(t),"线程1");
		Thread t2=new Thread(new Run2(t),"线程2");
		Thread t3=new Thread(new Run2(t),"线程3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class TicketSell2{
	private Integer num=100;
	public void sell(){
			num--;
	}
	public int getNum(){
		return num;
	}
}
class Run2 implements Runnable{
	private TicketSell2 t;
	private Lock lock=new ReentrantLock();
	private Condition con1=lock.newCondition();
	public Run2(TicketSell2 t){
		this.t=t;
	}
	@Override
	public void run() {
		while (true) {
			lock.lock();
			try {
				while (t.getNum()<=0) {
					con1.await();// 调用此方法阻塞当前线程,并且释放锁,便可以让另一个线程来对票数进行补充
				}
				t.sell();
				System.out.println(Thread.currentThread().getName() + "出售一张");
				System.out.println("当前剩余" + t.getNum());
				Thread.sleep(100);// 可以增大线程切换的概率
				con1.signalAll();//若票数充足则唤醒所有的被阻塞线程
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}

	}
	
}

       The release of the lock in finally is to ensure that the lock can be released after the lock is acquired, and the process of acquiring the lock should not be written in the try block, because if an exception occurs when the lock (the implementation of the custom lock) is acquired, the exception is thrown. At the same time, it will also cause the lock to be released for no reason. It must be ensured that the return statement occurs in the try clause to ensure that unlock() does not occur prematurely, exposing the data to the second task.

        2) Lock provides some features that synchronized does not have:

        3) Related APIs:

        4) Condition object:

Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();

	public void conditionWait() throws InterruptedException {
		lock.lock();
		try {
			condition.await();
		} finally {
			lock.unlock();
		}
	}

	public void conditionSignal() throws InterruptedException {
		lock.lock();
		try {
			condition.signal();
		} finally {
			lock.unlock();
		}
	}

     The condition object is obtained through the newCondition() method of the Lock object. When the condition object in thread A calls the await() method, it will enter the waiting set for the condition. Even if other threads have released the lock, he will still be in a blocked state. Until a thread uses the same condition object to perform the signalAll() operation (the signal() method is also possible, but this method randomly unblocks a thread in the waiting set), making thread A out of the blocking state, but not necessarily will run immediately, and will continue from where it was last run until it acquires the lock again. But this may also bring a problem, which is deadlock, because thread A depends on other threads to wake up, and if there is no thread to wake up, it will cause deadlock. Detailed API of Condition

    3.3 Thread Local Storage

        1) Concept: Thread local storage is an automated mechanism that works by eradicating the sharing of variables, creating a different storage for each different thread that uses the same variable. For example, if 5 threads all use the object represented by variable x, then local storage will generate 5 different memory blocks for x. ThreadLocal, the thread variable, is a storage structure with a ThreadLocal object as a key and any object as a value. This structure is attached to the thread, which means that a thread can query a value bound to the thread based on a ThreadLocal object. Through these values, we can see it as a state representation of the thread, which is exclusive to each thread and is not affected by other threads.

public class ThreadLocalHolder {
	private static final ThreadLocal<Long> time = new ThreadLocal<Long>() {
		protected Long initialValue() {
			return System.currentTimeMillis();
		}
	};
	public static final void begin() {
		time.set(System.currentTimeMillis());
	}
	public static final long end() {
		return System.currentTimeMillis() - time.get();
	}
	public static void main(String[] args) {
		ThreadRunTime t1=new ThreadRunTime();
		ThreadRunTime t2=new ThreadRunTime();
		t1.start();
		t1.start();
	}
}
class ThreadRunTime extends Thread{
	@Override
	public void run() {
		try {
			ThreadLocalHolder.begin();
			Thread.sleep(1000);
			Thread.yield();
			System.out.println(getName()+ThreadLocalHolder.end());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

        So what if you don't use thread variables? If the thread variable is not used, that is, the type of the time variable is directly set to Long. If the t1 thread is switched to the t2 thread to run during the thread sleep or the thread is switched, when the t2 runs and then returns to the t1 thread, call ThreadLocalHolder.end(). What is obtained is the running time of thread t2. Therefore, from another point of view, the thread variable ThreadLocal also avoids the competition for shared resources, but this method cannot achieve synchronization, so we can regard the objects contained in ThreadLocal as the state of the thread.

    3.4 Deadlock, Livelock, Starvation

    To understand deadlock and livelock, you only need to grasp two points:

    1) Deadlock is when two threads both hold the lock required by the other's lock and never release it, waiting for the other to release the lock, that is, without giving in to each other. For example, thread 1 already holds lock A and needs lock B to continue running, while thread 2 holds lock B and needs lock A to continue running. However, thread 1 will not release lock A, and thread 2 will not release lock B, resulting in thread Getting stuck in an infinite wait can lead to a deadlock. Ways to avoid deadlock:

  • Avoid a thread acquiring multiple locks at the same time. 
  • Avoid a thread occupying multiple resources in the lock at the same time, and try to ensure that each lock only occupies one resource. 
  • Try to use a timed lock, use lock.tryLock(timeout) instead of using the internal locking mechanism.
  • For database locks, locking and unlocking must be in a database connection, otherwise the unlocking failure will occur.

    2) Livelock means that thread 1 can use the resource, but it is polite, let other threads use the resource first, and thread 2 can also use the resource, but it is gentleman and also let other threads use the resource first. In this way, you let me, I let you, and the last two threads cannot use resources, that is, give way to each other.

    3) Starvation means that the CPU time slice of a thread is completely preempted by other threads and all CPU time slices cannot be run due to the following reasons.

  • High-priority threads eat up all the CPU time of low-priority threads.
  • The thread is permanently blocked in a state waiting to enter a synchronized block.
  • A thread is waiting on an object that itself is also waiting forever to complete (such as calling the object's wait method).

    3.5 Summary

        Locks in Java can avoid thread safety problems caused by multi-thread competition for the same resource and achieve synchronization. It must be remembered that locks in Java all come from objects, the synchronized synchronization code block must be given an object that needs to be synchronized, that is, the shared resource object, and the synchronized synchronization method is actually equivalent to the synchronized code in the form of synchronized(this). Block, Lock display lock is also similar to synchronized synchronization code block, but Lock display lock acquires the lock directly through itself, and has some more features than synchronized.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325482575&siteId=291194637