java复习笔记2

1.Hashmap的线程安全问题

比较熟知的就是Hashtable,但是HashTable是直接采用synchronize修饰,效率低
在并发包中也提供了一个类:ConcurrentHashMap ,这个类通过分段锁实现线程安全
1.7中​ ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现;采用 分段锁(Sagment) 对整个桶数组进⾏了分割分段(Segment),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。

1.8中又修改了,底层为数组+链表+红黑树,摒弃了Segment的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,通过并发控制 synchronized 和CAS来操作保证线程的安全。CAS:乐观锁
ConcurrentHashMap是否能完全取代Hashtable?
不能,ConcurrentHashMap是Hashtable高并发版本,但是这样也就会带来问题,问题就是ConcurrentHashMap数据是弱一致的,fail-safe原理的应该都会有这种问题,在遍历ConcurrentHashMap时,可能无法获取到最新的数据,但是一旦你从Hashtable中获取到了数据,那么一定是最新的(多线程不好抢到Hashtable就是了……)选择什么数据结构,是要依照你的当前场景进行权衡,找到数据一致性与高并发都可接受的点

2.正则表达式

元字符:代表字母数字空格等等
重复限定符:为了简化正则表达式,在元字符后加规定重复限定符,就可以达到要匹配多少个的目的
分组:使用小括号扩住多个东西,那么这多个东西就被划成了一个组
转义:\就是转义符,使用这个去将原本带含义的字符变成不带含义的字符
( 就代表匹配一个左括号,而不是认为它是分组中的一个括号
条件或 正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。

^(130|131|132|155|156|185|186|145|176)\d{8}$ 

比如这个正则表达式,前三个数字匹配到了括号中的任意一个都算成功
**区间:**那如果你发现条件或需要写很长一串,并且这一串其实有一些字符是在字母表中挨着的,怎么办呢,使用[] 中括号
正则提供一个元字符中括号 [] 来表示区间条件。
限定0到9 可以写成[0-9],限定A-Z 写成[A-Z],限定某些数字 [165]
如上面那个可以改为如下格式

^((13[0-2])|(15[5-6])|(18[5-6])|145|176)\d{8}$ 

设计模式

单例:singleton,懒汉式,饿汉式都怎么写,懒汉式的线程安全
工厂:不是直接new对象,而是通过工厂生产对象
代理:静态代理和动态代理,静态代理和动态代理区别,手写动态代理

多线程

重要关键字

start,run,wait,notify,yield,sleep,join
在这里插入图片描述

wait,notify,notifyall

wait,notify,notifyall都是Object类中定义的方法,但是都必须在同步代码块中,由同步监视器或者同步方法调用,否则会抛异常

守护线程

守护线程特点:一个线程如果被设定为守护线程(setDaemon),则该线程会随着主线程结束而结束,

interrupt

调用线程的interrupt方法,这个方法会改变线程状态,线程并不会立刻中断,而是等待系统在后续将其中断

volatile和内存可见性

内存可见性:当多个线程操作共享数据时,彼此不可见。可能你这个线程数据是1,第二个线程数据是2,第三个数据是3,如果没有特定的关键字,如volatile,他们不会去找其他线程同步变量的。
(想想也是啊,因为有时候,同一变量不在所有线程中共用,本身就应该是不同的)
那如果我们需要该变量内存可见,怎么操作?

乐观锁(CAS,版本号机制),和悲观锁

悲观锁:就是synchronized,每一次写必须只有一个线程来进行
乐观锁:不加锁,而是使用机制,来实现软锁的目的
版本号:在每一次修改之前会通过原数据的版本号,得到一个期望的版本号,然后修改数据,如果修改完期待版本号不等于现有版本号,则放弃修改,重新入队,等待重新修改
CAS:CAS就是compare and swap(比较交换)
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
V就是实际上变量使用的值,可以理解为它有一个备份,就是A,在B要修改的时候V先跟A比较,
如果此时A与V相同,V的值替换成B的值,然后有线程将A的值修改为V
如果此时A与V不同,B放弃修改,A的值与V的值同步
JUC atomic类中就是使用的CAS机制
CAS缺点:1.对代码块的原子性不能保证,只能保证单个变量的原子性
2.如果写线程很多的情况下,效率差,占用cpu高

CAS可能会有ABA问题,如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。

所以如果要解决aba问题,还是要使用CAS+版本号
除此之外CAS实现需要硬件层面的支持,在Java的普通用户中无法直接使用,只能借助atomic包下的原子类实现,灵活性受到了限制,CAS只能通过原子类去实现!!!

synchronized

synchronized怎么使用大家应该都很清楚了,主要是看看底层实现,还有,能辨别不同场景下,同步监视器是哪个,是创建对象,是Xxx.class?

多线程的几个性质:

这几个性质我是参考的 https://cloud.tencent.com/developer/article/1465413
原子性:指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。
关于volatile没有原子性的这个问题,可以参见https://www.cnblogs.com/keeya/p/9255136.html
对于volatile变量的单个读写是有原子性的,如 a =10;这种操作是有原子性的,但是如果是
a++这种操作,是没有原子性的,因为a++实际上是一个复合操作,对复合操作volatile没有原子性

关于原子性这个东西,我又想到,如果是在多线程常用类的构造器上,也是有说法的,下面是错误操作

public ipAddr(String ip,int port){
	this.ip = ip;
	//time a
	this.port = port;
}

这样,如果某个线程在time a去获取当前类ip port信息,它会获取到一个新的ip和旧的port。
正确操作是,把这两个,在同一个;语句中同时赋值给变量,要不一起赋值,要不不赋值
可见性:可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。
synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。
synchronized如果没锁,则资源变量是在内存中为最新状态,如果锁了,锁状态也是最新状态,因此有可见性。
而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。

volatile在主存中修改,以及其他线程如何获取最新值这个事可以参见https://www.cnblogs.com/keeya/p/9255136.html
有序性:有序性值程序执行的顺序按照代码先后执行。
synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。
可重入性:一个线程如果有了锁,然后然后再申请锁,它是可以重新进入,不排队的
synchronized和ReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

这个应用场景是啥呀hhhh

底层实现要JVM基础,先略……

ReenTrantLock

ReentrantLock是java.util.concurrent包下提供的一套互斥锁
ReentrantLock:
1.等待可中断,如果持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待
2.可以设定是否为公平锁:公平锁:先到先得,哪个线程先来,先执行哪个
3.锁可以绑定多个条件,一个ReentrantLock对象可以同时绑定多个条件。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

使用方法:​ 基于API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成

饿汉式单例

class Singleton{
private Singleton(){

}
private static Singleton instance = null;
public Singleton getInstance(){
	synchronized(Singleton.class){
		if(instance == null){
			instance = new Singleton();
		}
	}
	return instance;
}
}

volatile lock 前缀的指令, 多CPU的嗅探机制

https://blog.csdn.net/jinjiniao1/article/details/100540277
这个博客总结了lock的前缀指令以及多CPU下的嗅探机制
有volatile变量修饰共享变量在编译器编译后,后多出一个“lock” 来(lock前缀指令相当于一个内存屏障,会强制将对缓存的修改操作写入主内存)
该字符在多核处理器下回引发两个事件:
1.将当前处理器缓存行的数据写回系统内存;
2.这个写会内存的操作会使得其他处理器里缓存的该内存地址的数据无效
进行修改之后lock操作的两个事件1.缓存到内存 2.使其他缓存数据失效

关于其他线程如何发现自己缓存的值是否过期的问题:
1.每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态, 当处理器对这个数据进行修改操作的时候,会重新从系统内存中吧数据读到处理器缓存行里。
2.处理器使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存在总线上保持一致。
所以推断:volatile修饰的共享变量的写操作会触发“嗅探”,让处理器本地缓存中的volatile变量失效;

原文链接:https://blog.csdn.net/qq_33522040/article/details/95319946
多处理器总线嗅探原理:

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会想处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。

转发和重定向

记错了,转发是一次,重定向是两次
转发不丢请求数据,重定向丢请求数据

synchronzied和lock底层实现,AQS机制

线程池以及阻塞队列实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值