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

相关文章
|
1月前
|
设计模式 缓存 供应链
读写锁分离设计模式详解
大家好,我是V哥。在商城系统中,用户浏览商品详情页时可以查看库存数量,这是高频的读操作;当用户下单成功时,系统会更新库存数量,这是低频的写操作。为了提升系统的并发性和性能,读写锁分离设计模式是最好的选择。该模式通过将读操作和写操作分离,允许多个读线程并发执行,而写操作则需要独占锁,确保数据一致性。适用于读多写少的场景,如库存管理、缓存读取等。通过读写锁分离,可以显著提升系统的性能和用户体验。
|
3月前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
4月前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
4月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
4月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
71 1
|
4月前
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
|
5月前
|
缓存 安全 Java
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
|
4月前
|
设计模式 SQL 安全
单例模式大全:细说七种线程安全的Java单例实现,及数种打破单例的手段!
设计模式,这是编程中的灵魂,用好不同的设计模式,能使你的代码更优雅/健壮、维护性更强、灵活性更高,而众多设计模式中最出名、最广为人知的就是Singleton Pattern单例模式。通过单例模式,我们就可以避免由于多个实例的创建和销毁带来的额外开销,本文就来一起聊聊单例模式。
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
58 1
C++ 多线程之初识多线程
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
下一篇
DataWorks