Java多线程(三)同步与锁定

多线程系列(十二) -生产者和消费者模型 在 Java 多线程编程中,还有一个非常重要的设计模式,它就是:生产者和消费者模型。 阅读详情

转载请注明出处:http://blog.youkuaiyun.com/github_39430101/article/details/77488972
在并发编程中发生的最常见的一种情况是超过一个执行线程使用共享资源。

Java内存模型与多线程

Jvm有主内存(Main Memory)和工作内存,主内存其实就是我们平时说的Java堆内存,存放程序中所有的类实例、静态数据等变量,是多个线程共享的,而工作内存存放的是该线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每个线程私有的其他线程不能访问,每个线程对变量的操作都是以先从主内存将其拷贝到工作内存再对其进行操作的方式进行,多个线程之间不能直接互相传递数据通信,只能通过共享变量来进行。

这里写图片描述

同步

隐式锁synchronized,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该代码。synchronized修饰地方只有两个:

  • 一是在方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前的方法名上面,代码如下:
public synchronized void synMethod(){ 
    .....
}
  • 二是修饰在代码块上面的,对某一代码块使用synchronized(Object),指定加锁对象:
public int synMethod(int a){
    synchronized(this){
        .....
    }
}
示例
public class Web12306 implements Runnable {
    private int num = 10;
    @Override
    public void run() {
        synchronized(this){  
           while(true){
            if(num<=0){
                break; //跳出循环
            }      
           System.out.println(Thread.currentThread().getName()+"抢到了"+num--); 
        }  
      }
  }

    public static void main(String[] args) {
        Web12306 web = new Web12306();
        Thread t1 = new Thread(web,"路人甲");
        Thread t2 = new Thread(web,"黄牛乙");
        Thread t3 = new Thread(web,"攻城狮");
        t1.start();
        t2.start();
        t3.start();
    }

}
//输出:
路人甲抢到了10
路人甲抢到了9
路人甲抢到了8
路人甲抢到了7
路人甲抢到了6
路人甲抢到了5
路人甲抢到了4
路人甲抢到了3
路人甲抢到了2
路人甲抢到了1

死锁

过多的同步容易造成死锁,这里我们举一个一手给钱一手给货的例子

public class Demo{
    public static void main(String[] args) {
        Object g = new Object();
        Object m = new Object();
        Test t1 = new Test(g,m);
        Test2 t2 = new Test2(g,m);
        Thread proxy = new Thread(t1);
        Thread proxy2 = new Thread(t2);
        proxy.start();
        proxy2.start();
    }
}
class Test implements Runnable{
    public Test(Object goods, Object money) {
        super();
        this.goods = goods;
        this.money = money;
    }
    Object goods;
    Object money;

    @Override
    public void run() {
        while(true){
            test();
        }
    }
    public void test(){
        synchronized(goods){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(money){

            }
        }
        System.out.println("一手给钱");
    }
}
class Test2 implements Runnable{
    Object goods ;
    public Test2(Object goods, Object money) {
        super();
        this.goods = goods;
        this.money = money;
    }
    Object money ;

    @Override
    public void run() {
        while(true){
            test();
        }
    }
    public void test(){
        synchronized(money){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(goods){

            }
        }
        System.out.println("一手给货");
    }
}
生产者消费者模式(用来平衡生产者和消费者的处理能力)

生产者消费者问题是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程–即所谓的生产者和消费者在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。于此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区慢时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,载唤醒消费者。通常常用的方法有信号灯法、管程等。如果解决方法不够完成,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
这里写图片描述
信号灯法:
创建一个Movie类

public class Movie {
    private String pic;
    /*
     * 信号灯 flag -->true 生产者生产,消费者等待,生产完成后通知消费 flag -->false
     * 消费者消费,生产者等待,消费完成后通知生产
     */
    private boolean flag = true;

    public synchronized void play(String pic) {
        if (!flag) { // 等待
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //开始生产
        try {
            Thread.sleep(500);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("生产了"+pic);
        //生产完毕
        this.pic = pic;
        //通知消费者
        this.notify();
        //生产者停下
        this.flag = false;
    }

    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //开始消费
        try {
            Thread.sleep(200);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("消费了"+pic);
        //消费完毕
        //通知生产
        this.notifyAll();
        this.flag = true;
    }
}

生产者电影工厂

public class Player implements Runnable {
    private Movie m;
    public Player(Movie m) {
        super();
        this.m = m;
    }
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            if(0==i%2){
                m.play("飞跃疯人院");
            }else 
                m.play("肖申克的救赎");
        }
    }

}

