Java中的多线程(创建方式、安全问题、同步、死锁)

2023-11-05

/*学习笔记 */

——多线程

简述

进程:正在进行中的程序(直译).

线程:就是进程中一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程。

一个进程中至少要有一个线程。

开启多个线程是为了同时运行多部分代码。

每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。

多线程好处:解决了多部分同时运行的问题。

多线程的弊端:线程太多回到效率的降低。

其实应用程序的执行都是cpu在做着快速的切换完成的。这个切换是随机的。

JVM启动时就启动了多个线程,至少有两个线程可以分析出来。

1,执行main函数的线程,
该线程的任务代码都定义在main函数中。

2,负责垃圾回收的线程。

如何创建一个线程呢?

创建线程的方式

方式一:继承Thread类。

步骤:
1,定义一个类继承Thread类。
2,覆盖Thread类中的run方法。
3,直接创建Thread的子类对象创建线程。
4,调用start方法开启线程并调用线程的任务run方法执行。

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。

而运行的指定代码就是这个执行路径的任务。

jvm创建的主线程的任务都定义在了主函数中。

而自定义的线程它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的。所以Thread类也有对任务的描述。
这个任务就通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。

run方法中定义就是线程要运行的任务代码。

开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法。
将运行的代码定义在run方法中即可。

注意,用strat方法来开启线程并调用run方法。
以下是一个创建并开启线程的例子:

    class Demo extends Thread
    {
    	private String name;
    	Demo(String name)
    	{
    		
    		this.name = name;
    	}
    	public void run()
    	{
    		for(int x=0; x<10; x++)
    		{
    			for(int y=-9999999; y<999999999; y++){}   //这是用来延时的
    			System.out.println(name+"....x="+x);
    		}
    	}
    }
    
    class ThreadDemo2 
    {
    	public static void main(String[] args) 
    	{
    		Demo d1 = new Demo("旺财");
    		Demo d2 = new Demo("xiaoqiang");
    		d1.start();//开启线程,调用run方法。
    		
    		d2.start();
    		
    	}
    }

从图中可以看出,两个线程在交叉进行

可以通过Thread的getName获取线程的名称 Thread-编号(从0开始)

把输出语句换成如下的语句(加上了getName 方法),并运行之:

     System.out.println(name+"....x="+x+".....name="+getName());

获得了线程的名字

还可以使用Thread.currentThread().getName()方法,获得当前线程的名称:

    //部分代码
        class Demo extends Thread
        {
        	private String name;
        	Demo(String name)
        	{
        		super(name); //直接调用父类中的构造函数
        	
        	}
        	public void run()
        	{
        		for(int x=0; x<10; x++)
        		{
        			System.out.println(name+"....x="+x+".....name="+Thread.currentThread().getName());
        		}
        	}
        }

运行结果
多线程运行图解(以上面的程序为例):
在这里插入图片描述
如图,三个线程是同时进行的。
在以上代码的基础上,在main方法中写入一条异常代码,并运行之:

    System.out.println(4/0);

在这里插入图片描述
可以看到,虽然main方法出现并报告了异常,但是其它两个线程依然在运行。说明单个线程的结束并不会影响其它线程的进行。

方式二:实现Runnable接口。

这种方式的步骤如下:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3,通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。

4,调用线程对象的start方法开启线程。
示例代码如下:

    class Demo implements Runnable //准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。
    					//通过接口的形式完成。
    {
    	public void run()  //覆盖run方法
    	{
    		show();
    	}
    	public void show()
    	{
    		for(int x=0; x<20; x++)
    		{
    			System.out.println(Thread.currentThread().getName()+"....."+x);
    		}
    	}
    }
    
    
    class  ThreadDemo
    {
    	public static void main(String[] args) 
    	{	
    		Demo d = new Demo();
    		Thread t1 = new Thread(d);//此处传入的参数是一个Runnable对象
    		Thread t2 = new Thread(d);
    		t1.start();
    		t2.start();
    	}
    }

