一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。
关于线程的同步,一般有以下解决方法:
1. 在需要同步的方法的方法签名中加入synchronized关键字。
2. 使用synchronized块对需要进行同步的代码段进行同步。
3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。
让我们从JVM的角度来看看锁这个概念:
在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:
1)保存在堆中的实例变量
2)保存在方法区中的类变量
这两类数据是被所有线程共享的。
(程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)
在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。
对于对象来说,相关联的监视器保护对象的实例变量。
对于类来说,监视器保护类的类变量。
(如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)
为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。
但是如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)
类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。
这样我们就知道了:
1、对于同步的方法或者代码块来说,必须获得对象锁才能够进入同步方法或者代码块进行操作;
2、如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的
Class对象(唯一);
3、对于代码块,对象锁即指synchronized(abc)中的abc;
4、因为第一种情况,对象锁即为每一个线程对象,因此有多个,所以同步失效,第二种共用同一个对象锁lock,因此同步生效,第三个因为是
static因此对象锁为ThreadTest3的class 对象,因此同步生效。
如上述正确,则同步有两种方式,同步块和同步方法(为什么没有wait和notify?这个我会在补充章节中做出阐述)
如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单态模式才生效;
(本类的实例有且只有一个)
如果是同步方法,则分静态和非静态两种 。
静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。
所以说,在Java多线程编程中,最常见的synchronized关键字实际上是依靠对象锁的机制来实现线程同步的。
我们似乎可以听到synchronized在向我们说:“给我一把 锁,我能创造一个规矩”。
牧码仁 CSDN