JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

简介: JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 一.静态同步函数的锁是class对象 我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢? 我们做一个这样的小实验,我们给show.

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

一.静态同步函数的锁是class对象

我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢?

我们做一个这样的小实验,我们给show方法加上static关键字去修饰

private static synchronized void show() {
        if (tick > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "show:" + tick--);
        }
    }

然后我们来打印一下

这里写图片描述

发现他打印出0票了,说明他还是存在this隐患,同时也说明了一点就是他使用的锁不是this,那既然不是this,那是什么呢?

  • 因为静态方法中,不可以定义this,我们可以分析,静态进内存中,内存中没有本类对象,但是一定有该类的字节码文件对象类名.class,我们可以这样同步
synchronized (MyThread.class)

你就会发现,线程是安全的了

静态同步的方法,使用的锁是该字节码的对象 类名.class

二.多线成中的单例设计模式

还记得我们讲的单例设计模式吗?我们今年温习一下这两个实现单例模式的方法

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {

        /**
         * 单例设计模式
         */

    }
}

/**
 * 饿汉式
 * 
 * @author LGL
 *
 */
class Single1 {
    private static final Single1 s = new Single1();

    private Single1() {

    }

    public static Single1 getInstance() {
        return s;
    }
}

/**
 * 懒汉式
 * @author LGL
 *
 */
class Single {
    private static Single s = null;

    private Single() {

    }

    public static Single getInstance() {
        if (s == null) {
            s = new Single();
        }
        return s;
    }

}

我们着重点来看懒汉式,你会发现这个s是共享数据,所以我们所以延迟访问的话,一定会出现安全隐患的,但是我们使用synchronized来修饰的话,多线程启动每次都要判断有没有锁,势必会麻烦的,所以我们可以这样写

public static Single getInstance() {
        if (s == null) {
            synchronized (Single.class) {
                if (s == null) {
                    s = new Single();
                }
            }
        }
        return s;
    }

这样其实是比较麻烦的,我们用饿汉式比较多,懒汉式作用是延时加载,多线成访问就会有安全问题

三.多线程的死锁

我们同步当中会产生一个问题,那就是死锁

  • 同步中嵌套同步

是怎么个意思?我们来实现一下这段代码

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {

        /**
         * 需求:简单的卖票程序,多个线程同时卖票
         */
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);

        t1.start();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        myThread.flag = false;
        t2.start();
    }

}

/**
 * 卖票程序
 * 
 * @author LGL
 *
 */
class MyThread implements Runnable {

    // 票数
    private int tick = 100;

    Object j = new Object();

    boolean flag = true;

    @Override
    public void run() {

        if (flag) {
            while (true) {
                synchronized (j) {
                    show();
                }
            }
        } else {
            while (true) {
                show();
            }
        }

    }

    private synchronized void show() {
        synchronized (j) {
            if (tick > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "show:" + tick--);
            }
        }
    }
}

这段代码里面,this锁中又object锁,object中又this锁,就会导致死锁,不信?我们运行一下

这里写图片描述

你会看到他会停止不动了,这就是死锁,而在我们开发中,我们应该尽量避免死锁的发生。

四.线程中的通讯

线程中的通讯,是比较重要的,我们看一下这张例图

这里写图片描述

存什么,取什么

线程中通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。我们来具体看看例子

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {
        /**
         * 线程间通讯
         */
    }

}

// 资源
class Res {
    String name;
    String sex;
}

// 输入
class Input implements Runnable {

    @Override
    public void run() {

    }

}

// 输出
class Output implements Runnable {

    @Override
    public void run() {

    }

}

我们定义这些个类,对吧,一个资源,两个操作,紧接着,我们应该怎么去操作他?

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {
        /**
         * 线程间通讯
         */

        Res s = new Res();
        Input in = new Input(s);
        Output out = new Output(s);

        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);

        t1.start();
        t2.start();

    }

}

// 资源
class Res {
    String name;
    String sex;
}

// 输入
class Input implements Runnable {

    private Res s;

    public Input(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        int x = 0;

        while (true) {

            if (x == 0) {
                s.name = "lgl";
                s.sex = "男";
            } else if (x == 1) {
                s.name = "zhangsan";
                s.sex = "女";
            }
            // 交替
            x = (x + 1) % 2;
        }
    }

}

// 输出
class Output implements Runnable {

    private Res s;

    public Output(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        while (true) {
            System.out.println(s.name + "..." + s.sex);
        }
    }

}

这样去操作,你看下输出,这里出现了一个有意思的现象

这里写图片描述

你回发现他输出的竟然有女,这就是存在了安全隐患,但是也进一步的证实了,线程间的通讯

五.线程通讯带来的安全隐患

我们线程通讯,会有安全隐患,那已经怎么去解决呢?我们是不是一来就想到了同步synchronized?其实这样做没用的, 因为你传的锁是不一样的,你要想让锁唯一,就类名.class

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {
        /**
         * 线程间通讯
         */

        Res s = new Res();
        Input in = new Input(s);
        Output out = new Output(s);

        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);

        t1.start();
        t2.start();

    }

}

// 资源
class Res {
    String name;
    String sex;
}

// 输入
class Input implements Runnable {

    private Res s;

    Object o = new Object();

    public Input(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        int x = 0;

        while (true) {
            synchronized (Input.class) {
                if (x == 0) {
                    s.name = "lgl";
                    s.sex = "男";
                } else if (x == 1) {
                    s.name = "zhangsan";
                    s.sex = "女";
                }
                // 交替
                x = (x + 1) % 2;
            }
        }
    }

}

