【Java并发编程的艺术学习】第二章摘要补全

1.并发编程

多线程情况下,一个线程需要读取到其他线程写后内容再开始操作。

2.如何保证写后读

2.1加锁

        当前线程对资源进行加锁,在此时其他线程相对该资源操作的话是无法上锁的,所以无法对该资源进行任何操作。

在该线程写完之前不可以释放锁

如果一个方法加锁,那么不管什么线程想拷贝该方法都要事先对该方法加锁。

如果想进行加锁的话需要对读和写操作同时进行加锁,分开加锁的话可能没有执行完毕时间片就到了,这样是没有任何意义的。

public class Test {
	public static int a;
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread() {
			public void run() {
				getA();
			}
		};
		t1.start();
		t1.join();
	}
	
	
	public synchronized static int getA() {
		return a; //读
	}
	public synchronized static void setA(int x) {
		a = x; //写
	}
	public synchronized static void m1(int a) {
		a++; //int k=a+1; a=k; 读+写
		
	}
	
}

2.2不加锁的方式:先比较并且交换(CAS)

2.1volatile的应用

1.volatile保证可见性,什么是可见性

可见性是当一个线程修改一个共享变量,另一个线程能读到这个修改的值。

2.volatile能保证一定准确吗?

不一定。volatile只能保证任意时刻读取

比如有两个线程,线程1在t1时刻读取到值为20,恰好在读取后的下一瞬间t2线程2对该资源写回变为1000。在这个过程中,volatile只能保证在t1的一瞬间是正确的,在t2时刻我们在t1时刻读取到的值和t2并不同,所以说一定准确这个说法是不对的。

3.那我们如果不加volatile和加volatile还有什么区别,不加的话我们难道读取就是错的吗?

对于我们的代码来说,比如123456,在我们认知中通常执行顺序是123456,但是实际上并不是,在计算机内部会采用流水线执行,采用流水线执行的话会导致指令重排序,那么一些已经写完的读不到,就会导致计算错误,所以我们需要保证多线程下的可见性。

流水线:每个环节只做一件事

比如制造望远镜,每个人做一个那么需要完成所有,可能一个人完成一天完成一个,那么一百个人一天内最多完成一百个,效率很低下,但是如果每个人只需要完成一个部件,那么效率就会大大提升,所以计算机内部也采用流水线方式执行

4.除了volatile还有什么能保证线程的可见性?

final:防止指令重排序的作用,保证多线程的可见性。

2.2synchronized的实现原理和应用(重点)

