3.6多线程

一.多线程

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更好

五.总结

猜你喜欢

转载自blog.csdn.net/m0_72618437/article/details/129373483