// 输出
class Output implements Runnable {

    private Res s;

    public Output(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (Input.class) {
                System.out.println(s.name + "..." + s.sex);
            }
        }
    }

}

这样,就解决了问题了

六.多线程等待唤醒机制

我们不需要多线成高速消耗CPU,而是在适当的时候唤醒他,所以我们需要定义一个布尔值

package com.lgl.hellojava;

//公共的   类   类名
public class HelloJJAVA {

    public static void main(String[] args) {
        /**
         * 线程间通讯
         */

        Res s = new Res();
        Input in = new Input(s);
        Output out = new Output(s);

        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);

        t1.start();
        t2.start();

    }

}

// 资源
class Res {
    String name;
    String sex;
    boolean flag = false;
}

// 输入
class Input implements Runnable {

    private Res s;

    Object o = new Object();

    public Input(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        int x = 0;

        while (true) {
            synchronized (Input.class) {

                if (s.flag) {
                    try {
                        // 等待线程都存放在线程池
                        wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

                if (x == 0) {
                    s.name = "lgl";
                    s.sex = "男";
                } else if (x == 1) {
                    s.name = "zhangsan";
                    s.sex = "女";
                }
                // 交替
                x = (x + 1) % 2;
                s.flag = true;
                // 通知
                notify();
            }
        }
    }

}

// 输出
class Output implements Runnable {

    private Res s;

    public Output(Res s) {
        this.s = s;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (Input.class) {
                if (!s.flag) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                } else {
                    System.out.println(s.name + "..." + s.sex);
                    s.flag = false;
                    notify();
                }

            }
        }
    }

}

都使用在同步中,因为要对待件监视器(锁)的线程操作,所以要使用在线程中,因为只有同步才具有锁

为什么这些操作线程的方法要定义在Object类中呢?因为这些方法在操作同步线程中,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上notify,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一把锁!而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

我们今天介绍就先到这里,线程的概念比较多,我们要写好几篇!!!

原文地址http://www.bieryun.com/2751.html

相关文章
|
9月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
1062 157
|
9月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
456 1
|
设计模式 存储 Java
【再谈设计模式】备忘录模式~对象状态的守护者
备忘录模式属于行为型设计模式。它的主要目的是在不破坏对象封装性的前提下,捕获并外部化一个对象的内部状态,以便之后可以将该对象恢复到这个状态。原发器(Originator):创建一个备忘录,用于记录当前时刻它的内部状态。原发器还可以使用备忘录来恢复其内部状态。备忘录(Memento):存储原发器对象的内部状态。备忘录应该防止原发器以外的其他对象访问其内部状态。负责人(Caretaker):负责保存备忘录,但不能对备忘录的内容进行操作或检查。
511 82
|
设计模式 供应链 安全
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
中介者模式定义了一个中介对象来封装一组对象之间的交互方式。中介者使得各对象之间不需要显式地相互引用,从而降低了它们之间的耦合度。它通过将对象之间的交互逻辑集中到中介者对象中,使得系统的结构更加清晰,易于维护和扩展。
433 18
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
|
设计模式 监控 Java
并发设计模式实战系列(6):读写锁
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发设计模式实战系列,第六章,废话不多说直接开始~
227 0
|
设计模式 Java Go
【再谈设计模式】状态模式~对象行为的状态驱动者
状态模式属于行为型设计模式。它将对象的行为封装在不同的状态类中,使得对象在不同的状态下表现出不同的行为。上下文(Context):这是一个包含状态对象的类,它定义了客户感兴趣的接口,并维护一个具体状态对象的引用。上下文将操作委托给当前的状态对象来处理。抽象状态(State):这是一个抽象类或者接口,它定义了一个特定状态下的行为接口。所有具体的状态类都实现这个接口。具体状态(Concrete State):这些是实现抽象状态接口的类,每个具体状态类实现了与该状态相关的行为。
676 18
|
设计模式 缓存 安全
「全网最细 + 实战源码案例」设计模式——单例设计模式
单例模式是一种创建型设计模式,确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。它常用于控制共享资源的访问,如数据库连接、配置管理等。实现方式包括饿汉式(类加载时初始化)、懒汉式(延迟加载)、双重检查锁、静态内部类和枚举单例等。其中,枚举单例最简单且安全,能有效防止反射和序列化破坏。
435 7
|
设计模式 算法 Java
【再谈设计模式】访问者模式~操作对象结构的新视角
  访问者模式是一种行为设计模式,旨在解决对象结构与操作逻辑的耦合问题。在软件系统开发中,当面临复杂的对象结构(如多种类型对象组成的树形或图形结构),且需要对这些对象执行不同操作时,传统方式将操作直接写在对象类中会导致类职责过多,不利于维护和扩展。而访问者模式通过将操作与对象结构分离,允许在不改变现有对象结构的情况下定义新操作,元素接受访问者访问,访问者定义对不同类型元素的操作逻辑,从而为应对这种复杂情况提供了有效的解决方案。
322 0
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
314 2
|
设计模式 缓存 供应链
读写锁分离设计模式详解
大家好,我是V哥。在商城系统中,用户浏览商品详情页时可以查看库存数量,这是高频的读操作;当用户下单成功时,系统会更新库存数量,这是低频的写操作。为了提升系统的并发性和性能,读写锁分离设计模式是最好的选择。该模式通过将读操作和写操作分离,允许多个读线程并发执行,而写操作则需要独占锁,确保数据一致性。适用于读多写少的场景,如库存管理、缓存读取等。通过读写锁分离,可以显著提升系统的性能和用户体验。
323 5

热门文章

最新文章