消费者

public class Watcher implements Runnable {
    private Movie m;
    public Watcher(Movie m) {
        super();
        this.m = m;
    }
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            m.watch();
        }
    }

}

主线程

public class App {
    public static void main(String[] args) {
        Movie m = new Movie();
        Player p = new Player(m);
        Watcher w = new Watcher(m);
        new Thread(p).start();
        new Thread(w).start();
    }
}

运行结果:
这里写图片描述

任务调度

  • Timer定时器类
  • TimerTask任务类
  • 通过java timer timertask:(Spring的任务调度就是通过他们来实现的)
  • 在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其他线程的。而TimerTask类是一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力
  • 在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
  • 在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
public class TimeDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            @Override
            public void run(){
                System.out.println("so easy....");
            }
        }, new Date(System.currentTimeMillis()+1000), 200);
    }
}

运行结果:
这里写图片描述

总结

1、sleep()

使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。

2、join()

join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。

3、yield()

该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。不会释放锁标志。

4、wait()和notify()、notifyAll()

这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

5、关键字synchronized
该关键字用于保护共享数据,当然前提条件是要分清哪些数据是共享数据。每个对象都有一个锁标志,当一个线程访问到该对象,被Synchronized修饰的数据将被”上锁”,阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了。

JAVA多线程之并发与同步,Lock与Syncronized 说到线程,就不得不先说线程和进程的关系。进程是系统的执行单位,一般一个应用程序即是一个进程,程序启动时系统默认有一个主线程,即是UI线程,我们知道不能做耗时任务,否则ANR程序无响应。这时需要借助子线程实现,即多线程。由于线程是系统CPU的最小单位,用多线程其实就是为了更好的利用cpu的资源。一、多线程创建方式//线程休眠1000毫秒,sleep使线程进入Block状态,并释放资源xx.start();//启动线程,run函数运行@Override@Override。 阅读详情

相关推荐

Java中多线程同步问题的解决方法

Java中的多线程同步问题是一个复杂的问题,但通过使用上述的多种方法,可以有效地解决这些问题。在实际开发中,应根据具体的需求和场景选择合适的同步方法。例如,对于简单的同步需求,可以使用关键字;对于需要更灵活锁机制的场景,可以使用;对于需要控制线程访问资源数量的场景,可以使用Semaphore等。同时,还应注意避免过度同步,以免影响程序的性能。希望本文的内容能够帮助读者更好地理解和解决Java多线程同步问题。

shejizuopin的博客 1128

深入浅出生产者-消费者模式

笔者也建立的自己的公众号啦,平时会分享一些编程知识,欢迎各位大佬支持~ 扫码或微信搜索北风IT之路关注 生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案。也经常有面试官会让手写一个生产者消费者,从代码细节可以看出你对多线程编程的熟练程度,今天我们来详细看一下如何写出一个生产者消费者模式,并且逐步对其优化争取做到高性能。 结构剖析 在生产者-消费者模式中,通常有...

beifengtz 2609

java多线程——线程同步问题

试想一个场景:同一个银行账户有1000RMB,两个对象同时对该账户取钱,两人各取800,流程如下: 输入帐号、密码,验证成功 输入取钱金额,系统比较账户余额和取钱金额 验证成功,允许取钱操作,然后,两个人一起开开心心的拿着1.6k回家了。剩下一个余额为-600的账户默默哭泣。 这就是我们常说的:多线程并发操作线程安全问题。 就取钱例子模拟代码实现: 1、定义一个账户

Think In JAVA—Max 1612

(四)生产者消费者模式

(一)生产者消费者模式原理: 在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。生产消费者模式如下图: (二)代码实现 生产者是一堆线程,消费者是另一堆线程,内存缓冲区可以使用List数组队列,数据类型只需要定义一个简单的类就好。最关键就是内存缓冲区为空的时候消费者必须等待,而内存缓冲区满的时候,生产者必须等待。其他时候可以是个动态平衡:   首先设置Market类: 其中变量有:Max(仓库最大值),List(用于存放数据) 方法有

