package BingFaBianCheng.bingFaBianCheng3.test;
import BingFaBianCheng.bingFaBianCheng3.entity.L;
import BingFaBianCheng.bingFaBianCheng3.util.CASLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "enjoy")
public class Test1 {
Thread t1;
Thread t2;
CASLock c =new CASLock();
int k =0;
L l = new L();
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
test1.start();
}
public void start() throws InterruptedException {
t1 = new Thread(){
@Override
public void run() {
sum();
}
};
t2 = new Thread(){
@Override
public void run() {
sum();
}
};
t1.start();
t2.start();
//阻塞
t1.join();
t2.join();
//两个线程执行10000次加一,最后正确的结果应该是两万
System.out.println(k);
}
public void sum(){
/**
* 锁 对象当中的一个标识
* 加锁:就是去改变这个对象的标识的值
* 加锁成功:让方法正常返回
* 加锁失败:让失败的这个线程 死循环 阻塞
*/
// c.lock();
for (int i = 0; i <= 9999; i++) {
k = k + 1;
}
// c.unlock();
}
//12月份之前 都不能有多个线程访问 sun
}
// 当t1线程来时,k=9,中k=9+1
// 但是t1线程还没有把结果更新到主存中时,t2线程来了,也加一,最后实际只加了一次
// 所以最后的结果一定是小于20000的
k = k + 1;
package BingFaBianCheng.bingFaBianCheng3.util;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicBoolean;
public class CASLock {
private volatile int status=0;//标识---是否有线程在同步块-----是否有线程上锁成功
//获取Unsafe对象,只能这么获取,Unsafe这个类比较难有兴趣同学可以自己研究研究
//窃以为Unsafe是java里面最牛逼的一个对象,没有之一
private static final Unsafe unsafe = getUnsafe();
//定义一个变量来记录 volatile int status的地址
//因为CAS需要的是一个地址,于是就定义这个变量来标识status在内存中的地址
private static long valueOffset = 0;
/**
* 初始化的获取status在内置的偏移量
* 说白了就是status在内存中的地址
* 方便后面对他进行CAS操作
*/
static {
try {
valueOffset = unsafe.objectFieldOffset
(CASLock.class.getDeclaredField("status"));
} catch (Exception ex) {
throw new Error(ex); }
}
/**
* 加锁方法
*/
public void lock(){
/**
* 判断status是否=0;如果等于0则改变成为1
* 而判断赋值这两个操作可以通过一个叫做CAS的技术来完成
* 通过cas去改变status的值,如果是0就改成1
* 思考一下为什么要用CAS
* 关于CAS如果你不了解可以先放放,后面我们讲
* 目前就认为CAS用来赋值的和 = 的效果一样
*/
while(!compareAndSet(0,1)){
//加锁失败会进入到这里空转
}
// 如果加锁成功则直接正常返回
}
//为什么unlock不需要CAS呢?可以自己考虑一下,如果不懂可以讨论
public void unlock(){
//只有修改值这个操作,就是原子性的
status=0;
}
boolean compareAndSet(int except,int newValue){
// 这行代码是原子性的,只有一条被操作系统执行,不会被拆分成几条指令
//如果 valueOffset或者 status这个变量 = except 那么改成 newValue
return unsafe.compareAndSwapInt(this,valueOffset,except,newValue);
}
/**
* 获取Unsafe对象
* @return
*/
public static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe)field.get(null);
} catch (Exception e) {
}
return null;
}
}
package BingFaBianCheng.bingFaBianCheng3.test;
import BingFaBianCheng.bingFaBianCheng3.entity.L;
import BingFaBianCheng.bingFaBianCheng3.util.CASLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "enjoy")
public class Test1 {
Thread t1;
Thread t2;
int k =0;
L l = new L();
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
test1.start();
}
public void start() throws InterruptedException {
t1 = new Thread(){
@Override
public void run() {
sum();
}
};
t2 = new Thread(){
@Override
public void run() {
sum();
}
};
t1.start();
t2.start();
//阻塞
t1.join();
t2.join();
//两个线程执行10000次加一,最后正确的结果应该是两万
System.out.println(k);
}
public void sum(){
/**
* 锁 对象当中的一个标识
* 加锁:就是去改变这个对象的标识的值
* 加锁成功:让方法正常返回
* 加锁失败:让失败的这个线程 死循环 阻塞
*/
//业内一般叫做锁对象
//synchronized究竟改变了l对象的什么东西
synchronized (l) {
for (int i = 0; i <= 9999; i++) {
k = k + 1;
}
}
}
}
synchronized锁的是L对象还是代码块?
synchronized究竟改变了L对象的什么东西?
对象由对象头、实例数据、对齐填充组成
// 定义一个对象User
// User这个对象有多大?
// ClassLayout.parseInstance(u).toPrintable()这条命令可以得到u对象的大小(需要添加依赖openjdk.jol)
//
User u = new User;
// 第一行
A object internals:
// 第二行
OFFSET SIZE TYPE DESCRIPTION VALUE
// 第三行
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
// 第四行
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
// 第五行---对象头是12字节,1个字节等于8个byte,总共是96byte
//
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
// 第六行---boolean类型对象是1个字节
12 1 boolean A.f false
// 第七行---对齐填充,使得最后的结果是8的倍数
13 3 (loss due to the next object alignment)
// 第八行
Instance size: 16 bytes
// 第九行
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
mark word的8位存储的内容不是固定的
无锁可偏向(无hash)101与无锁不可偏向(有hash)001结构一样
前25位没用,hash:31,前56(25+31)位存的是hashcode,hashcode()也是一个native方法,需要调用后才有hashcode值;
5432(打印hashcode的16进制,刚好等于对象头第五字节+第四字节+第三字节+第二字节拼起来的结果)
为什么会有前25位,是因为小端存储,小端存储指高字节存储到高地址,低字节存储到低地址,所以打印出来是反过来的,所以第八个字节+第七个字节+第六个字节+第五个字节第一位肯定都是0,
package BingFaBianCheng.bingFaBianCheng3.util;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class HashUtil {
public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
// 手动计算HashCode
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long hashCode = 0;
for (long index = 7; index > 0; index--) {
hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index-1)*8);
}
String code = Long.toHexString(hashCode);
System.out.println("util‐‐‐‐‐‐‐‐‐‐‐0x"+code);
}
}
第一个字节共8位(01—十六进制表示法)— 第一位没有用到;第二位到第五位表示分代年龄;第六位表示bl,指偏向标识,即是否可偏向,0表示不可偏向;第七位和第八位记录锁的状态,指示是偏向锁(01)、轻量锁、重量锁、gc等等
第一个字节最后三位是001时,表示当前对象锁状态是偏向锁,但是不可偏向;如果没有hash,但是前三位也是001时,说明开启了偏向延迟,jvm默认是4秒钟之内任何线程都不可偏向,关闭偏向延迟后,第一个字节的后三位变成101,同时没有hash值
偏向延迟关闭命令 -XX:BiasedLockingStartupDelay=0
第一个字节最后三位是001时,表示当前对象锁状态是偏向锁,但是不可偏向,第一个字节最后三位是101时,表示可偏向并且可持有的锁是偏向锁
为什么加了hash就不可偏向呢?— 因为计算了hashcode,无法存放持有锁线程的线程ID,发生了冲突
偏向锁已偏向(101)
轻量锁(00)
重量锁(10)
gc标记(11)
如果执行力hashcode方法,线程的对象头存的是001,01表示偏向锁,但是没有获得,第一个0表示不可偏向,即不可偏向的没有持有的偏向锁
没有计算hashcode—偏向锁打开的情况下,当一把锁第一次被线程持有的时候,是偏向锁,如果这个线程再次加锁还是偏向锁,如果别的线程来执行(交替执行),是轻量锁,如果有资源竞争,是重量锁
package BingFaBianCheng.bingFaBianCheng3.test;
import BingFaBianCheng.bingFaBianCheng3.entity.A;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j(topic = "enjoy")
public class TestJol {
static A l = new A();
static Thread t1;
static Thread t2;
public static void main(String[] args) throws InterruptedException {
// 计算hash值,如果获取偏向锁存储线程id时会产生冲突
System.out.println(Integer.toHexString(l.hashCode()));
log.debug("线程还未启动----无锁");
log.debug(ClassLayout.parseInstance(l).toPrintable());
System.out.println(ClassLayout.parseInstance(l).toPrintable());
t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 3; i++) {
testLock();
}
}
};
t2 = new Thread(){
@Override
public void run() {
testLock();
}
};
t1.setName("t1");
t1.start();
//等待t1执行完后再启动t2
//是线程是交替进行的,防止直接变成重量锁
t1.join();
t2.setName("t2");
t2.start();
}
/**
* synchronized 如果是同一个线程加锁
* 交替执行 轻量锁
* 资源竞争----mutex
*
*
*/
public static void testLock(){
//偏向锁 首选判断是否可偏向 判断是否偏向了 拿到当前的id 通过cas 设置到对象头
synchronized (l){
//t1 locked t2 ctlock
log.debug("name:"+Thread.currentThread().getName());
//有锁 是一把偏向锁
log.debug(ClassLayout.parseInstance(l).toPrintable());
}
}
}
如果不加t1.join(),synchronized直接变成重量锁了,因为有资源竞争,t1和t2打印出来都是010
如果加了t1.join(), 打印出来是000,synchronized变成了轻量锁
如果加了t1.join(),同时注释掉t2.setName(“t2”)和t2.start(), 打印出来是001,这里的01不是偏向锁,任何对象初始化出来都是01,表示它是偏向锁但是没有偏向,并且是不可偏向的。之后走到synchronized里面变成000,因为不可以偏向,直接膨胀成轻量锁
如果再注释掉第一行hash方法,先打印出来的是101,后打印的也是101,第一个101是一把偏向锁,其余54位什么都没存,但是没有人偏向它,第二个101执行到synchronized里面,可偏向,变成了偏向锁,同时保存线程ID
如果是同一个线程加锁,就是偏向锁
如果是两个线程交替直系,是轻量锁
如果两个线程有资源竞争,底层是从pthread_mutex_t实现的重量锁
膨胀过程不可逆,都是在某些极苛刻条件下可偏向
要说明虚拟地址,需要说清进程,
4g的内存 ---- 代码块、数据块、堆(全局变量)、栈(局部变量)
gcc -g -c xx.c----汇编指令,生成一个.o文件
Objdump -d xx.o ---- 生成汇编代码,
gcc -c xx.c xx.out — 将.c文件编译成.out文件(printf("%d",&l))
./xx.out — 运行.out文件
cat /proc/进程号/maps — 就可以看到相应的虚拟地址,java中打印的对象地址也是虚拟地址