目录

1.并发与并行

2.线程切换

3.单线程一定比多线程快吗?

4.如何查看线程信息?

5.锁

6.volatile

7.Synchronized

8.锁的升级与对比

9.轻量级锁

10.轻量级的锁和重量级的锁的区别

11.自旋锁自旋超过多少次才会升级到重量级的锁?

12.什么是原子操作?

13.CAS操作

1.并发与并行

        并发是指多线程下对相同资源进行竞争,对同一变量进行读写操作。并行则是各自运行自己的,没有竞争互不影响。

2.线程切换

        在多核情况下,上下文切换可达到1毫秒1次。

                我们可以使用哪些工具测量:

                        可以使用Lmbench3测量上下文切换的时长

                        可以使用vmstat测量上下文切换的次数

3.单线程一定比多线程快吗?

        ①.多线程是有上下文切换的,这会产生时间开销的

        ②.在没有CPU浪费的情况下,单线程是比多线程快的

4.如何查看线程信息?

①用jstack命令dump(记录一瞬间状态)线程信息,看看pid为3117的进程里的线程都在做什么

sudo -u admin opt/ifeve/java/bin/jstack 31177 > /home/tengfei.fangtf/dump17

切换管理员权限从bin目录下,查看31177线程的信息,并将信息保存在后面的文件下。

②统计所有线程分别处于什么状态,发现300多个线程处于WAITING(onobjectmonitor)状态。

grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'| sort | uniq -c

筛选信息,筛选数据的前n行或者后n行都可以 ,统计排序出来并按一定的规则进行筛选

5.锁

       举一个死锁的例子:

        两个线程A、B,其中一个线程上锁,它里面的代码需要先执行,里面代码是先睡眠2s,然后锁住B,由于先睡眠,此时CPU会先去执行线程B,线程B的代码是锁住A,此时并不能直接去锁,因为A已被锁住,没有解锁,只能等待,等到2s过了,继续执行线程A,A的下一个步骤是锁B,此时B没有解锁,就等待,两边都在等待,就产生了死锁。

        如何避免死锁:

①避免一个线程同时获取多个锁

②避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源

③尝试使用定时锁,使用locktryLock来替代使用内部锁机制

④对于数据库锁,加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况。

6.volatile

        volatile是一个轻量级的锁,可以保证共享变量的“可见性”。

        可见性:在多线程情况下,一个线程修改了volatile修饰的变量,其他的线程可以立马读到这个被修改的值。这个点关键在于每个在进行操作的时候都会产生一个副本数据,进行修改。

        内存屏障:是一个约定,就是有个标记,当线程访问的时候,如果有这个标记,就不允许在去访问某个变量

        原子操作:要么都成功,要么都失败。比如五个步骤,前四个步骤成功了,最后一步失败了,那么前面四个都要还原成原来的。

        缓存:相对于远程服务器,有一个离使用者更近的地方,反应更快就是缓存。

        写缺失:发生在多线程中,假如有两个线程,一个线程把内存中的数据删除了,而另一个 线程打算把缓存中的数据刷回去的时候,发现内存中数据没有了。

volatile如何保证可见性?

        在多线程中,其他线程将内存的数据存入缓存中,此时相同数据有多个,当一个缓存行中的数据修改完之后,需要刷回内存,此时刷回去的时候,内存的数据一更改,那么原先缓存中其他的数据就会失效,如果其他线程还想使用,就需要重新从内存中取出数据放入缓存中。

由于有总线,绝对是一个个排队更改的,不存在多线程他就必须一起通过一起修改。

Java并发锁 分布式锁 java并发加锁_数据

        他有两种实现方式:第一是锁总线,这个锁住总线代表无论是其他线程不能通过,即使和它无关 的指令也不能通过,只能通过他自己,代价太大,不推荐。第二是锁缓存,只是锁住其他缓存的线程,一个打算刷回去的时候,其他几个就会被锁住,阻止多个区域往回刷新。

Java并发锁 分布式锁 java并发加锁_数据_02

7.Synchronized

