2.synchronized详解
为了避免 临界区的竟态条件发生,有多种手段可以达到目的
- 阻塞式解决方案: synchronized ,Lock
- 非阻塞式解决方案:原子变量
互斥:synchronized
synchronized ,俗称【对象锁】,才采用互斥的方式 ( 多线程锁住同一个对象才有互斥的效果 ) 让同一时刻最多只有一个线程持有【对象锁】,其他线程再想获得这个【对象锁】时就会被阻塞,这样就能保证拥有锁的线程可以安全顺利的执行 临界区内的代码,不用担心上下文切换;
注意:
虽然java中互斥和同步都可以采用synchronized关键字来完成, 但是它们还有区别:
- 互斥 是保证临界区的竟态条件发生,同一时刻只能有一个线程执行临界区代码;
- 同步 是由于执行的先后,顺序不同,需要一个线程 等待其他线程运行到某个点
语法:
synchronized(对象){
//临界区
}
------------------------------------
//线程1获得了对象锁,这时候线程2来了再想获得对象锁,就必须要等待线程1释放对象锁,线程2就会陷入阻塞状态( blocked),
//等线程1执行完了,会把对象锁释放,唤醒阻塞状态中的其他线程,其他线程才有机会获得这个对象锁
//换句话说
synchronized(n){
i++; //静态变量i
}
对1 里面的案例 进行修正
@Slf4j
public class Interrupt {
static int n =0;
static final Object obj = new Object;
@SneakyThrows
public static void main(String[] args) {
Thread t1 =new Thread(()->{
for (int i =0; i<5000;i++){
synchronized (obj){
n++;
}
}
});
Thread t2 =new Thread(()->{
for (int i =0; i<5000;i++){
synchronized (o){
n--;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(n);
}
}
再讲解一下 这个synchronized 流程;:
t1 线程遇到了synchronized(对象){ 临界区 } ,拿到了对象锁,执行临界区的代码(就好比是,他进了一个房间并且把门锁了,然后在房间里工作),这时候再有其他线程 也运行到了这段代码,那么它们就只能等待,发生了上下文切换 Context Switch)
阻塞住了(blocked)(从运行状态 却换到了阻塞状态); 在这中间 即时 t1的时间片用完了,被踢出了房间但是这房间的门还是锁着的,(也不要错误的理解为锁住的对象就能一直运行),对象锁还是由t1持有,其他线程还是进不去,只有等t1再次被分配时间片才能开门进入;当t1执行完{ 临界区}内的代码,会释放对象锁,并且唤醒其他线程,分配到时间片的线程会持有对象锁,进去执行代码;:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DC8cnXff-1644027605400)(ddd.assets/image-20220114025539724.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s76H4JMi-1644027605401)(ddd.assets/image-20220114025559745.png)]
思考
synchronized实际上是用 对象锁,保证了 临界区内的代码的原子性,临界区的代码对外是不可分割的,是一个整体,不会被线程切换所打断
为了加深印象,请思考下面三个问题
如果把sychronized(obj) 放在for循环外面,如何理解?---->考察的是临界区内的代码原子性;
@SneakyThrows
public static void main(String[] args) {
Thread t1 =new Thread(()->{
for (int i =0; i<5000;i++){
synchronized (obj){
//只有 n++;这4条指令对进来的线程来说 是一个不可分割的整体;
n++;
}
}
});
------------------------------------------------------------------------------
@SneakyThrows
public static void main(String[] args) {
Thread t1 =new Thread(()->{
//放在for循环外面 ,这2W条指令对进来的线程来说 是一个不可分割的整体;
synchronized (obj){
for (int i =0; i<5000;i++){
n++;
}
}
});
如果t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎么运行? 这里考察的是锁对象;
这个的话 t1 t2持有的是不同的对象锁,不用多说了;
如果t1 synchronized(obj) 而 t2没有不使用synchronized关键字 会怎么运行?
@SneakyThrows
public static void main(String[] args) {
Thread t1 =new Thread(()->{
for (int i =0; i<5000;i++){
//t1要对n++操作;t1就会持有锁对象
synchronized (obj){
n++;
}
}
});
Thread t2 =new Thread(()->{
for (int i =0; i<5000;i++){
//t2要对n++操作 但是不会去尝试获取锁,不去获取对象锁,那也就不会被阻塞 ,因此代码继续运行t2还能能对共享数 据操作;
n--;
}
});
用面向对象思想 对上面的案例进行改进:
将互斥的逻辑封装 对外只用简单的调用
对共享资源的保护由内部来实现
package chapact01;
import com.sun.org.apache.bcel.internal.generic.NEW;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
@Slf4j
public class Interrupt {
@SneakyThrows
public static void main(String[] args) {
Room room = Room.getInstance();
Thread t1 =new Thread(()->{
for (int i =0; i<5000;i++){
room.increment();
}
});
Thread t2 =new Thread(()->{
for (int i =0; i<5000;i++){
room.decrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getN());
}
}
class Room{
private static int n =0;
private static volatile Room INSTANCE;
private Room(){
}
public void increment(){
synchronized (this){
n++;
}
}
public void decrement(){
synchronized (this){
n--;
}
}
public int getN() {
synchronized (this) {
return n;
}
}
//这里呢 Room 之创建一个对象是没问题的,但是如果创建两个对象 就是两个对象锁了;那么安全性就无法保障,可以使用单例模式改进
//双重检查 的单例模式
public static Room getInstance(){
//双重检查不用每次调用
if (INSTANCE ==null){
synchronized (Room.class){
if (INSTANCE ==null){
INSTANCE= new Room();
}
}
}
return INSTANCE;
}
//这样你无论获取多少个实例 ,都是同一个对象;
}
---------------------------------------
//上面的案例 还可以不用 单例模式
//把(this)对象锁 换成(Room.class) 这个类只会被加载这一次, 那么new 多个对象也是同一个对象锁,就也能确保共享资源的安全问题
方法上的synchronized
1.成员方法上加synchronized
public synchronized void increment(){
n++;
}
//等价于 //synchronized 加在成员方法上,它锁的是this对象;它只能锁对象
public void increment(){
synchronized (this){
n++;
}
}
2.静态方法上加synchronized
public synchronized static void increment(){
n++;
}
//等价于 //synchronized 加在静态方法上,它锁的是类对象(就是大的Class实例),只存在这一份
public static void increment(){
synchronized(Room.class){
n++;
}
}
所谓的 “线程八锁”
第一锁:结果: 1 2 或 2 1
@Slf4j
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
第二锁 结果 一秒后 1 2 或者 2 一秒后 1
@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
第三锁 结果: 2 3一秒后 1 或者 3 一秒后 1 2 或者 3 2 一秒后 1
@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
第四锁 结果: 2 一秒后 1
@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
第五锁 结果:2 一秒后 1
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
第六锁:result: 一秒后 12 或者 2 一秒后 1
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
第七锁 Result:2一秒后1
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
第八锁 Result: 一秒后12 或者 2 一秒后 1
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
w Thread(()->{ n2.b(); }).start();
}
第八锁 Result: 一秒后12 或者 2 一秒后 1
```java
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}