1.Synchonized原理(锁的实现原理)?(我讲不清楚,就直接贴图了)
JVM 基于 入和退出 Monitor 对 象来实现 方法同步和代 码块 同步,但两者的 实现细节 不一 。代 码块 同步是使用 monitorenter和monitorexit 指令 实现 的,而方法同步是使用另外一种方式 实现 的, 细节 JVM 范里并没有详细说明。但是,方法的同步同 可以使用 两个指令来 实现
monitorenter 指令是在 编译 后插入到同步代 码块 的开始位置,而 monitorexit 是插入到方法 结束处 和异常 JVM 要保 每个 monitorenter 对应 monitorexit 与之配 。任何 象都有一个monitor 与之关 ,当且一个 monitor 被持有后,它将 定状 线 行到 monitorenter指令时 ,将会 尝试获 象所 对应 monitor 的所有 ,即 尝试获 象的
synchronized 用的 是存在 Java 里的。如果 象是数 组类 型, 机用 3 个字
Word )存 储对 ,如果 象是非数 组类 型, 2 储对 。在 32 位虚 机中, 1
等于 4 ,即 32bit ,如表 2-2 所示
【Java并发编程的艺术学习】第二章摘要补全_第1张图片
Java 里的 Mark Word 里默 储对 象的 HashCode 、分代年 锁标记 位。 32 JVM
Mark Word 的默 储结 构如表 2-3 所示。
在运行期 Mark Word 里存 的数据会随着 锁标 志位的 化而 化。 Mark Word 可能
以下 4 种数据,如表 2-4 所示。
【Java并发编程的艺术学习】第二章摘要补全_第2张图片
64 位虚 机下, Mark Word 64bit 大小的,其存 储结 构如表 2-5 所示。
【Java并发编程的艺术学习】第二章摘要补全_第3张图片
2.对于synchronized的使用
源码
public class Test02 {
	public synchronized void m1() throws InterruptedException{
		System.out.println("开始执行m1");
		Thread.sleep(3000);
		System.out.println("结束执行m1");
	}
	public synchronized void m2() throws InterruptedException{
		System.out.println("开始执行m2");
		Thread.sleep(3000);
		System.out.println("结束执行m2");
	}
	public synchronized static  void m3() throws InterruptedException{
		System.out.println("开始执行m3");
		Thread.sleep(3000);
		System.out.println("结束执行m3");
	}
	public synchronized static void m4() throws InterruptedException{
		System.out.println("开始执行m4");
		Thread.sleep(3000);
		System.out.println("结束执行m4");
	}
	public  void m5() throws InterruptedException{
		System.out.println("开始执行m5");
		Thread.sleep(3000);
		System.out.println("结束执行m5");
	}
	public static  void m6() throws InterruptedException{
		System.out.println("开始执行m6");
		Thread.sleep(3000);
		System.out.println("结束执行m6");
	}
}
public class Test0201 {
	public static void main(String[] args) throws InterruptedException {
		final Test02 x=new Test02();
		final Test02 y=new Test02();
		Thread t1=new Thread() {
			public void run() {
				try {
					x.m1();//更改部分
				}catch(InterruptedException e){ 
					e.printStackTrace();
				}
				
			}
		};
		Thread t2=new Thread() {
			public void run() {
				try {
					y.m2();//更改部分
				}catch(InterruptedException e){ 
					e.printStackTrace();
				}
				
			}
		};
		t1.start();
		t2.start();
	}
	
}

1.类锁:锁住类

动态方法需要通过类来调用,通过运行

【Java并发编程的艺术学习】第二章摘要补全_第4张图片【Java并发编程的艺术学习】第二章摘要补全_第5张图片

【Java并发编程的艺术学习】第二章摘要补全_第6张图片

可以看到,上锁部分为整个类

2.方法锁

【Java并发编程的艺术学习】第二章摘要补全_第7张图片【Java并发编程的艺术学习】第二章摘要补全_第8张图片

【Java并发编程的艺术学习】第二章摘要补全_第9张图片【Java并发编程的艺术学习】第二章摘要补全_第10张图片

在这里对比可以看到,对于静态方法,锁住的为方法区,(图四4比3先执行是因为对于操作系统来说,先进入就绪队列代表大概率先被选中执行,不是一定先执行)

3.对于类锁来说,锁住该类并不影响无锁的方法执行

【Java并发编程的艺术学习】第二章摘要补全_第11张图片

4.对于方法区来说同样

【Java并发编程的艺术学习】第二章摘要补全_第12张图片

建议自己运行一下这部分代码,言语逻辑表述不是很清晰。

3.概念补充:

HotSpot:JAVA运行区域

栈帧:方法

4.偏向锁
一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的进程ID,以后该进程在进入和退出同步块的时候不需要加锁和解锁,测试一下就可以
5.轻量级锁
其他线程一旦释放锁就知道
就是不断地用CAS去替换对象头部指针
相当于死循环
适用于线程比较少的情况下
很消耗CPU
6.重量级锁
其他线程尝试获取锁时都会被阻塞住(进入阻塞队列),当持有锁的线程释放后会唤醒这些线程
阻塞队列:减少浪费CPU

2.3原子操作的实现原理

1.CAS方法不唯一,在Java中很多地方都封装了CAS
2.什么是线程安全类?
内部加了锁,不需要我们再次加锁
3.CAS原子操作三大问题
ABA问题
循环时间长开销大
只能保证一个共享变量的原子操作
4.ABA问题示意图
【Java并发编程的艺术学习】第二章摘要补全_第13张图片

你可能感兴趣的:(java,学习,jvm)