运行结果如下:
在这里插入图片描述

实现Runnable接口的好处:
1,将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务的封装成对象。
2,避免了java单继承的局限性。(继承了某个父类后就无法继承Thread类了)

所以,创建线程的第二种方式较为常用。

线程的状态

以下为线程状态的图解。
在这里插入图片描述
同一时间内,CPU只能赋予唯一一个线程以执行权。而其它线程将会进入临时阻塞状态。不同的资料中还会对线程的状态进行细分,而最重要的就是红框中的三种状态,它们是可以互相转化的。

多线程应用示例

——几个人同时卖同一种票,票的总量是固定的:

   class Ticket implements Runnable  //不使用继承的做法,因为只需要一个对象、多个线程
    {
    	private  int num = 100;
    
   
    	public void run()
    	{
    		while(true)
    		{
    		
    				if(num>0)
    				{    					
    					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
    				}    			
    		}
    	}
    }
    
    
    class  TicketDemo
    {
    	public static void main(String[] args) 
    	{
    
    		Ticket t = new Ticket();//创建一个线程任务对象。
    
    		Thread t1 = new Thread(t);
    		Thread t2 = new Thread(t);
    		Thread t3 = new Thread(t);
    		Thread t4 = new Thread(t);
    
    		t1.start();
    		t2.start();
    		t3.start();
    		t4.start();
    		
    	}
    }

运行结果:
在这里插入图片描述

多线程安全问题

多线程安全问题产生的原因:

1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
就会导致线程安全问题的产生。

解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中,用同步代码块就可以解决这个问题。

同步

Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。

同步的好处:解决了线程的安全问题。

同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。

同步的前提:同步中必须有多个线程并使用同一个

两种使用同步的方法:
1,同步代码块

    synchronized(对象) //括号内的就是同步锁
    {
    	需要被同步的代码 ;
    }

2,同步函数:在函数加synchronized关键字。如:

  public synchronized void show() 

同步应用示例

依然使用的卖票的例子。但是加入了同步代码块和同步函数。

     class Ticket implements Runnable
        {
        	private  int num = 100;
        	boolean flag = true;
        	public void run()
        	{
    
    		if(flag)
    			while(true)
    			{
    				synchronized(this)  //同步代码块
    				{
    					if(num>0)
    					{
    						try{Thread.sleep(10);}catch (InterruptedException e){}						
    						System.out.println(Thread.currentThread().getName()+".....obj...."+num--);
    					}
    				}
    			}
    		else
    			while(true)
    				this.show();
    	}
    
    	public synchronized void show() //同步函数
    	{
    		if(num>0)
    		{
    			try{Thread.sleep(10);}catch (InterruptedException e){}
    			
    			System.out.println(Thread.currentThread().getName()+".....function...."+num--);
    		}
    	}
    }
    
    class SynFunctionLockDemo 
    {
    	public static void main(String[] args) 
    	{
    		Ticket t = new Ticket();
    		Thread t1 = new Thread(t);
    		Thread t2 = new Thread(t);
    
    		t1.start();
    		try{Thread.sleep(10);}catch(InterruptedException e){} //给第二个线程运行的机会
    		t.flag = false;
    		t2.start();
    	}
    }

在这里插入图片描述
同步函数的使用的锁是this;

同步函数和同步代码块的区别:
同步函数的锁是固定的this。

同步代码块的锁是任意的对象。

建议使用同步代码块。

静态的同步函数使用的锁是——该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。

死锁示例

