一.多线程
1.线程的状态
2.线程不安全的原因
3.synchronized
synchronized的本质操作,是修改了object对象头中的"对象头"里面的一个标记
当两个线程同时针对一个对象加锁,才会产生竞争
1)修饰普通方法
相当于锁对象指定为this
2)修饰代码块
要手动指定,锁对象是谁,
3)synchronized加到静态方法中
但是疑问.静态方法有this吗
静态方法->类方法
普通方法->实例方法
针对类对象加锁
类对象就是运行程序的时候,.class加载到JVM内存中的样子
反射机制 来源于.class
二.监视器锁 monitor lock
有的时候代码中的异常信息,可能会提到monitor
可重入:同一个线程针对同一个锁,如果出现了死锁就是不可重入,如果不会死锁,就是可重入的
1.死锁
不可重入的情况
外层先加一次锁,里面又要对同一个对象加锁
外层锁:进入方法,则开始加锁,这次能够加锁成功,当前锁没有人占用的
里层锁:进入代码块,开始加锁,这次加锁不能成功,因为锁被外层占用,得等外层锁释放了,才能加锁成功(竞争的情况)
外层锁要想执行完整个方法,才能释放
但是要想执行完整个方法,就得让里层锁加锁成功继续往下走
可重入的情况
可重入锁内部,会记录当前锁被哪个线程占用,同时也会记录一个加锁次数
线程A对锁第一次加锁,显然能加锁成功
锁内部就记录了,当前占用的是A.且加锁次数为1
后续再让A对锁加锁,此时不是真的进行加锁操作,而是计数器加锁+1
后续再解锁的时候,先把计数-1
当锁的次数减到0的时候,就真的解锁了
可重入锁的意义
①提高开发效率
②,程序中需要有更高的开销(维护锁,降低了运行效率
2.死锁的其他场景
1)一个线程,一把锁
2)两个线程,两把锁.
3)N个线程,M把锁
哲学家就餐问题
3,死锁的四个必要条件
1.互斥使用--一个锁被一个线程占用了以后,其他线程站用不了(锁的本质,保证原子性
2,不可抢占---一个锁被一个线程占用了之后,其他线程不能把这个锁抢走
3.请求和保持 当一个锁占据了多吧锁之后,除非显示的释放锁,否则这些锁始终都是被该线程持有的
以上都是锁本身的特点
4.环路等待 等待关系,成环了
如何避免环路等待
要像哲学家问题一样,提前约定好
嵌套使用锁.很容易出现死锁,一定要约定好加锁的顺序
三.java标准库
java有很多现成的类,有些是线程安全的,有些是不安全的.在多线程环境下,如果使用线程不安全的要谨慎
1.不安全 的类
2.安全的类
前三个在一些关键方法上都有synchronized
有了这些操作,就可以保证在多线程环境下,修改同一个对象
STtring没有synchronized,String是不可变对象.所以单线程多线程都无法修改
所以安全
有的编程语言就会把所有的对象设计成不变,更好的处理并发--erlang
三.volatile
进制编译器优化,保证内存可见性
1.工作内存
JMM就是把上述的硬件结构,在java中用专门的术语又抽象了封装了一边
cpu从内存取数据,速度慢尤其是频繁取的时候
就可以把这样的数据就直接放在寄存器里,后面直接从寄存器来读
但是寄存器太小了
所以又有一块新的空间 比寄存器大,比内存小,速度比内存快
称为缓存(cache)
cache和寄存器统称为工作内存
2.volatile和synchronized的关系
1)volatile只能保证可见性,不能保证原子性
volatile只是处理一个线程读一个线程写的情况..synchronized都可
2)synchronized很容易引起线程阻塞,volatile不会
四,wait和notify
等待和通知,处理线程调度随机性的问题
让线程调度变得有顺序.join也是一种控制顺序的方式,主要是通过控制线程结束的方式
wait和notify都是Object对象的方法
调用wait方法的线程,就会陷入阻塞
阻塞到其他线程通过notify来通知
1,wait
非法监视状态
wait的工作内容
1)释放锁
2)等待其他线程的notify通知
3)收到通知之后,重新获取锁,并继续往下执行
因此用wait/notify.就得搭配synchronized
wait哪个对象.就得针对哪个对象加锁
我们打开线程监视器看一下
2.notifyAll
notify就是唤醒在wait状态的线程
并且都是针对同一个对象的操作
所以还是用notify更好
五.总结