锁的相关知识:
1)这就是类似于说我们ATM机上面有一把锁,同一时刻,如果说人们之间不相互认识,那么通过这把锁就进行限制了说就限制了说一次只能有一个人来进来取钱,我们通过这样的锁,就可以来进行避免上述这种乱序排序执行的情况
2)特点:互斥的,同一时刻只有一个线程可以获取到锁,如果其他的线程也尝试获取到锁,就会发生阻塞等待,一直阻塞到刚才的线程释放锁,剩下的线程再尝试竞争锁
1
1)当一个线程加锁成功的时候,其他线程尝试加锁,就会进入到阻塞等待状态,此时我们对应的线程,就处于BLOCK状态,阻塞会一直持续到占用锁的线程把锁释放为止
2)咱们的加锁操作,就是把若干个不是原子的操作封装成一个原子的操作
3)同步:在多线程中,线程安全中,同步,本质上是指定的是互斥,只有一个人可以获取成功,其他竞争同类资源的只能失败
4)但是在IO或者是网络编程里面,同步相对的词叫做异步,此时同步和异步没有任何关系了,和线程也没有关系了,它表示的是消息的发送方是怎么获取到结果的
5)synchronized本质上来说是修改了对应对象的对象头里面的一个标记
class Counter{
public int count;
}
Counter counter=new Counter();
1)我们对应的线程进入到increase之前会尝试加锁,直到我们对应的线程执行完成increase方法执行完后才会执行解锁
2)但是在编程中如果出现了一个线程获取到锁了,出问题了,自己不干活,还影响到其他线程获取到锁,那么该怎么办呢?后续会说的
3)我们在进行使用synchronized关键字,其实本质上就是在针对我们具体的某一个对象在进行加锁,我们正常情况下在我们创建的对象中会包含一个具体的字段,表示该对象的加锁状态,可以想象到是一个BOOLEAN类型,针对这个对象未加锁就显示为false,针对这个对象进行加锁就显示为true;
4)在数据结构中,加锁的有 Stringbuffer,vector,Hashtable
一)加入到普通方法前,相当于是针对当前this来进行加锁;
1)synchronized加到普通方法前,表示锁this(当前实例对象),就是设置this也就是普通对象的对象头的标记位,如果两个线程同时的并发去调用这个synchronized修饰的方法,尝试针对同一个对象来进行加锁的时候,本质上是修改了Object对象中的“对象头的”里面的一个标记,假设一开始默认情况下是false,线程1已经把这个对象的标志位设置成了true,此时线程2也尝试来进行获取到这把锁,但是此时因为标志为不是false,所以说我们就不能进行修改标志位,必须要等到标志位变成false的时候,或者说等到线程1出了synchronized修饰的方法了也就是将我们对象对应的标志位改成false之后,我们的线程2才可以进行获取到锁
2)进入到synchronized修饰的方法,就相当于是加锁;出了synchronized修饰的方法,就相当于是解锁;它的功能就相当于把并发变成串行,两个线程针对不同的对象来进行加锁,就不会产生竞争
3)适当的牺牲速度,换来的是结果的准确性;
4)如果此时是加锁的状态,其他的线程就会无法执行这里面的逻辑,就只能进行阻塞等待;
这就相当于是ATM机取钱,同一时刻只能有一个人进入到ATM机取钱,别的人想要进来,并且获取到锁,是会阻塞等待的;
//我们是针对当前this来进行加锁 public synchronized void run() { System.out.println("我叫做run方法"); }
1)进入到Synchronized修饰的方法或者同步代码块==将对象头的标志位从false改成true
2)退出synchronized修饰的方法或者同步代码快==将对象头的标志位从true修改成false
3)当我们的两个线程尝试针对同一个对象加锁的时候才会产生竞争,如果是两个线程针对不同的对象进行加锁,那么就不会产生竞争
4)这就类似于说两个人抢1个ATM机,会产生竞争,但是如果说两个线程同时抢两个不同的ATM机,那么就不会产生竞争
2)加到某个静态方法前表示锁当前类的类对象,对类对象进行加锁
就算我们把synchronized加到静态方法上面,静态方法上面是没有this的,因为和实例不相关,也就是说谈不上this,所谓的静态方法更应该叫做类方法,咱们的普通的方法,更严谨的叫法,就应该叫做实例方法
2.1)反射是面向对象的一个基本特性,和封装,继承,多态是并列关系;反射也叫做自省,是指一个对象可以认清自己,这个对象里面包含哪些属性,每个属性叫啥名字,是什么类型,包含哪些方法,每个方法叫啥名字,参数列表是什么,所以说反射机制就是.class文件赋予的力量
2.2)这些信息来自.class文件,它咱们在进行运行程序的时候,是.java文件编译生成的字节码文件,会在JVM运行的过程中加载到内存里面,就会通过类对象来描述这个.class文件的内容和一切信息,类名.class就得到了这个类对象,每个类的类对象都是单例的
2.3)由于类对象是单例的,多个线程并发调用被synchronized修饰的同步代码快况且是给类对象进行加锁或者是使用synchronized修饰时的静态方法,一定会触发锁竞争
package com; class Counter{ public static int count=0; public synchronized void increase(){ //这是针对当前Counter对象来进行加锁 count++; } } public class Solution { private static Counter counter=new Counter(); public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(()->{ for(int i=0;i<10;i++){ counter.increase(); } }); Thread t2=new Thread(()->{ for(int i=0;i<10;i++){ counter.increase(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.count); } }
三:如果说我们是针对某一个代码块进行加锁,就需要手动进行指定,锁对象是什么,synchronized后面要加一个括号,表示进行加锁的对象,针对哪一个对象来进行加锁,咱们JAVA中的任何对象,都可以作为锁对象
3.1)进入到synchronized修饰的同步代码快,相当于是加锁==给对象头的标志位设置从false设置成true;
3.2)出了到synchronized修饰的同步代码快,相当于是解锁==给对象头的标志位设置从true设置成了false;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for(int i=0;i<10;i++){
synchronized(Object.class){
count++;
}
}
});
Thread t2=new Thread(()->{
for(int i=0;i<10;i++){
synchronized(Object.class){
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
1)是如果括号里面的内容类型不同,也就是说对应到两个类对象(这时有两把锁
就不会出现竞争)
2)但是如果两个对象是相同的类,就是一把锁,就会发生竞争,他们必须竞争同一把锁
1)如果两个线程,尝试针对同一个锁对象进行加锁,此时一个线程会先获取到锁,另一个线程就会阻塞等待;
2)如果两个线程,尝试对不同的锁对象进行加锁,此时两个线程都可以获取到锁,互不冲突;3)synchronized会进行记录是当前哪一个线程所持有的锁,当线程一获取到这个锁的时候,在进入到代码块,此时就知道线程一已经获取到了这个锁,就不会进行阻塞了;
回顾内存可见性:
线程的核心代码中,循环其实没干啥,反复地执行循环条件的比较操作,具体情况如下
1)先从内存中读取flag的值到CPU中
2)在CPU中执行比较这个数与0之间的相等关系
3)从CPU上的寄存器读取数据速度要比从内存中读数据要快得多
1)编译器在判断这个逻辑中,循环也没干什么事情,只是频繁的读内存而已,于是编译器就把这个过程优化了,第一次把内存数据读到CPU后,后续的都不是真的从内存中读了,而是读取刚才在CPU中读的数据。
2)编译器认为Flag没有出现改动,其实只是在当前线程中没有出现改动,而编译器不能感知其他线程对flag进行了修改,于是就发生了误判,其实编译器是好心,但是你优化过的逻辑必须的和原来的逻辑是等价的呀!!!!
3)一个线程读,一个线程写;两个线程都写,那就用volatile来解决,禁止编译器过度优化
4)加了volatile 后,对这个数据的读取肯定是在内存中读取,禁止刚才的优化;但是他不可以保证原子性
5)编译器什么时候进行优化,什么时候不进行优化,我们是把握不住的;如果在循环里面加入sleep(1000),降低循环的速率,可能编译器就不会进行优化了;
总结:一个线程频繁读,另一个线程写,大概率会用到volatile;