Java多线程(二)线程同步

news2024/9/24 3:25:31

1、线程同步问题

当多个线程同时操作同一个数据时,就会产生线程同步问题。

为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”。当一个线程运行到需要同步的语句后,CPU不去执行其他线程中的、可能影响当前线程中的下一句代码的执行结果的代码块,必须等到下一句执行完后才能去执行其他线程中的相关代码块,这就是线程同步。

下面这个例子中,多个线程共同操作同一个账户里的余额,就有可能出现线程同步错误。

//模拟一个账户,其中有余额1000元。取钱时如果不足1000元就不能取。
public class Account {
    private int balance = 1000;
    public void qu(){
        if(balance>=1000){
            try {
                // 模拟CPU时间片到期,导致的线程切换
                Thread.sleep(100); // 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= 1000;
            System.out.println("取了1000元,balance = " + balance);
        }
    }
}

class ThreadTest extends Thread{
    private Account account;
    public ThreadTest(Account account){
        this.account = account;
    }
    public void run(){    
        account.qu();            //在线程类中调用取钱的方法
    }
}
//模拟多个人同时操作同一个账户。结果有可能出现这样一种情况:
//账户中共有余额1000元,但却被取走了2000元。
public static void main(String[] args) {
    Account account = new Account();
    ThreadTest tt1 = new ThreadTest(account);      //给每个线程传入的是同一个账户
    ThreadTest tt2 = new ThreadTest(account);      //给每个线程传入的是同一个账户
    tt1.start();
    tt2.start();    
}

运行结果:

取了1000元,balance = -1000
取了1000元,balance = -1000

2、线程同步解决方案一synchronized

解决办法:加上synchronized关键词,使取钱的方法成为一个同步方法。

一旦一个包含锁定方法(用synchronized修饰)的线程被CPU调用,其他线程就无法调用相同对象的锁定方法。当一个线程在一个锁定方法内部,所有试图调用该方法的同实例的其他线程必须等待

synchronized:同步锁(互斥锁)

1.在java语言中,引入了同步锁的概念,每个对象都有一个与之关联的内部锁(排他锁),用以保证共享数据的安全性问题。

2.关键词synchronized用来给某个方法或某段代码加上一个同步锁。

3.当调用者调用此方法时,必须获得这把锁才可以调用。

4.当某个调用者获得这把锁之后,其他调用者就无法获得了。

5.当调用结束后,调用者释放这把锁,此时其他调用者才可以获得。

6.这个机制保障了某个同步方法同时只能有一个调用者。

1.锁定方法

public class Account {
    private int balance = 1000;
    public  synchronized void qu(){ // 同步方法
        if(balance>=1000){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= 1000;
            System.out.println("取了1000元,balance = " + balance);
        }
    }
}

2.锁定代码块

public class Account {
    private int balance = 1000;
    public   void qu(){
        System.out.println("Account.qu");
        synchronized(this){ // 同步块
            if(balance>=1000){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                balance -= 1000;
                System.out.println("取了1000元,balance = " + balance);
            }
        }
    }
}

3、死锁问题

同步锁具有互斥作用,即排他性。但是这又造成了另外一个问题:死锁

为了完成一个功能,需要调用两个资源。但是,当两个线程同时调用这两

个资源时,就会出现这样的现象:两个线程都不放弃抢到的一个资源,而另一个资源却永远也抢不到。

例:

    public class TestLock {
        public static String objA = "strA";
        public static String objB = "strB";  
        
