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

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

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


JAVA之旅,一路有你,加油!

一.静态同步函数的锁是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类中。

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

如果有兴趣,可以加群:555974449

目录
相关文章
|
2月前
|
设计模式 存储 Java
【再谈设计模式】备忘录模式~对象状态的守护者
备忘录模式属于行为型设计模式。它的主要目的是在不破坏对象封装性的前提下,捕获并外部化一个对象的内部状态,以便之后可以将该对象恢复到这个状态。原发器(Originator):创建一个备忘录,用于记录当前时刻它的内部状态。原发器还可以使用备忘录来恢复其内部状态。备忘录(Memento):存储原发器对象的内部状态。备忘录应该防止原发器以外的其他对象访问其内部状态。负责人(Caretaker):负责保存备忘录,但不能对备忘录的内容进行操作或检查。
222 82
|
3月前
|
设计模式 供应链 安全
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
中介者模式定义了一个中介对象来封装一组对象之间的交互方式。中介者使得各对象之间不需要显式地相互引用,从而降低了它们之间的耦合度。它通过将对象之间的交互逻辑集中到中介者对象中,使得系统的结构更加清晰,易于维护和扩展。
79 18
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
|
4月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
317 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
3月前
|
安全 Java 开发者
Java并发迷宫:同步的魔法与死锁的诅咒
在Java并发编程中,合理使用同步机制可以确保线程安全,避免数据不一致的问题。然而,必须警惕死锁的出现,采取适当的预防措施。通过理解同步的原理和死锁的成因,并应用有效的设计和编码实践,可以构建出高效、健壮的多线程应用程序。
67 21
|
3月前
|
设计模式 Java Go
【再谈设计模式】状态模式~对象行为的状态驱动者
状态模式属于行为型设计模式。它将对象的行为封装在不同的状态类中,使得对象在不同的状态下表现出不同的行为。上下文(Context):这是一个包含状态对象的类,它定义了客户感兴趣的接口,并维护一个具体状态对象的引用。上下文将操作委托给当前的状态对象来处理。抽象状态(State):这是一个抽象类或者接口,它定义了一个特定状态下的行为接口。所有具体的状态类都实现这个接口。具体状态(Concrete State):这些是实现抽象状态接口的类,每个具体状态类实现了与该状态相关的行为。
90 18
|
3月前
|
设计模式 算法 Java
【再谈设计模式】访问者模式~操作对象结构的新视角
  访问者模式是一种行为设计模式,旨在解决对象结构与操作逻辑的耦合问题。在软件系统开发中,当面临复杂的对象结构(如多种类型对象组成的树形或图形结构),且需要对这些对象执行不同操作时,传统方式将操作直接写在对象类中会导致类职责过多,不利于维护和扩展。而访问者模式通过将操作与对象结构分离,允许在不改变现有对象结构的情况下定义新操作,元素接受访问者访问,访问者定义对不同类型元素的操作逻辑,从而为应对这种复杂情况提供了有效的解决方案。
64 0
|
5月前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
174 3
|
6月前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
163 2
java 中 i++ 到底是否线程安全?
|
6月前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
159 2
|
6月前
|
Java 编译器 Maven
Java“class file contains wrong class”解决
当Java程序运行时出现“class file contains wrong class”错误,通常是因为类文件与预期的类名不匹配。解决方法包括:1. 确保类名和文件名一致;2. 清理并重新编译项目;3. 检查包声明是否正确。
171 3