监视器锁-synchronized关键字

目录

前言:

1.多线程编程安全

1.1多线程安全概念

1.2多线程不安全实例

1.3多线程不安全的原因

1.4解决以上线程不安全的示例

2.synchronized关键字(重点)

2.1synchronized关键字概念

2.2synchronized关键字特性

2.2.1互斥特性

2.2.2可重入特性

2.3synchronized使用示例

2.3.1修饰代码块

2.3.2直接修饰普通方法

2.3.3修饰静态方法


前言:

 对线程的基本了解已经结束,除此之外,Java多线程编程中还有保证线程安全的内容。

 对于多线程编程,线程安全无疑是最重要的。监视器锁是保证线程安全的重要部分。

序列:多线程 - 005


1.多线程编程安全

1.1多线程安全概念

 如果多线程环境下代码的运行结果是符合我们预期的,即在单线程环境下应该的结果,则说这个多线程程序是安全的。

1.2多线程不安全实例

以下代码创建出了两个对n变量都自增5000次的线程,然后main线程等待其都自增完毕再进行输出n的值。如下:

public class Main {
    public static int n = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread01 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {//对n变量自增5000次
                Main.n++;
            }
        });
        Thread thread02 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {//对n变量自增5000次
                Main.n++;
            }
        });
        thread01.start();//启动两个自增线程
        thread02.start();
        thread01.join();//main线程等待thread01线程自增完成后,再执行
        thread02.join();//同上
        System.out.println("n的值为:" + Main.n);
    }
}

可以预期到,我们想要的n的值,最后输出应该是10000,这是一个多线程安全的结果。但是这个程序的运行结果和我们预期的值却大相径庭,为8964。这便是一个线程不安全的示例,运行结果如下:

1.3多线程不安全的原因

(1)线程的调度是“随机的”:这是多线程编程的罪魁祸首。随机调度使一个程序在多线程环境下,执行的顺序存在很多的变数。程序员必须保证在任意执行顺序下,代码都能正常工作。

(2)修改共享数据:多线程程序在运行时,对于相同的数据变量,会修改数据变量 ,影响多线程的安全性。

(3)不保证原子性:多线程程序运行时,对一个操作不会保证原子性,从而影响多线程的安全性。一条java语句不一定是原子的,比如n++操作分三步完成:

  1. 从内存把数据读取到CPU;
  2. 进行数据更新;
  3. 把数据写回到CPU。

(4)不保证可见性:一个线程对共享变量的修改,不能被其他的线程及时的看到。

1.4解决以上线程不安全的示例

以下代码输出结果为10000,符合多线程编程的安全性。其中使用到的synchronized关键字,下文接着会进行重点介绍。

public class Main {
    public static int n = 0;
    public static Object object = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread01 = new Thread(()->{
            synchronized (object){
                for (int i = 0; i < 5000; i++) {//对n变量自增5000次
                    Main.n++;
                }
            }
        });
        Thread thread02 = new Thread(()->{
            synchronized (object){
                for (int i = 0; i < 5000; i++) {//对n变量自增5000次
                    Main.n++;
                }
            }
        });
        thread01.start();//启动两个自增线程
        thread02.start();
        thread01.join();//main线程等待thread01线程自增完成后,再执行
        thread02.join();//同上
        System.out.println("n的值为:" + Main.n);
    }
}

2.synchronized关键字(重点)

2.1synchronized关键字概念

为了解决多线程编程的不安全,从而引入了编程器锁,synchronized关键字。

对于以上的多线程自增代码示例,synchronized关键字可以先将thread01每一次自增的代码进行加锁,此时thread02线程无法对n变量进行修改操作,保证了thread01线程的原子性,也保证了thread02线程不会修改共享数据n;反之对于thread02线程加锁,效果相同。

2.2synchronized关键字特性

2.2.1互斥特性

synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一对象的synchronized就会阻塞等待

  • 进入synchronized修饰的代码块时,相当于“加锁”;
  • 退出synchronized修饰的代码块时,相当于“解锁”;

2.2.2可重入特性

synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。

锁死问题:一个线程没有释放锁,然后又尝试再次加锁。

  • 第一次加锁,加锁成功,lock();
  • 第二次加锁,锁已经被占用 ,阻塞等待,lock();

Java 中的 synchronized 是可重入锁, 因此没有上面的“锁死问题”。以下代码不会影响自增结果为10000。

Thread thread01 = new Thread(()->{
            synchronized (object){
                synchronized (object){
                    for (int i = 0; i < 5000; i++) {//对n变量自增5000次
                        Main.n++;
                    }
                }
            }
        });

在可重入锁的内部,包含了 "线程持有者" 和 "计数器" 两个信息。

  • 如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己,那么仍然可以继续获取到锁,并让计数器自增;
  • 解锁的时候计数器递减为 0 的时候,才真正释放锁。(才能被别的线程获取到)

2.3synchronized使用示例

基本使用:synchronized本质上要修改指定对象的 "对象头"。从使用角度来看,synchronized 也势必要搭配一具体的对象来使用。这个“对象头”可以是任意对象,只要符合逻辑即可。

2.3.1修饰代码块

可以明确指定锁哪个对象。

锁任意对象,示例如下::

public class Main {
    public static Object object = new Object();
    public void method(){
        synchronized (object){
            //这里是锁的内容,为任意对象
        }
    }
}

锁当前对象,示例如下::

public class Main {
    public void method(){
        synchronized (this){
            //这里是锁的内容,为当前对象
        }
    }
}
2.3.2直接修饰普通方法

示例如下:

public class Main {
    public synchronized void method(){
        //对整个普通方法加锁
    }
}
2.3.3修饰静态方法

示例如下:

public class Main {
    public synchronized static void method(){
        //对整个静态方法加锁
    }
}

以上便是Java多线程编程中监视器锁的介绍。

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加菲猫-siu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值