        public static void main(String[] args) {
            Lock1 l1=new Lock1();
            Thread t1=new Thread(l1);           
            Lock2 l2=new Lock2();
            Thread t2=new Thread(l2);
            
            t1.start();
            t2.start();
        }
    }
    public class Lock1 implements Runnable{  
        @Override
        public void run() {
            try{
                  System.out.println("Lock1 running");
                   while(true){
                       synchronized(TestLock.objA){
                          System.out.println("Lock1 lock strA");
                          Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
                           synchronized(TestLock.objB){
                                     System.out.println("Lock1 lock strB");
                            }
                        }
                    }
             }catch(Exception e){
                 e.printStackTrace();
             }  
        }  
    }
    package com.neuedu.thread.lock;   
    public class Lock2 implements Runnable{
        @Override
        public void run() {
            try{
                System.out.println("Lock2 running");
                 while(true){
                     synchronized(TestLock.objB){
                         System.out.println("Lock1 lock strB");
                        Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
                         synchronized(TestLock.objA){
                             System.out.println("Lock1 lock strA");
                         }
                     }
                 }
             }catch(Exception e){
                 e.printStackTrace();
             }      
        } 
    }

1.产生死锁的必要条件:

虽然线程在运行过程中可能会发生死锁,但产生死锁是必须具备一定条件的。产生死锁必须同时具备下面四个必要条件,只要其中任意一个条件不成立,死锁就不会产生:

  1. 互斥条件:线程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个线程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的线程释放该资源。
  2. 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己以获得的资源保持不放。
  3. 不可抢占条件:线程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放。
  4. 循环等待条件。在发生死锁时,必然存在一个线程—资源的循环链,即线程集合{P0,P1,P2,P3,...,Pn}中的P0正在等待P1占用的资源,P1正在等待P2占用的资源,... ... ,Pn正在等待已被P0占用的资源。

2.处理死锁的方法