roothahh的博客 9788

架构设计 -- 生产者/消费者模式

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

线程问题生产者消费者流程图

生产者消费者流程图; 生产者消费者流程图。

生产者/消费者模式(转载)

★简介     在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。     单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而

w57w57w57的专栏 3657

信号完整性仿真SI之Cadence Sigrity SPEED2000_眼图仿真(四)

今天给大家分享一个仿真的操作流程,是关于Cadence Sigrity SPEED2000的时域波形仿真和眼图仿真。我们会经常看见硬件工程师调试示波器上的一些信号的波形变化,其实这些也可以通过仿真电路的搭建来做出投板前的仿真,来优化走线,以减少版本的迭代次数。如果要和实际的信号保持一致,就要求仿真设置的参数和实际的参数保持一致,这也是仿真的重点和难点。下面先让我们了解一下Cadence Sigrity SPEED2000时域仿真的操作流程: 1.仿真模式选择与文件导入 选择Cadence Sigrity下的

刘小同学的博客 1万+

浅谈Java 多线程同步

最近在研究多线程同步的一些问题,整理了网上很多文档,在这里给大家分享下 Java并发机制的底层实现原理 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。 多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数...

持之以恒! 3394

《Halcon 100项目-9》Halcon彩色线序检查

Halcon彩色线序检查

doubleintfloat的博客 1043

不止于HTTPS请求:用VS2022编译的libcurl玩转文件上传和代理设置

本文详细介绍了如何使用VS2022编译的libcurl库实现文件上传和高级代理配置。通过实战代码示例,展示了多部分表单上传、二进制数据上传以及各种代理设置技巧,帮助C++开发者提升网络编程能力。

weixin_28595749的博客 206

Java进阶 ——— Java多线程(三)之多线程同步问题

引言 接上一篇,Java进阶 ——— Java多线程(二)之如何开启多线程 介绍了Java多线程的开启方法,但是多线程运行的安全问题,将是本篇的重点 延伸阅读,Java多线程系列文章 Java进阶 ——— Java多线程(一)之进程和线程 Java进阶 ——— Java多线程(二)之如何开启多线程 在第一篇文章中,提到要实现多线程安全,就要实现线程同步,那么线程同步有哪些方法呢? 介绍线程同步...

dai的博客 1310

EverOS:为AI智能体构建长期记忆系统的完整指南

长期记忆是AI智能体实现持续学习和自我演进的核心能力,它通过结构化存储和关联检索历史交互信息,使智能体能够跨越单次对话的局限。其技术原理通常涉及向量数据库、记忆提取算法和知识图谱等组件,用于对非结构化对话内容进行编码、组织和索引。这一能力的技术价值在于显著提升了智能体的个性化服务水平、任务完成连贯性以及从经验中学习的能力,是实现真正“智能”伙伴的关键。在应用场景上,长期记忆系统广泛应用于个性化聊天伴侣、持续学习的编程助手、游戏NPC角色塑造以及客户服务机器人等领域。本文以EverOS为例,深入解析了其提供的

weixin_28679635的博客 449

深入理解Java中的多线程与同步

在计算机领域,多线程是指一个进程(程序的执行单元)中包含多个执行流。这意味着程序可以同时执行多个任务,提高了程序的并发性和响应性。Java通过java.lang.Thread类和java.util.concurrent包提供了丰富的多线程支持。

qq_28068311的博客 1289

Arduino智能小灯DIY:人体感应+光线感应+蓝牙控制全攻略(附完整代码)

本文提供了一份完整的Arduino智能小灯DIY制作指南。项目融合了人体感应、光线感应与蓝牙控制三大功能,通过详细的硬件清单、电路连接图、核心逻辑讲解及附带的完整代码,手把手教你打造一个能自动感知环境与人、并支持手机远程控制的智能照明装置,是入门智能家居和物联网开发的绝佳实践。

java5的博客 1094

无人机视角下基于YOLOv5/v8/v10的车辆检测系统:从算法原理到UI界面实战