Synchronized()中只能是引用类型,不能是基本类型。

        当这个锁在普通方法上的时候,锁的是实例对象(调用这个方法的实例对象)。

        当这个锁在静态方法上的时候,锁的是当前类的class对象。

        当这个锁在同步方法块的时候,锁的是Synchronized括号里的对象。

Synchronized在JVM中的实现原理:

        我们只要记住两个指令monitorenter和monitorexit,JVM是基于进入和退出Monitor对象来实现方法同步和代码块同步的,但是两者的实现细节也是有一定区别的。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式来实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。

        monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter都必须有一个monitorexit指令与其对应。任何一个对象都要有一个Monitor对象与之关联,并且如果有一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,也就是尝试获取对象的锁。

        Java对象头:Synchronized用的锁时存在Java对象头里的。Java对象头里的Mark word里默认存储对象的HashCode、分代年龄和锁标记位。

8.锁的升级与对比

锁只能升级不能降级,意味着偏量锁升级升级成轻量级锁之后,不可以降级为偏向锁。

偏向锁:

        加锁过程:

        这个不需要自己控制,只要你进入这个线程,那么这个对象就会加上锁。一个线程访问这个同步块想获取锁的时候,会在对象头和锁栈帧中的记录着存储锁偏向的线程ID,那么之后进入的话就比较这个ID就可以了。如果成功了,那么就可以获取锁。如果失败了,两种情况,看看是否设置了偏向锁,怎么看是否设置呢?就是看Mark Word中的锁标识是否设置成了1(1肯定代表设置了),如果没有设置,这就需要CAS去竞争锁了;如果设置了,就尝试使用CAS将对象头的偏向锁指向当前线程。

         撤销过程:

        这个偏向锁的撤销机制是只有等到竞争出现才会释放锁,也就是当其他线程开始尝试获取偏向锁时,持有偏向锁的线程才会释放锁。

        比如现在一个线程(1号线程)持有偏向锁,此时来了另外的2号线程想要获取锁,那么2号线程需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,也就是暂停1号线程。然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将偏向锁设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

只有一个线程来竞争才是最适合偏向锁的,太多了就会锁膨胀为轻量级锁。

9.轻量级锁

加锁状态:

        线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

轻量级锁解锁:

        轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁、

Java并发锁 分布式锁 java并发加锁_数据_03

        自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

10.轻量级的锁和重量级的锁的区别

轻量级的锁:

        假如一个多线程轮流执行一个任务,如果是轻量级的锁,那么一个线程在工作,其他的线程会一直自旋,一直检查锁的状态,当线程停止工作,解开锁时,那么其他几个线程会选出一个立马替上。由此可见,轻量级的锁由于自旋,极其浪费CPU的性能,但是他的反应速度是很快的,一旦解锁就会有其他线程锁住。

重量级的锁:

        还是多个线程执行一个任务,其中一个执行,其他的会进入到阻塞队列,此时,CPU就不需要管这几个线程了,专心的负责那个锁住的线程任务,当解开锁时,会有一个通知发给阻塞队列,然后他们在过来上锁。由此可见,重量级的锁基本不占用CPU,但是反应速度有点慢,需要等消息队列过来才能去锁住。

11.自旋锁自旋超过多少次才会升级到重量级的锁?

        有线程超过10次自旋或自旋线程数超过CPU核数的一半,自动升级为重量级锁。

12.什么是原子操作?

        不可中断的一个或者一系列操作。

13.CAS操作

        我们都知道线程修改数据都是有一个副本的,拿副本去内存跟本来的数据相比。比如有三个线程123,此时内存数据为0,他们要进行+1操作,此时,他们拿到了三个副本,都是0,线程1现在进行+1,然后拿着+1的数据进入内存,他记录这原先的数据0和现在的数据1,先拿0去跟内存的数据进行比较,看看是不是我们期望的值(也就是原先的值),发现也是0,那么就替换成这个1,此时线程2又拿着原先的值0和现在的值进行比较,发现内存中的数据是1,和手里不一样,就会操作失败,那么就进行自旋,把自己的数据变为1,然后将这个副本在带回去,这个就是CAS操作,比较并交换。