死锁:常见情景之一就是同步的嵌套。

    class Test implements Runnable
    {
    	private boolean flag;
    	Test(boolean flag)
    	{
    		this.flag = flag;
    	}
    
    	public void run()
    	{
    		
    		if(flag)
    		{
    			while(true)
    				synchronized(MyLock.locka)
    				{
    					System.out.println(Thread.currentThread().getName()+"..if   locka....");
    					synchronized(MyLock.lockb)				{
    						
    						System.out.println(Thread.currentThread().getName()+"..if   lockb....");
    					}
    				}
    		}
    		else
    		{
    			while(true)			
    				synchronized(MyLock.lockb)
    				{
    					System.out.println(Thread.currentThread().getName()+"..else  lockb....");
    					synchronized(MyLock.locka)
    					{
    						System.out.println(Thread.currentThread().getName()+"..else   locka....");
    					}
    				}
    		}
    
    	}
    
    }
    
    class MyLock
    {
    	public static final Object locka = new Object();
    	public static final Object lockb = new Object(); //两个锁
    }

    class DeadLockTest 
    {
    	public static void main(String[] args) 
    	{
    		Test a = new Test(true);
    		Test b = new Test(false);
    
    		Thread t1 = new Thread(a);
    		Thread t2 = new Thread(b);
    		t1.start();
    		t2.start();
    	}
    }

运行结果如下,由于两个进程都拿到了同步锁并且不释放,造成循环无法进行下去的现象。
在这里插入图片描述

