synchronized 关键字,用来控制线程同步的,保证我们的线程在多线程的环境下,不被多个线程同时执行,确保我们数据的完整性。
同步方法和同步代码块操作:
private synchronized void add(){ count ++; }
private void add(){ synchronized (lock){ count ++; } }
同步方法中默认使用的是this或者当前类对象作为锁;是由于java的每一个对象都有一个内置锁,用synchronized修饰的方法,内置锁会保护整个方法。在调用方法之前需要获取到这个内置锁,否则就会处于阻塞的状态。
注意:synchronized修饰静态方法,将锁住整个类。
同步代码块中可以选择使用对象来作为锁,灵活性就比较的大,可以是类锁、static修饰的对象锁、普通对象锁;因此,synchronized(lock)是基于lock这个对象的生命周期来控制锁的粒度。
注意:加锁的关键在于锁对象,需要对多个线程共享。
在Java SE 1.6之前为重量级锁。在Java SE 1.6版本就已经对synchronized进行来各种的优化,因为在大部分的情况下,加锁的代码并不存在多线程的竞争,为了减少获得锁和释放锁带来的性能消耗而引入来偏向锁和轻量级锁。在synchronized中,锁存在四种状态,分别是:无锁、偏向锁、轻量级锁,重量级锁。锁的状态根据竞争的激烈程度来从低到高不断的升级。
可以看出同步关键在与lock对象,为什么lock对象可以作为同步锁呢?因此就需要知道对象在内存中是如何布局的
对象在内存中的布局
在JVM虚拟机(hotpot虚拟机)中,对象在内存中的布局可以分为三个区域:对象头(header)、实例数据(Instance Data)、对齐填充(Padding):
Jvm源码实现:
当我们使用new创建一个对象实例当时候,JVM层面实际上会创建一个instanceOopDesc对象。
Hotspot虚拟机采用OOP-Klass 模型来描述Java对象实例,OOP(Ordinary Object Point)指的是普通对象指针,Klass用来描述对象实例的具体类型。Hotspot采用instanceOopDesc和arrayOopDesc来描述对象头,arrayOopDesc对象来描述数组类型。
#ifndef SHARE_VM_OOPS_INSTANCEOOP_HPP #define SHARE_VM_OOPS_INSTANCEOOP_HPP #include "oops/oop.hpp" // An instanceOop is an instance of a Java Class // Evaluating "new HashTable()" will create an instanceOop. class instanceOopDesc : public oopDesc { public: // aligned header size. static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; } // If compressed, the offset of the fields of the instance may not be aligned. static int base_offset_in_bytes() { // offset computation code breaks if UseCompressedClassPointers // only is true return (UseCompressedOops && UseCompressedClassPointers) ? klass_gap_offset_in_bytes() : sizeof(instanceOopDesc); } static bool contains_field_offset(int offset, int nonstatic_field_size) { int base_in_bytes = base_offset_in_bytes(); return (offset >= base_in_bytes && (offset-base_in_bytes) < nonstatic_field_size * heapOopSize); } }; #endif // SHARE_VM_OOPS_INSTANCEOOP_HPP
class oopDesc { friend class VMStructs; private: volatile markOop _mark; union _metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata; }
从以上源码中可以看出InstanceOopDesc继承了OopDesc,并且在普通实例对象中,创建了_mark和_metadata两个成员。
class markOopDesc: public oopDesc { private: // Conversion uintptr_t value() const { return (uintptr_t) this; } public: // Constants enum { age_bits = 4, lock_bits = 2, biased_lock_bits = 1, max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits, hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits, cms_bits = LP64_ONLY(1) NOT_LP64(0), epoch_bits = 2 }; }
_mark的类型为markOop,表示对象标记;
_metadata 表示类元信息,类元信息中存储的是对象指向它的类元数据Klass 的首地址,_klass表示普通指针,_compressed_klass表示压缩类指针。
Marker word 记录类对象和锁有关的信息,当某个对象被synchronized关键字当成同步锁的时候,围绕这个锁的一系列的操作都和Mark word有关。Mark word在32位的虚拟机中的长度为32bit,在64位的虚拟机中的长度为64bit。
Mark word 里面存储的数据会随着锁标志位的变化而变化,它存储的数据可能存在以下5种情况:
为什么任何的对象都可以实现锁?
1、Java中的每个对象都派生自Object类,而每个Java Object 在JVM内部都有一个native的C++对象oop/oopDesc进行对应。
2、线程在获取锁的时候,实际上就是获得一个监视器对象monitor(可以认为是一个同步对象),所有的Java对象是天生携带monitor,在多个线程访问同步代码块的时候,相当于去争抢对象的监视器,修改对象中锁标识。
ObjectMonitor* monitor() const { assert(has_monitor(), "check"); // Use xor instead of &~ to provide one extra tag-bit check. return (ObjectMonitor*) (value() ^ monitor_value); }
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/290285.html