(ZT) java lock mechanism

Repost address: http://blog.csdn.net/yangzhijun_cau/article/details/6432216

Before a synchronized code is executed by a thread, he must first obtain the permission to execute this code. In java, it is to obtain the lock of a synchronization object (an object has only one lock); if the lock of the synchronization object is obtained at this time Taken by other threads, he (this thread) can only wait (the thread is blocked in the lock pool waiting queue). After getting the lock, he starts to execute the synchronization code (code modified by synchronized); the thread returns the lock to the synchronization object immediately after executing the synchronization code, and some other thread waiting in the lock pool can get the lock and execute it Synchronized code. This ensures that only one thread is executing the synchronous code at the same time.

As we all know, in Java multi-threaded programming, a very important aspect is the synchronization of threads.
Regarding thread synchronization, there are generally the following solutions:

1. Add the synchronized keyword to the method signature of the method that needs to be synchronized.

2. Use the synchronized block to synchronize the code segment that needs to be synchronized.

3. Use the Lock object in the java.util.concurrent.lock package provided in JDK 5.

In addition, in order to solve the security problem that may occur when multiple threads access the same variable, we can not only use a synchronization mechanism, but also ensure better concurrency through ThreadLocal added in JDK 1.2.

In this article, the Java multi-thread synchronization mechanism will be discussed in detail, and ThreadLocal will be discussed.

The general directory structure is as follows:

1. First-come, first-served threads—Question: Why do we need multi-thread synchronization? What is the mechanism of Java multi-thread synchronization?
Second, give me a lock, I can create a rule - what are the traditional multi-threaded synchronization programming methods? What are their similarities and differences?
3. Lock is here, everyone gets out of the way - a detailed explanation of Lock in the Java concurrency framework.
4. You have it and I have it all - how does ThreadLocal solve concurrency security?
5. Summary - Comparison of several methods of Java thread safety.


1. The thread comes first and then arrives.

Let's take an example of Dirty: the bathroom of a restaurant is very small, and almost only one person can go to the toilet. In order to ensure undisturbed access, people who use the toilet must lock the door when entering the bathroom. We can think of the bathroom as a shared resource, and the multitude of people who need to go to the toilet can be thought of as multiple threads. If the bathroom is currently occupied, other people must wait until the person finishes using the toilet and opens the door to come out. This is like when multiple threads share a resource, they must be separated first.

Someone said: What if I don't have this door? Let two threads compete with each other, whoever goes first, whoever can work first, how good is this? But we know: if there is no door in the toilet, and the people who go to the toilet flock to the toilet together, then there will inevitably be disputes, the normal steps of going to the toilet will be disrupted, and unexpected results may occur, such as some people May have to be forced to fertilize in the wrong place...

It is precisely because of this door that anyone who enters the toilet alone can complete their toileting process smoothly without being disturbed or even having unexpected results. That is to say, when you go to the toilet, you should pay attention to first come first served.


So in a Java multithreaded program, when multiple threads compete for the same resource, how can we ensure that they will not "fight"? Some people say that the synchronization mechanism is used. Yes, like the example above, it is a typical synchronization case. Once the first person starts to go to the toilet, the second person must wait for the first one to finish before starting his toileting process. Once a thread enters a process, it must wait for a normal return and exit the process before the next thread can start the process. The most important thing here is the bathroom door. In fact, the door of the bathroom plays the role of a resource lock. As long as the person using the toilet locks the door, it is equivalent to obtaining the lock, and when he opens the lock, it is equivalent to releasing the lock.

That is to say, the thread synchronization mechanism of multithreading is actually controlled by the concept of locks. So in a Java program, how is the lock reflected?


Let's look at the concept of locks from the perspective of the JVM:

In the Java program runtime environment, the JVM needs to coordinate the data shared by two types of threads:
1) Instance variables stored in the heap
2) Stored in the method area Class variables