随着无人机技术的普及和智慧城市的发展,无人机视角下的车辆检测在交通监控、战场侦察、灾害救援等领域展现出巨大的应用价值。然而,无人机航拍图像存在目标尺度小、视角多变、背景复杂等挑战。本文深入探讨了如何利用YOLOv5、YOLOv8、YOLOv10三个代表性目标检测算法,构建一套完整的无人机车辆检测系统。文章不仅详细解析了各版本YOLO的核心技术演进,还提供了从数据集准备、模型训练、性能评估到基于PyQt5/PySide6的图形界面开发的完整代码实现。通过本文,读者将能够掌握构建无人机实时车辆检测系统的全流程技

YOLO 172

java中线程对象同步和方法同步_多线程 - Java同步方法锁定对象或方法?

多线程 - Java同步方法锁定对象或方法?如果我在同一个类中有2个同步方法,但每个方法访问不同的变量,那么2个线程可以同时访问这两个方法吗? 锁是否发生在对象上,或者它是否与synchronized方法中的变量一样具体?例:class X {private int a;private int b;public synchronized void addA(){a++;}public synch...

weixin_29061041的博客 288

流体仿真软件:COMSOL Multiphysics二次开发_(11).COMSOL与外部软件的数据交换

通过上述方法,您可以实现 COMSOL Multiphysics 与外部软件的数据交换,从而更好地集成和分析仿真结果。无论是从文本文件导入数据,导出数据到 MATLAB,还是通过 API 和 COMSOL Server 实现实时通信,都能满足不同场景下的需求。希望这些内容对您在流体仿真领域的开发工作有所帮助。

kkchenjj的博客 1553

Java 多线程同步与死锁

在多线程中如何找到安全问题所在: 1,明确哪些代码是多线程运行代码 2,明确共享数据 3,明确多线程运行代码中哪些代码是操作共享数据的 静态的同步方法中,使用锁是该方法所在类的字节码文件对象,即 类名.class  前天俺们谈到了加锁,但是在使用加锁的同时又会带来一个问题,就是死锁。什么叫死锁? 所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而

百炼成钢 6733

一文整理深度学习【深度学习linux的docker+pytorch+cuda+nvidia-docker+vscode配置】

docker+pytorch+cuda+nvidia-docker+vscode

Edward的博客 1335

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Java线程同步和锁定

同步和锁定 Java中每个对象都有一个内置锁。 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。 释放锁是指持锁线程退出了synchronized同步方法或代码块。 关于锁和同步的几个要点 1)只能同步方法,而不能同步变量和类;

Joesa优快云的博客 3792

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

JAVA多线程之线程同步

线程同步、并发的基本概念以及解决问题的三种方法

qq_39367410的博客 3389

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Java多线程实现之同步方法详解

当多个线程同时访问共享资源时,可能会导致数据不一致等线程安全问题,Java提供了synchronized关键字来实现线程同步,确保同一时刻只有一个线程可以访问共享资源。本文我将详细介绍Java同步方法的原理、使用方式以及相关的最佳实践,帮你更好地理解和应用线程同步技术

aa_hdkf_vg的博客 1924

Java多线程(三)—线程同步

文章目录Java多线程—线程同步同步代码块同步方法释放同步监视器同步锁死锁 Java多线程—线程同步 当多个线程访问一个数据时,很容易出现线程安全问题,很大原因是线程不同步造成的。 同步代码块 为了解决这个问题,Java使用同步监视器来解决,方法就是使用同步代码块。 synchronized(obj){//obj同步监视器 //同步代码块 } 线程开始执行同步代码块前,必须获得对同步监视器的锁定,任何时刻只能有一个可以获得对同步监视器的锁定,执行完后将会释放。例如使用sync.

沧海一粟 344

Java多线程同步Synchronized深入解析

同步的概念: 同步分为 同步方法 和 同步块 两种方式。 锁定的内容分为 锁定类的某个特定实例 和 锁定类对象(类的所有实例) 变量分为 实例变量(不带static的变量) 和 类变量(带static的变量) 使用同步的原因 1. 在系统中对访类要使用多线程进行访问; ...

chouxie8829的博客 136
上一篇: Java多线程(二)认识Java里面的Thread
下一篇: Weblogic安装及配置(linux环境)
武装
博客等级 码龄9年 36粉丝 35原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值