多线程下的单例模式

    //饿汉式
    class Single
    {
    	private static final Single s = new Single();
    	private Single(){}
    	public static Single getInstance()
    	{
    		return s; //在多线程状态下不能保证只有一个对象,出现问题
    	}
    }
    
    
    
    //懒汉式
    
    加入同步为了解决多线程安全问题。
    
    加入双重判断是为了解决效率问题。
    
    
    
    
    class Single
    {
    	private static Single s = null;
    
    	private Single(){}
    
    	public static Single getInstance()
    	{
    		if(s==null)
    		{
    			synchronized(Single.class)		
    			{
    				if(s==null)
    					s = new Single();
    			}
    		}
    		return s;
    	}
    }
    
    class  SingleDemo
    {
    	public static void main(String[] args) 
    	{
    		System.out.println("Hello World!");
    	}
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中的多线程(创建方式、安全问题、同步、死锁) 的相关文章

随机推荐

  • Java学习笔记-多线程

    4 5 多线程 线程与进程 进程 是指一个内存中运行的应用程序 每个进程都有一个独立的内存空间 线程 是进程中的一个执行路径 共享一个内存空间 线程之间可以自由切换 并发执行 一个进程最少有一个线程 线程实际上是进程基础之上的进一步划分 一
  • 【算法】回溯

    子集树 include
  • C++ 指向类成员函数的指针

    记录两种可以间接调用类成员函数的方法 一种是直接使用函数指针 另一种使用C 11的function模板配合bind函数实现对固定类对象的成员函数调用 定义类 新建类的对象 class Cfun public void fun int x c
  • 【Matlab】 读取文件各种方法

    本技术支持指南主要处理 ASCII binary and MAT files 要得到MATLAB中可用来读写各种文件格式的完全函数列表 可以键入以下命令 help iofunMATLAB中有两种文件I O程序 high level and
  • 五分钟了解机器学习十大算法

    作者 Fahim ul Haq 译者 刘志勇 策划 赵钰莹 编辑 程序员大白公众号 仅作学术交流 如有侵权 请联系删文 本文为有志于成为数据科学家或对此感兴趣的读者们介绍最流行的机器学习算法 机器学习是该行业的一个创新且重要的领域 我们为机
  • 关于安装第三方包:dpkg: 处理软件包 redis-server (--configure)时出错:----说是redis-server未配置等原因,导致无法安装成功---bug

    在使用memcached 安装时报的redis错误 tarena tarena sudo apt get install memcached sudo tarena 的密码 正在读取软件包列表 完成 正在分析软件包的依赖关系树 正在读取状态
  • 这才是真正的技术,从0到高手的进阶!

    很多人以为学会了urlib模块和xpath等几个解析库 学了Selenium就会算精通爬虫了 但到外面想靠爬虫技术接点私活 才发现寸步难行 今天就告诉你 真正的爬虫高手应该学哪些东西 就算你毫无基础 你也能知道应该怎么去学习 话不多说 我们
  • 【Excel自动化办公Part6】:插入图片、插入柱状图、插入条形图

    目录 一 插入图片 二 插入柱状图 三 插入条形图 一 插入图片 sheet add image 创建的对象 单元格 导入模块 from openpy drawing image import Image 例 将一张云中君的皮肤图片插入到表
  • 部署zabbix分布式监控

    使用zabbix部署分布式监控 一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案 zabbix能监视各种网络参数 保证服务器系统的安全运营 并提供灵活的通知机制以让系统管理员快速定位 解决存在的各种问题 zabb
  • 特征选择 (feature_selection)

    当数据预处理完成后 我们需要选择有意义的特征输入机器学习的算法和模型进行训练 通常来说 从两个方面考虑来选择特征 特征是否发散 如果一个特征不发散 例如方差接近于0 也就是说样本在这个特征上基本上没有差异 这个特征对于样本的区分并没有什么用
  • 各种开源协议

    出处 http www open open com solution view 1319816219625 现今存在的开源协议很多 而经过Open Source Initiative组织通过批准的开源协议目前有58种 http www op
  • 一阶导数/微分和二阶导数/微分算子在图像锐化处理方面的区别

    在仔细思考一阶导数 微分和二阶导数 微分算子在图像锐化处理方面的结果数值区别后 可以得到一阶导数 微分和二阶导数 微分算子对图像处理的区别 斜坡面上 灰度线性增加 因灰度持续增加因此一阶导数一直不为0 二阶导数只有终点和起点不为0 一阶导数
  • 火到爆的扩散模型(Diffusion Model)帮你具象化幻想世界

    Diffusion Model 如果你对人工智能有所了解 想必已经听说过Diffusion Model了 如果还没有 那就一起来了解一下吧 扩散 Diffusion 对于人能智能而言 是一个借用的概念 在热力学中 它指细小颗粒从高密度区域扩
  • 共享虚拟机(VM)教程

    vmware提供的共享虚拟机服务 我搜索了一下基本没有教程教你怎么用和注意事项的 所以我摸索了做了一个 仅供参考 1 VM共享虚拟机是什么 VMware共享虚拟机是基于局域网的云虚拟化操作系统 不是指虚拟机和主机共享文件网络 2 VM共享虚
  • 软件系统设计步骤与原理

    软件系统设计步骤与原理 在系统分析阶段 我们已经搞清楚了系统做什么的问题 其中最主要的是需求分析 确定用户需求 功能需求 系统性能 系统安全等方面的需求 数据流图 可以确定数据需求与转换过程 那么在系统设计阶段 就是着手实现需求的过程 即解
  • Qt 数据库 bindValue()使用方法

    使用数据库时 有很多种添加数据到数据库的方法 我这里使用的是变量传递数据 q prepare INSERT INTO CAN3 eDevID eMeaning eType eCurrentValue eDescrible ePermissi
  • idea中生成方法注释

    Description TODO Author YourName Date DATE 如果想在IDEA中生成类似上述的注释 那么可以先输入 再单击Enter键即可
  • jeeplus mysql_2.jeeplus源码-项目部署文档.docx

    项目部署文档官方网址 作者 lgf更新日期 2016 5 9开发工具 eclipse myeclipse mysql oracle tomcat6 7 8 打开eclipse右键 gt Import gt 选择如图 gt 选择工程 fini
  • c++vector查找元素所在的索引下标

    find函数 include
  • Java中的多线程(创建方式、安全问题、同步、死锁)

    学习笔记 多线程 简述 进程 正在进行中的程序 直译 线程 就是进程中一个负责程序执行的控制单元 执行路径 一个进程中可以有多个执行路径 称之为多线程 一个进程中至少要有一个线程 开启多个线程是为了同时运行多部分代码 每一个线程都有自己运行