博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制...
阅读量:6340 次
发布时间:2019-06-22

本文共 8667 字,大约阅读时间需要 28 分钟。

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类中。

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

原文地址

你可能感兴趣的文章
U盘病毒原理分析和解决方法
查看>>
谈谈系统架构这个东西
查看>>
thunderbird 导入通讯录乱码问题解决
查看>>
[.Net线程处理系列]专题四:线程同步
查看>>
yum安装crontab
查看>>
JVM初探- 内存分配、GC原理与垃圾收集器
查看>>
Ruby和SHELL中如何遍历指定目录的文件
查看>>
LevelDB
查看>>
CentOS7下安装mysql5.6修改字符集为utf8并开放端口允许远程访问
查看>>
C#进阶系列——WebApi 跨域问题解决方案:CORS
查看>>
你真的会玩SQL吗?让人晕头转向的三值逻辑
查看>>
Unity 脚本的未来发展
查看>>
hdu 2055 An easy problem (java)
查看>>
JQuery:JQuery捕获HTML
查看>>
js自动闭合html标签,自动补全html标记
查看>>
cpu进程调度---RT Throttling【转】
查看>>
在MapGuide 的Fusion Viewer的选择面板中显示超链接
查看>>
CentOS7下单机部署RabbltMQ环境的操作记录
查看>>
unity shader tags
查看>>
挺有意思的,队列,先进先出,排队进行!
查看>>