  1. 预防死锁。该方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个来预防产生死锁。
  2. 避免死锁。在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而可以避免产生死锁。
  3. 检测死锁。通过检测机构及时地检测出死锁的发生,然后采取适当的措施,把进程从死锁中解脱出来。
  4. 解除死锁。当检测到系统中已发生死锁时,就采取相应的措施,将进程从死锁状态中解脱出来。常用方法是---撤销一些进程,回收他们的资源,将他们分配给已处于阻塞状态的进程,使其能继续运行。

4、 线程同步的第二种解决方案 wait() notify()

1.生产者消费者问题

生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个线程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。

package test06;
//仓库(有界缓冲区)
class Storage{
    //库存
	private int count = 0;
    //供生产者调用的生产的方法
	public synchronized void set(){
		if(count>=5){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count++;
		System.out.println("生产了一个,仓库中有:" + count);
		this.notify();
	}
    //供消费者调用的消费的方法
	public synchronized void get(){
		if(count<=0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count--;
		System.out.println("消费了一个,仓库中有:" + count);
		this.notify();
	}
}
//生产者
class Producer extends Thread{
	private Storage storage;
	public Producer(Storage storage){
		this.storage = storage;
	}
	public void run(){
		for(int i=0;i<50;i++){
			this.storage.set();
		}
	}
}
//消费者
class Customer extends Thread{
	private Storage storage;
	public Customer(Storage storage){
		this.storage = storage;
	}
	public void run(){
		for(int i=0;i<50;i++){
			this.storage.get();
		}
	}
}
public class Test2 {
	public static void main(String[] args) {
		Storage storage = new Storage();
		Producer producer = new Producer(storage);
		Customer customer = new Customer(storage);
		
		customer.start();
		producer.start();
	}
}

总结线程同步的常用方法有以下两种:

1、synchronized

2、wait与notify

5、 守护线程(了解)

Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。用个比较通俗的比喻,任何一个守护线程都是整个JVM中所有非守护线程的保姆。只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

thread.setDaemon(true)   	//设置线程为守护线程,该设置必须在thread.start()之前设置
thread.isDaemon() 			//可以判断当前线程是否为守护线程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2093754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记一次学习--webshell绕过(动态检测)

目录 第一种样本 代码分析 第二种样本 代码分析 题目分析 结果 不断学习&#xff0c;不断进步 快就是慢&#xff0c;慢就是快。审视自身 第一种样本 <?php class xxxd implements IteratorAggregate {public $xxx "system";public function __construct(…

C++ | Leetcode C++题解之第388题文件的最长绝对路径

题目&#xff1a; 题解&#xff1a; class Solution { public:int lengthLongestPath(string input) {int n input.size();int pos 0;int ans 0;vector<int> level(n 1);while (pos < n) {/* 检测当前文件的深度 */int depth 1;while (pos < n && in…

R语言统计分析——单因素协方差分析

参考资料&#xff1a;R语言实战【第2版】 单因素协方差分析&#xff08;ANCONA&#xff09;扩展了单因素方差分析&#xff08;ANOVA&#xff09;&#xff0c;包含一个或多个定量的协变量。下面使用multcomp包中的litter数据集进行操作&#xff1a; # 加载数据集 data(litter,p…

0-HDMI高速接口协议基础介绍

高清多媒体接口(HDMI&#xff0c;High Definition Multimedia Interface)是一种数字化视频/音频接 口技术&#xff0c;是适合影像传输的专用型数字化接口&#xff0c;其可同时传送音频和视频信号&#xff0c;同时无需在 信号传送前进行数/模或者模/数转换。从2002年HDMI发布最初…

Windows 环境nginx安装使用及目录结构详解

一、 Windows 环境nginx安装及基本使用 1、下载 nginx-1.27.1 最新的主线版本 安装 nginx/Windows&#xff0c;请下载1.27.1最新的主线版本&#xff0c; nginx 的主线分支包含所有已知的修复程序。 2、 解压缩 nginx-1.27.1 版本 nginx/Windows 作为标准控制台应用程序&#x…

YOLO | YOLO目标检测算法(分类、检测、分割)

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 分类、检测、分割 思考&#xff1a;计算机视觉能够解决哪些问题&#xff1f;&#xff1f;&#xff1f;&#xff1f;分类、检测、分割 分割&#xff1a;语义分割和实例分…

SOMYO™——将“照片”转为“手绘素描”的专业级软件

一定要往下看&#xff0c;最精彩的在最后哦&#xff01; 1 关于素描的几句话 素描是西方美术的灵魂。值得为自己、亲人与好友留下一张别致的素描。 素描的定义&#xff1a;艺术家通过线条的粗细、浓淡、疏密等变化&#xff0c;试图精准地再现形态的体感、质感和动感的艺术…

轻量级进程(LWP)和线程

线程是CPU调度的基本单位 进程是承担系统资源的基本实体(进程是资源分配的基本单位) 线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配 任何一个线程都可以创建或撤销另一个线程 多进程里&#xff0c;子进程可复制父进程的所有堆和栈的数据&#xff1b…

黑神话悟空-提高画质、防卡顿、修复等各种功能、各种CT表、各种存档、武器包、人物、装备替换等193+MOD合集

193MOD合集&#xff0c;提高画质&#xff0c;减少卡顿、修复等功能MOD各种CT表各种存档武器包、物品、人物、装备、造型替换等 具体MOD可在文件内查看 特别说明&#xff1a;3款珍品大圣套装替换初始套装MOD是不同的&#xff0c;&#xff08;其中全装备珍品大圣套装是不可以跳出…

笔记 12 : 彭老师课本第 6 章, PWM ,代码实践

&#xff08;85&#xff09; 驱动写多了&#xff0c;会发现&#xff0c;首先就是硬件的初始化&#xff0c;配置硬件。 &#xff08;86&#xff09;查看源代码组织&#xff1a; &#xff08;87&#xff09; 编译过程不变&#xff1a; &#xff08;88&#xff09; 运行同以前的步…

2024年8月总结及随笔之逝

1. 回头看 日更坚持了609天。 读《零信任网络&#xff1a;在不可信网络中构建安全系统》更新完成读《软件开发安全之道&#xff1a;概率、设计与实施》开更并持续更新 2023年至2024年8月底累计码字1463007字&#xff0c;累计日均码字2402字。 2024年8月码字109278字&#x…

全网最适合入门的面向对象编程教程:42 Python常用复合数据类型-collections容器数据类型

全网最适合入门的面向对象编程教程&#xff1a;42 Python 常用复合数据类型-collections 容器数据类型 摘要&#xff1a; 在 Python 中&#xff0c;collections 模块提供了一组高效、功能强大的容器数据类型&#xff0c;扩展了内置的基础数据类型&#xff08;如 list、tuple、…

ubuntu14.04.5出现配环境后重启进不了图形化界面解决记录

前言 这段时间给公司接了一个需要使用到linux环境进行交叉编译的工程&#xff0c;就采用了简单易操作的图形化ubuntu系统。 镜像采用的是&#xff1a;ubuntu14.04.5-desktop-amd64.iso(官网下载的&#xff09; 配置环境的过程下载了众多依赖包&#xff0c;总之就是缺啥下载啥…

Mac 安装 jdk 8详细教程

Mac 电脑上安装Jdk 8 的步骤很简单&#xff0c;不用想Windows那样需要配置环境变量PATH、JAVA_HOME。 具体方法如下&#xff1a; 首先&#xff0c;去JDK官网下载对应版本的JDK 8。 这里需要注册一个账号&#xff0c;然后&#xff0c;账号下载。 下载完后&#xff0c;得到一个…

【IEEE独立出版 | 往届快至会后2个月检索】2024年第四届电子信息工程与计算机科学国际会议(EIECS 2024,9月27-29)

2024年第四届电子信息工程与计算机科学国际会议&#xff08;EIECS 2024&#xff09;将于2024年9月27日至29日在中国延吉举行。会议由长春理工大学主办&#xff0c;延边大学、长春理工大学电子信息工程学院、长春理工大学计算机学院、长春理工大学人工智能学院承办&#xff0c;多…

视频智能分析厨帽检测算法,厨帽检测算法全套源码、样本和模型展示

厨帽检测算法是一种基于人工智能和计算机视觉技术的系统&#xff0c;旨在自动检测厨师是否佩戴了符合规范的厨帽。该算法通过分析视频流或图像数据&#xff0c;实时识别厨帽的佩戴情况&#xff0c;从而帮助餐饮企业确保员工的着装符合卫生标准。这一技术广泛应用于餐馆、厨房、…

旋转排序:搜索算法

搜索旋转排序数组的算法设计 引言 在计算机科学的世界中&#xff0c;二分搜索算法被广泛认为是处理已排序数组查找任务的高效工具。 它通过不断将搜索范围缩小一半的方式&#xff0c;快速定位到所需元素的位置&#xff0c;这种方法的时间复杂度仅为O(log n)&#xff0c;使得…

开源轻量级进程监控工具monit的使用以及monit进程监控工具常用的监控配置案例示例大全

一、开源轻量级进程监控工具monit的应用 今天在服务器杀进程时&#xff0c;发现有一个进程一直在重启&#xff0c;寻找服务器各种定时任务未发现有定时程序&#xff0c;也没有发现supervisord的进程管理服务&#xff0c;后来才发现服务器上启用了monit这个工具&#xff0c;moni…

初识Redis:JavaSpring客户端

第一步&#xff0c;需要在添加依赖的时候&#xff0c;选中这两个依赖。 第二步&#xff0c;修改配置文件 此处可以用properties也可以用yml。 第三步&#xff0c;完善controller 之前使用jedis&#xff0c;是通过jedis对象里面的各种方法来操作redis的&#xff0c;此处Spring中…

粘包、半包和Netty中的自定义帧解码器间的关系

之前学习Netty的时候学到自定义编解码器这一部分后就没再继续学习&#xff0c;同时对于这部分知识学习不深入。一直有个误区&#xff1a;自定义编码以及解码服务器就能够解决TCP作为流式协议传输&#xff08;无消息边界&#xff09;导致的粘包、半包问题。实则上面这句话有非常…