These two types of data are shared by all threads.
(Programs do not need to coordinate data stored on the Java stack. These data are private to the thread that owns the stack.)

In the Java virtual machine, each object and class is logically associated with a monitor of.
For objects, the associated monitor protects the object's instance variables.

For classes, monitors protect the class variables of the class.

(If an object has no instance variables, or a class has no variables, the associated monitor monitors nothing.) 
To implement the monitor's exclusive monitoring capabilities, the Java virtual machine associates a lock with each object and class. Represents a privilege that only one thread is allowed to have at any one time. Thread access to instance variables or class variables does not require locks.

But if a thread acquires a lock, no other thread can acquire a lock on the same data until it releases the lock. (To lock an object is to acquire the monitor associated with the object.)

Class locks are actually implemented with object locks. When the virtual machine loads a class file, it creates an instance of the java.lang.Class class. When locking an object, what is actually locked is the Class object of that class.

A thread can lock the same object multiple times. For each object, the Java virtual machine maintains a lock counter. Each time the thread acquires the object, the counter is incremented by 1, and each time it is released, the counter is decremented by 1. When the counter value is 0, the lock is completely released.

Java programmers do not need to add locks by themselves. Object locks are used internally by the Java virtual machine.

In a java program, you only need to use the synchronized block or synchronized method to mark a monitoring area. Every time you enter a monitoring area, the Java virtual machine will automatically lock the object or class.

Seeing this, I think you must be tired, right? o(∩_∩)o...lol. Let's take a break, but before that, please remember:
when a limited resource is shared by multiple threads, in order to ensure mutually exclusive access to the shared resource, we must give them a first come, first served. And to do this, object locking plays a very important role here.

在上一篇中,我们讲到了多线程是如何处理共享资源的,以及保证他们对资源进行互斥访问所依赖的重要机制:对象锁。



本篇中,我们来看一看传统的同步实现方式以及这背后的原理。



很多人都知道,在Java多线程编程中,有一个重要的关键字,synchronized。但是很多人看到这个东西会感到困惑:“都说同步机制是通过对象锁来实现的,但是这么一个关键字,我也看不出来Java程序锁住了哪个对象阿?“


没错,我一开始也是对这个问题感到困惑和不解。不过还好,我们有下面的这个例程:

  1. public class ThreadTest extends Thread {   
  2.     private int threadNo;   
  3.     public ThreadTest(int threadNo) {   
  4.         this.threadNo = threadNo;   
  5.     }   
  6.     public static void main(String[] args) throws Exception {   
  7.         for (int i = 1; i < 10; i++) {   
  8.            new ThreadTest(i).start();   
  9.             Thread.sleep(1);   
  10.         }   
  11.      }   
  12.     
  13.     @Override  
  14.      public synchronized void run() {   
  15.         for (int i = 1; i < 10000; i++) {   
  16.             System.out.println("No." + threadNo + ":" + i);   
  17.         }   
  18.      }   
  19.  }   

      这个程序其实就是让10个线程在控制台上数数,从1数到9999。理想情况下,我们希望看到一个线程数完,然后才是另一个线程开始数数。但是这个程序的执行过程告诉我们,这些线程还是乱糟糟的在那里抢着报数,丝毫没有任何规矩可言。
     但是细心的读者注意到:run方法还是加了一个synchronized关键字的,按道理说,这些线程应该可以一个接一个的执行这个run方法才对阿。
     但是通过上一篇中,我们提到的,对于一个成员方法加synchronized关键字,这实际上是以这个成员方法所在的对象本身作为对象锁。在本例中,就是 以ThreadTest类的一个具体对象,也就是该线程自身作为对象锁的。一共十个线程,每个线程持有自己 线程对象的那个对象锁。这必然不能产生同步的效果。换句话说,如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的! 

