目录
1.进程和线程的区别是什么?
2.Java当中创建线程的方法有什么?
3.线程不安全的原因
3.1如何解决线程不安全问题
4.synchronized的代码块是可重入的,怎么实现一把可重入的锁?
5.sleep和wait的区别。
6.单例模式
6.2懒汉模式
6.3懒汉模式
7.线程池
7.1ThreadPoolExecutor
7.2Executors
8.锁策略
8.1悲观锁和乐观锁
8.2重量级锁和轻量级锁
8.2等待挂起锁和自选锁
8.3互斥锁和读写锁
8.4可重入锁和不可重入锁
8.5公平锁和非公平锁
9.synchronized的工作流程
10.CAS指令和ABA问题
11.synchronized和ReentranLock的区别(为什么有synchronized,还要使用juc中的lock)?
12.多线程下使用ArrayList
13.多线程下使用哈希表
14.死锁
14.1什么是死锁
14.2死锁形成的条件
14.3如何避免死锁
1.进程和线程的区别是什么?
2.Java当中创建线程的方法有什么?
3.线程不安全的原因
3.1如何解决线程不安全问题
1.不共享资源,每一个线程独享一份资源,从根本上避免竞争。
2.不修改资源,使用不可变的对象。
3.保证原子性,使用synchronized关键字进行加锁。
4.保证可见性和禁止重排序。
4.synchronized的代码块是可重入的,怎么实现一把可重入的锁?
5.sleep和wait的区别。
6.单例模式
简单来说,单例模式,就是让一个类,变量等在整个进程中只被修改一次。
6.2懒汉模式
"饿"字体现的是一种迫不及待的状态,懒汉模式是在类加载的时候就创建出来,不管外部有没有人来获取实例,先把实例创建好,然后等待被使用。由于是时在类加载的时候创建的所以不涉及线程安全。
如果一个单例模式体积比较到,在整个程序运行中根本没有用到,但是还没在类加载的时候被创建,此时就会白白浪费资源。
6.3懒汉模式
"懒"字体现的是一种能拖就拖的状态,懒汉模式就是类的加载时不创建,当有外部第一次真正要获取实例时才创建 ,如果没有,那永远不创建。
懒汉模式最好采用,if + synchronized + if的结构,减少线程之间的竞争。
为了预防内存可见性和指令重排序问题,实例的引用变量最好采用volatile修饰。
7.线程池
7.1ThreadPoolExecutor
7.1ThreadPoolExecutor提供了7个参数。
new ThreadPoolExector{
corePoolSize,
maximumPoolSize.
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
}
- AbortPolicy:默认策略,之间摆烂不干了,直接抛出RejectedExecutionException。
- CallerRunsPoilcy:线程池没有更多的线程了,让调用submit的线程自行执行任务。
- DiscardPolicy:抛弃最新的任务。
- DiscardPoilcy:抛弃最老的任务。
7.2Executors
上边的太麻烦了,所有java标准库提供了另一组类,这点ThreadPoolExecutorsk进行进一步封装,简化线程池的使用。
- newFixedThreadPool:指定线程池的数目,后续不可以改变
- newCachedThreadPool:线程可以无限叠加。
8.锁策略
8.1悲观锁和乐观锁
悲观锁就是在加锁时候,当有多个线程同时竞争一个锁时,会预测接下来发生的锁竞争会非常激烈,此时就会对这样激烈的情况做出相应的措施(先获取锁资源,在进行数据操作)。
乐观锁就是在加锁的时候,当有少量线程同时竞争一个锁时,会预测接下来发生的锁竞争不激烈,因此就不需要对接下来的锁竞争做出其他措施(不直接加锁,而是等完成数据更新之后再判断数据是否冲突,如果冲突了,就返回错误信息,进一步确定如何处置)。
8.2重量级锁和轻量级锁
重量级锁时当多个线程竞争同一资源时,未获取锁的线程会被操作系统挂起(进入阻塞状态),直到锁被释放后由操作系统唤醒。
轻量级锁是假设多线程竞争较少,通过CAS(Compare-And-Swap)等原子操作尝试直接获取锁,失败时再升级为重量级锁。
8.2等待挂起锁和自选锁
等待挂起锁是重量级锁的典型实现,也就是当线程进行加锁操作时,发现锁竞争非常激烈,此时就会进行阻塞等待,获取锁周期长(一直等,直到获取到锁),并且很难及时获取到锁,这个过程不消耗cpu。
自选锁是轻量级锁的典型实现,一般不会进入阻塞等待,而是进入盲等状态,它获取锁的周期短(也就是短时间内多次进行加锁申请),并且可以及时获取到锁,但是这个过程会一直消耗cpu。
8.3互斥锁和读写锁
普通互斥锁是最简单的锁类型,核心作用就是保证统一时间里只有一个线程能够访问共享资源(Java中常用的就是synchronized)
读写锁是为了解决“读多写少”的情况,核心原因是读锁之间不会产生线程安全问题,因此不需要加锁,但是写与写之间,读与写之间是线程不安全的,因此读写锁是读锁与读锁之间不互斥,只让读锁和写锁,写锁和写锁之间产生互斥。
8.4可重入锁和不可重入锁
可重入锁是允许同一个线程多次获取同一把锁,并在内部通过计数器进行记录重入次数每次加锁时,计数器加1,释放锁是计数器减1,直到计数器为零的时候,才会真正的释放锁。
不可重入锁也就是不允许同一个线程重复获取锁,如果充分获取,此时会造成死锁或阻塞等待。
8.5公平锁和非公平锁
公平锁是线程获取锁的顺序严格按照锁申请的顺序执行,也就是那个线程等待的时间越长,那个线程优先获取锁。
非公平锁是随机获取,允许插队,新申请的线程也可能直接获取到锁,不需要考虑等待队列中的顺序。
synchronized是一个互斥锁,可重入锁,非公平锁。
9.synchronized的工作流程
总流程:无锁——偏向锁——轻量级锁——重量级锁
偏向锁并不是真正的锁,而是在锁上边加了一个很轻的标志,如果没有其他线程来竞争这个锁,最终当线程执行到解锁代码的时候,只是将标记清除即可,如果有其他线程来竞争,此时这个标记就会变成真正的锁,也就从偏向锁——轻量级锁,其他线程只能阻塞等待。
10.CAS指令和ABA问题
CAS是cpu提供的一条指令,所有是线程安全的,cas进行的操作就是将寄存器中的值与内存中的值进行比较,如果相等,就把内存中的值和另一个寄存器中的值进行交换,这里也可以理解为赋值。
ABA问题就是因为cas指令从内存中取值这这部分是我们自己的代码进行的,所以线程不安全,因此会出现问题,由一下例子说明:
余额1000,三个并发操作同时发生: 线程一将内存值1000读入寄存器1,然后暂停,线程二(取500)读内存到寄存器2,比较一致,内存改为500,线程三(存500)读内存到寄存器3,内存改回1000,线程一恢复,寄存器1(1000) == 内存(1000) 再次扣500,内存变为500。 但实际余额应该是1000,这里进行了两次扣款 。
要解决这种问题,其实就是解决这种值恰好重复的问题,因此我们可以在这里引入一个只增的版本号,只能通过cas指令进行加操作,这样就可以根据版本号是否匹配,判读是否因为aba问题而忽视的变化过程
11.synchronized和ReentranLock的区别(为什么有synchronized,还要使用juc中的lock)?
1.synchronized在进入代码块以后,是自动加锁并解锁的,ReentranLock是通过Lock和unLock手动加锁和解锁的,比较灵活。
2.synchronized等待资源是死等,而ReentranLock提供了lockInterruptidly(),运行中断响应。
3.synchronized为非公平锁,ReentranLock也默认为非公平锁,但是可以通过传入true表示使用公平锁。
4.synchronized如果加锁失败会陷入阻塞等待,但是ReentranLock提供了trylock方法,能够在竞争失败后之间返回。
5.synchronized使用的是Object的wait和notify,但一个锁只能绑定一个等待队列,ReentranLock可以通过Condition接口创建多个条件队列。
12.多线程下使用ArrayList
主要分为3种情况:
1.自行加锁:自己使用synchronized或其他方式加锁。
2.使用Collections.synchronizedList(new ArrayList)的方式创建一个ArrayList,在线程不安全部分使用synchronized。
3.使用CopyOnWriteArrayList。复制一个和原数组一样的,在读的时候在原数组中读取,写的时候,在复制数组中操作,操作完成以后,再将原数组的引用改为对复制数组的引用,这样就在不加锁的情况下完成了线程安全是ArrayList。
13.多线程下使用哈希表
1.HashMap — 非线程安全 多线程下不加保护会出现数据竞争,因此需要自己加锁保护,key允许为空。
2.. Hashtable — 全局同步锁 所有方法加了 synchronized,或者线程不安全的地方使用synchronized,其他地方和HashMap一样,key不允许为空。
3.ConcurrantHashMap.多线程下最好使用。采用给每个桶加锁的方式,实现即保护线程安全,又尽可能降低锁冲突的方式。
14.死锁
14.1什么是死锁
死锁是指在多线程系统中,两个或多个进程(或线程)因争夺资源而陷入无限等待的状态,导致所有相关进程都无法继续执行。每个进程都在等待其他进程释放资源,但自身又不释放已占用的资源,形成循环等待。
14.2死锁形成的条件
1.锁是互斥的,一个锁在同一时刻只能被一个线程持有。
2.锁是不可抢占的,一个线程持有锁是,另一个线程无法获取到那个锁。
3.请求和保持,线程请求锁资源后,除非获得锁资源,要不就一直等待。
4.循环等待,a线程持有b的资源,b线程持有c的资源,c线程持有a的资源,形成循环。
14.3如何避免死锁
1.把多线程中相互嵌套的锁改成并列的锁(破除请求和保持)。
2.对加锁的顺序做出约定,线程必须按照锁的优先级从高到低或从低到高的顺序获取锁。




