0
点赞
收藏
分享

微信扫一扫

java多线程编程:关于 synchronized 的一点理解;synchronized 的应用场景;形象举例如何正确加 synchronized

有点d伤 2022-03-23 阅读 27

文章目录

应该关注的问题

  • 锁是怎么产生的
  • synchronized 关键字锁的是谁
  • 需要等待的是谁
  • 怎么上锁才是对的

锁是怎么产生的

  • 这里你只需要记住一句话:
    • 每个对象产生的时候自己就带了一把锁
    • 我知道你以前可能想的是:我们需要创建一把锁,然后把。。。锁住,其实不是的,每个对象在产生的时候,自己就有一把锁,只是你看不见,只有再用 synchronized 的时候才能有用
    • 比如现在你有一个 Toilet 类,那么这个 Toiltet 创建的实例自身就带锁;你可以这么理解,每个厕所被生产出来的时候,大门上就会带一把锁。

synchronize 关键字锁的是谁

  • synchronized 可以锁三种东西:
    • synchronized 关键字加载静态方法上锁的是类对象
    • synchronized 关键字加载实例方法上锁的是这个实例(也就是 this
    • synchronized 关键字加载代码块上锁的是依然是实例对象,只不过相比于第二种方式,这样的效率会更高一些

锁非静态方法(实例方法)

  • 对一个实例对象进行限制,我们看下面的代码
    • 构造一个 Toilet 类,创建一个 Toilet 对象,名为:城南厕所;就像我们上面提到的,它在创建的时候就会有一把锁
    • 创建一个 Person 类,产生 1000 个排队上厕所的人。
    • 1000 个排队上厕所的人争夺的是这个 “城南厕所” 的对象。
    • 如果我们想让这些人在上厕所的时候不被打扰,应该给哪个代码段加 synchronized 关键字呢?当然是把对 toilet 变量 “写” 操作的代码块加上 synchronized;我们来看代码示例,毕竟语言描述太不具体:talk is cheap, show me the code

Producer 来产生排队上厕所的人

  • 每个人都是一个线程
package Test;

import static java.lang.Thread.interrupted;
import static java.lang.Thread.sleep;

public class Producer implements Runnable{
    Toilet toilet;
    public Producer(Toilet toilet){
        this.toilet = toilet;
    }

    @Override
    public void run() {
        int counter = 0;
        while (!interrupted()){
            new Thread(new Person(""+counter, toilet)).start();
            counter ++;

            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Toilet 公共厕所,构建实例“城南厕所”

package Test;

import static java.lang.Thread.interrupted;
import static java.lang.Thread.sleep;

public class Toilet implements Runnable{

    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }

    private Person person;
    String name;

    public Toilet(String toiletName){
        this.name = toiletName;
    }



    public static void main(String[] args) {

        Toilet toilet = new Toilet("城南厕所");
        new Thread(toilet).start();

        Producer producer = new Producer(toilet);
        new Thread(producer).start();

    }

    @Override
    public void run() {
        while (!interrupted()){

            if (person != null){
               
                System.out.println(person.name + "is peeing");
                System.out.println("please, wait");

                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(person.name + "leaves the toilet");

                person = null;
                
            }
            
        }
    }
}

Person 上厕所

package Test;



public class Person implements Runnable {
    String name;
    Toilet toilet;

    public Person(String name, Toilet toilet){
        this.name = name;
        this.toilet = toilet;
        System.out.println(name + "is creating");
    }


    public void pee(){


        this.toilet.setPerson(this);


    }

    @Override
    public void run() {
        pee();
    }

}

  • 根据输出的日志记录,很容易发现,厕所同时进了很多人,一个还没有完成,另外一个人就进来了;例如 0 号并没有走出 toilet ,反而是 9 号 先离开 toilet
  • 我们在很多例子中都知道这是因为线程安全问题导致的,我们要用 synchronized 关键字来实现同步,但你真的知道加在哪里么?

不易察觉的错误

  • 按照上面我们说的, toilet 之所以被很多人同时访问,肯定是因为 toiletperson 属性同时被多个人修改,那我把那段代码通过 synchronized 锁起来肯定就没事了。所以 toilet 代码如下修改:
    @Override
    public void run() {
        while (!interrupted()){
			// 在这里锁起来,保证这段代码是安全的
            synchronized (this){
                if (person != null){

                    System.out.println(person.name + "is peeing");
                    System.out.println("please, wait");

                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(person.name + "leaves the toilet");

                    person = null;

                }
            }
  • 我们只修改了 run 中的内容,把修改 toilet 变量的代码上锁,结果如何呢?

Process finished with exit code -1

  • 你会发现,其实问题并没有被解决。
  • 乍一看好像是 sleep 的问题。是不是因为 sleep 的时候,线程把锁释放了,然后让其他的线程访问了呢?
  • 当然不是,sleep 的时候,线程依然持有锁,这个部分大家可以去看 sleepwait 的区别,由于这块不是本文重点,所以不展开讲。
  • 那究竟是什么原因导致这块代码没有被锁住呢?

答案揭晓

在这里插入图片描述

  • 看似在下面的代码中你锁住了 person 不能被多个线程随意修改,但其实根本没有被锁住,因为上面的 setPerson 方法是线程不安全的,换句话说,没有用 synchronized 关键字限制。这样就导致了在多个 Person 的线程中,他们都可以用 setPerson 方法同时地修改 toiletperson 属性,导致 toiletperson 还是在不断地被修改。
    在这里插入图片描述
  • 所以我们要保证线程安全就要使用下面的代码,把真正关乎 toiletperson 属性修改的方法给锁住,就能够保证绝对安全了。

正确操作

package Test;

import static java.lang.Thread.interrupted;
import static java.lang.Thread.sleep;

public class Toilet implements Runnable{

    public Person getPerson() {
        return person;
    }
    public synchronized void setPerson(Person person) {
        this.person = person;
    }

    private Person person;
    String name;

    public Toilet(String toiletName){
        this.name = toiletName;
    }

    public static void main(String[] args) {

        Toilet toilet = new Toilet("城南厕所");
        new Thread(toilet).start();

        Producer producer = new Producer(toilet);
        new Thread(producer).start();

    }

    @Override
    public void run() {
        while (!interrupted()){
            synchronized (this){
                if (person != null){

                    System.out.println(person.name + "is peeing");
                    System.out.println("please, wait");

                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(person.name + "leaves the toilet");

                    person = null;

                }
            }
        }
    }
}

锁静态方法

  • synchronized 关键字放在静态方法上,就是对整个类对象进行限制。
  • 通过厕所的方法进行说明的话就是:
    • 在上面的例子中,如果很多人排队在 “城南厕所” 门口,当 synchronized 加在非静态的代码上时代表一个人上城南厕所,其他人不能上城南厕所,但是如果这时候有 “城北厕所”,他们可以去 “城北厕所”
    • 但是如果 synchronized 加在了静态方法上。那么当一个人进了 “城南厕所”,“城北厕所” 也会上锁,从而导致一个厕所锁住,所有厕所都不能上。换句话说,把这个类的所有实例都锁住了。

锁代码块

  • synchronized 加在代码块上,锁的还是实例对象,但是这样做的效率比加载实例方法上效率高
举报

相关推荐

0 条评论