我们来看下面的例程:

  1. public class ThreadTest2 extends Thread {   
  2.  private int threadNo; private String lock;   
  3.  public ThreadTest2(int threadNo, String lock) {   
  4.   this.threadNo = threadNo;   
  5.      this.lock = lock;   }   
  6. public static void main(String[] args) throws Exception {   
  7.    String lock = new String("lock");   
  8.      for (int i = 1; i < 10; i++) {     
  9.   new ThreadTest2(i, lock).start();   
  10.       Thread.sleep(1);   
  11.      }   
  12.   }     
  13. public void run() {     
  14.  synchronized (lock) {   
  15.       for (int i = 1; i < 10000; i++) {   
  16.        System.out.println("No." + threadNo + ":" + i);   
  17.     }      
  18.  }     
  19.  }   
  20.  }  

 

        我们注意到,该程序通过在main方法启动10个线程之前,创建了一个String类型的对象。并通过ThreadTest2的构造函数,将这个对象赋值 给每一个ThreadTest2线程对象中的私有变量lock。根据Java方法的传值特点,我们知道,这些线程的lock变量实际上指向的是堆内存中的 同一个区域,即存放main函数中的lock变量的区域。
        程序将原来run方法前的synchronized关键字去掉,换用了run方法中的一个synchronized块来实现。这个同步块的对象锁,就是 main方法中创建的那个String对象。换句话说,他们指向的是同一个String类型的对象,对象锁是共享且唯一的!

于是,我们看到了预期的效果:10个线程不再是争先恐后的报数了,而是一个接一个的报数。

再来看下面的例程:

1 public class ThreadTest3 extends Thread {   

  •  2    

 

  •  3     private int threadNo;   
  •  4     private String lock;   
  •  5    
  •  6     public ThreadTest3(int threadNo) {   
  •  7         this.threadNo = threadNo;   
  •  8     }   
  •  9    
  • 10     public static void main(String[] args) throws Exception {   
  • 11        
  • 12         for (int i = 1; i < 20; i++) {   
  • 13             new ThreadTest3(i).start();   
  • 14             Thread.sleep(1);   
  • 15         }   
  • 16     }   
  • 17    
  • 18     public static synchronized void abc(int threadNo) {   
  • 19         for (int i = 1; i < 10000; i++) {   
  • 20               
  • 21                 System.out.println("No." + threadNo + ":" + i);           
  • 22         }   
  • 23     }   
  • 24    
  • 25     public void run() {   
  • 36         abc(threadNo);   
  • 27     }   
  • 28 }  

    细心的读者发现了:这段代码没有使用main方法中创建的String对象作为这10个线程的线程锁。而是通过在run方法中调用本线程中一个静态的同步 方法abc而实现了线程的同步。我想看到这里,你们应该很困惑:这里synchronized静态方法是用什么来做对象锁的呢?



    我们知道,对于同步静态方法,对象锁就是该静态放发所在的类的Class实例,由于在JVM中,所有被加载的类都有唯一的类对象,具体到本例,就是唯一的 ThreadTest3.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!



    这样我们就知道了:

    1、对于同步的方法或者代码块来说,必须获得对象锁才能够进入同步方法或者代码块进行操作;


    2、如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的
    Class对象(唯一);


    3、对于代码块,对象锁即指synchronized(abc)中的abc;


    4、因为第一种情况,对象锁即为每一个线程对象,因此有多个,所以同步失效,第二种共用同一个对象锁lock,因此同步生效,第三个因为是
    static因此对象锁为ThreadTest3的class 对象,因此同步生效。

    如上述正确,则同步有两种方式,同步块和同步方法(为什么没有wait和notify?这个我会在补充章节中做出阐述)

    如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单态模式才生效;
    (本类的实例有且只有一个)

    如果是同步方法,则分静态和非静态两种 。

    静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。

    所以说,在Java多线程编程中,最常见的synchronized关键字实际上是依靠对象锁的机制来实现线程同步的。
    我们似乎可以听到synchronized在向我们说:“给我一把 锁,我能创造一个规矩”。

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326776908&siteId=291194637
Recommended