并发编程

并发编程

查看进程和线程

linux

进程

ps -fe //查看java进程
ps -fe | gropu java //使用管道运算符筛选java进程 
jps	//jdk自带命令,查看进程windows也可用
kill pidxxx  //杀掉进程

线程

top -H -p pidxxx //查看进程下的所有线程
jstack pidxxx		//抓取某一刻的线程信息
栈与栈帧

main线程运行时内存图,栈是线程私有的,所以此处只给线程main分配一个栈

1606114354900

线程上下文切换

即多个线程轮流使用cpu,每次切换,线程计数器都要记录他的线程执行到那一步了。

常见方法
join

join方法一般用于线程间通信 ,**底层原理是wait **

main{
 t.join();	//在main线程中调用t的join,也就是说当t运行结束后main就运行
}
main{
  t1.join();	
  t2.join();
  //当t1和t2中运行最慢的结束后可以运行,t1和t2可以并行运行
}

等待的时间已时间运行的时间为准

main{
	t.join(10); //如果t运行只花了3s,那么main线程3s后就会运行,如果运行时间超过10s,例如15s,那么就mian就在10s后运行
}
interrept

1、打断睡眠的线程和正常的线程

非睡眠状态下打断线程后打断标志为true,但线程继续运行

2、两阶段中断异常

3、isInterrupted()和interrupted()

前者不会清除打断标志,后者会清除

4、park

LockSupport.park();//当前线程阻塞 ,当使用interrupt打断时可以继续运行,打断标记为正时park失效

主线程与守护线程

一般情况下当线程运行结束后进程才会结束,对于守护线程来说当其他线程运行结束后只剩守护线程时进程也会结束

习题:统筹规划

第四章:并发之共享模型

同步与互斥

互斥保证了操作的原子性,同步等价于线程间的通信

共享带来的问题

为什么需要共享cpu

无法利用cpu的情况:sleep,wait,io操作,通过分时系统提交cpu的利用率。即一个时间段内多个线程可以获取cpu资源

java中的体现

问题的产生:线程一对静态变量i自加5000次,线程二对静态变量i自减5000次,最后的i不是0.

问题分析:i++操作在字节码层面不是一条指令。分为多条指令进行,在运算时在线程内进行运算然后写到主存,所线程一中计算后的结果还没有写到主存中cpu时间片用完了,线程二读取到了未被计算的i,所以造成这个结果

1606190784043

临界区

1606191102078

一段代码块内如果存在对共享资源的多线程读写操作,这段代码块称为临界区

竞太条件

多个线程在临界区内执行,由于代码的执行序列不同导致结果无法预测,称之发生了竞态条件

解决方法

阻塞式:synchronized,lock

非阻塞式:原子变量

1606201690965

互斥是对于同一资源来说的(静态变量i),同步只是规定了线程的执行顺序

synchronized保证了操作的原子性,但并不保证可见性???

synchronized在修改了本地内存中的变量后,解锁前会将本地内存修改的内容刷新到主内存中,确保了共享变量的值是最新的,也就保证了可见性。

1606203560079

方法上的synchronized

1606205255363

变量线程安全分析

方法本身是线程私有的,安不安全是对于这个方法里操作的资源来说的,如果这个资源是静态变量或者是引用对象等有可能造成线程不安全的问题

成员变量的安全性

1606207695209

局部变量的安全性
子类继承造成的线程安全问题

当子类覆盖了父类的方法后父类的局部变量会暴漏,方法的访问修饰符是有意义的,可以在一定程度上保护方法中的资源安全问题

常见线程类

String,Integer,StringBuffer,Random,HashTable,concurrent包下的类。

也就是说他们的每个方法是原子的,但是组合在一起不一定是线程安全的

String类为什么是不可变类

线程安全类方法的组合
实例分析
售票问题
转账问题

使用的锁必须是A账户和B账户同一把锁,所以将锁加在方法上是不行的,要用Account.class。使用类锁只能有两个账户同时转账,如果账户很多时会造成性能的低下

Monitor概念

java对象头

Integer对象的大小为8+4=12字节

monitor在操作系统中叫做管程

轻量级锁

如果一个资源有多个线程访问,但多线程访问的时间是错开的(无竞争),如果产生了竞争会升级为重量级锁。

语法任然是synchronized

加锁过程

调用加锁方法时,线程的栈帧里产生一个锁记录对象(JVM层面),

锁记录中的Object reference指向锁对象。

通过ca操作替换锁对象(Object)的mark word,将mark word的值存入锁记录,mark woed的值就变为了锁记录的地址和状态码。锁记录地址用来表示那个线程

如果cas失败有两种情况

1、其他线程已经拥有了这个对象锁,表明有竞争,进入锁膨胀

2、如果原来的线程自己执行了synchronized锁重入,方法栈中新建一个栈帧里面有锁记录对象,发现object标记是00,即轻量级锁,然后查看到所记录的地址是同在一个线程,不会发生锁膨胀。

1606289729887

升级为重量级锁

1606290404384

自旋锁

自旋是为了让后来的线程一尝试获取锁,而不是让他进入阻塞状态。适合多核cpu。如果重试多次后不成功则进入阻塞状态。jdk7后不能控制是否开启自旋

偏向锁

偏向锁在对轻量锁的优化在于每次锁重入不需要进行cas操作,CAS是cpu的一条指令。具体操作是将线程id设置到对象的mark word头,而不是轻量级锁中的锁记录对象地址 适用于冲突很少的情况,当线程很多时可以禁用偏向锁。 当一个对象调用hashcode时会禁用偏向锁。hash码填充到markword中。

wait

必须和synchronized配合使用

1606361460731

waiting是已经获得过锁但是又放弃的锁。wait必须先成为owner才能wait

blocked是没有获得到锁

notify唤醒的是waitset里的线程

同步模式之保护性暂停

join的实现,future的实现就是采用的此模式

任务:

​ 1、使用此模式在下载时与join的区别,一个等结果,一个等结束

​ 2、设置超时等待,要防止虚假唤醒

​ 3、join就是使用了保护性暂停模式

​ 4、多任务版guardObject常用于RPC

1606718843020

异步模式之生产者消费者
park和unpark

1、wait,notify必须匹配monitor使用

2、以线程为单位,即可以精确的唤醒某个线程,而notify只能随机唤醒一个线程

3、可以先执行unpark,执行多次的结果和执行一次的效果是一样的,但是notify却不能

状态转换

runnable<-- -->waitting

使用join,wait线程会进入waitset中,当notify,或者interrupt时会竞争cpr资源,如果成功则变为runnable,不成功则变为blocked

死锁、活锁、饥饿

死锁:一个线程互相拥有另一个线程想要的锁,导致两个线程阻塞

活锁:两个线程都在使用cpu,但由于互相改变结束的条件,导致都不能结束

饥饿:一个线程很少获得cpu资源

ReentrantLock

1、synchronized不可以中断,此锁可以。

2、可以设置超时时间。

3、可以设置公平锁,synchronized时非公平的,在entrylist中的谁竞争到谁先执行

4、可以设置多个条件变量,即有多个waitset,根据不同的情况选择进入哪一个,synchronize只有一个waitset

5、都支持可重入

应用

1、三种方式固定线程输出顺序(wait,lock,park)

2、三个线程循环五次输出abc。park操作的是线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值