Java核心技术卷一 -第十二章:多线程

Java核心技术卷一 -第十二章:多线程系列文章目录 Java 核心技术卷一 第一章 java 白皮书 的关键术语 Java 核心技术卷一 第三章 数据类型 Java 核心技术卷一 第三章 变量与常量 Java 核心技术卷一 第三章 运算符 Java 核心技术卷一 第三章 字符串 Java 核心技术卷一 第三章 输入与输出 Java 核心技术卷一 第三章 数组 Java 核心技术卷一 第四章 类之间的关系 依赖 Java 核心技术卷一 第四章 预定义类 LocalDate 类小应用 Java 核心技术卷一 第四章 构造器 Java 核心技术卷一 第

系列文章目录

文章目录


前言

本人为java初学者,文章内容仅为个人学习总结分享,其中包含了大量Java核心技术卷一里面的文章内容以及一些网站文章内容,由于参考文章过多,在此并不一一列出,如有侵权,请联系删除。

一、多线程

1.1、什么是进程?什么是线程?

1.2、基本线程数量

1.3、进程和线程是什么关系?

举个例子:

注意:

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

1.4、思考一个问题:

1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

1.6、java语言中,实现线程有两种方式,那两种方式呢?

java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

 // 定义线程类 public class MyThread extends Thread{ 
     public void run(){ 
     } } // 创建线程对象 MyThread t = new MyThread(); // 启动线程。 t.start(); 

代码展示:

public class Test02 { 
     public static void main(String[] args) { 
     AZX azx=new AZX(); //启动线程 //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。 //启动成功的线程会自动调用run方法,并且run方在分支的栈底部(压栈)。 //run方法在分支的底部,main方法在主栈的栈底部。run和main是平级的。 azx.start(); for (int i=0;i<1000;i++){ 
     System.out.println("主线程------>"+i); } } } class AZX extends Thread{ 
     @Override public void run() { 
     for (int i=0;i<1000;i++){ 
     System.out.println("分支线程------>"+i); } } } 

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。

 // 定义一个可运行的类 public class MyRunnable implements Runnable { 
     public void run(){ 
     } } // 创建线程对象 Thread t = new Thread(new MyRunnable()); // 启动线程 t.start(); 

代码展示:

public class Test02 { 
     public static void main(String[] args) { 
     //创建一个可运行的对象 MyRunnable r=new MyRunnable(); //将可运行的对象封装成一个线程对象 Thread t=new Thread(r); //启动线程 t.start(); for (int i=0;i<100;i++){ 
     System.out.println("主线程------>"+i); } } } class MyRunnable implements Runnable{ 
     @Override public void run() { 
     for (int i=0;i<100;i++){ 
     System.out.println("分支线程------>"+i); } } } 

1.7、线程的生命周期

在这里插入图片描述

1.8、线程的方法

代码展示:

public class Test02 { 
     public static void main(String[] args) { 
     //让当前线程进行休眠,5秒后唤醒 try { 
     //注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) { 
     e.printStackTrace(); } //5秒后执行这里的代码 System.out.println("HelloWorld!!!"); //获取当前对象线程的名字 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()); //创建一个线程对象 AZX r=new AZX(); //设置线程的名字 r.setName("ttttt"); //获取线程的名字 String tName= r.getName(); System.out.println(tName); //启动线程 r.start(); for (int i=0;i<10;i++){ 
     System.out.println("主线程------>"+i); } } } class AZX extends Thread{ 
     @Override public void run() { 
     for (int i=0;i<10;i++){ 
     //currentThread就是当前线程对象。当前线程是谁呢? //当t1我程执行run方法,那么这个当前线程就是t1 //当t2线程执行run方法,那么这个当前线程就是t2 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); } } } 

代码展示(在java中怎么强行终止一个线程的执行):

直接终止:

这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。

public class Test02 { 
     public static void main(String[] args) { 
     Thread t=new Thread(new AZX()); t.setName("t"); t.start(); //模拟5秒 try { 
     //注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) { 
     e.printStackTrace(); } //5秒后强行终止t线程 t.stop();//已过时,不建议使用 } } class AZX extends Thread{ 
     @Override public void run() { 
     for (int i=0;i<10;i++){ 
     //执行十秒 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); try { 
     Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } } } } 

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

间接终止:这种方法安全
public class Test02 { 
     public static void main(String[] args) { 
     MyRunnable r=new MyRunnable(); Thread t=new Thread(r); t.setName("t"); t.start(); //模拟5秒 try { 
     //注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) { 
     e.printStackTrace(); } //5秒后强行终止t线程 r.run=false; } } class MyRunnable implements Runnable{ 
     //打一个布尔标记 boolean run=true; @Override public void run() { 
     for (int i=0;i<10;i++){ 
     if(run){ 
     //执行十秒 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); try { 
     Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } }else { 
     //终止当前线程 return; } } } } 

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

1.9、线程安全

为什么这个是重点?

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*)

什么时候数据在多线程并发的环境下会存在安全问题呢?

满足以上3个条件之后,就会存在线程安全问题。

怎么解决线程安全问题呢?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

线程排队执行。(不能并发)。

用排队执行解决线程安全问题。这种机制被称为:线程同步机制

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

说到线程同步这块,涉及到这两个专业术语:

异步编程模型:

异步就是并发。

同步编程模型:

线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低:线程排队执行

同步就是排队。

1.10、模拟俩个线程对同一账号取款

Account类:

public class Account { 
     //账户 private String actno; //余额 private double balance; //构造方法 public Account(){ 
     } public Account(String actno, double balance) { 
     this.actno = actno; this.balance = balance; } //set与get方法 public String getActno() { 
     return actno; } public void setActno(String actno) { 
     this.actno = actno; } public double getBalance() { 
     return balance; } public void setBalance(double balance) { 
     this.balance = balance; } //取款的方法 public void withdraw(double money){ 
     synchronized (this){ 
     //取款之前的余额 double before=this.getBalance(); //取款之后的余额 double after=before-money; try { 
     Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } //更新余额 this.setBalance(after); } } } 

AccountThread类:

public class AccountThread extends Thread{ 
     //俩个线程共享同一个账户对象 private Account act; //构造方法 public AccountThread(Account act) { 
     this.act = act; } public void run(){ 
     //run方法的执行表示取款操作 double money=5000; //取款 act.withdraw(money); System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额:"+act.getBalance()); } } 

Test类:

public class AccountTest01 { 
     public static void main(String[] args) { 
     //创建账户对象(只创建1个) Account act=new Account("act-001",10000); //创建俩个线程 Thread t1=new AccountThread(act); Thread t2=new AccountThread(act); //设置name t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); } } 

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

代码执行原理(锁池):

在这里插入图片描述
在这里插入图片描述

提示:

这里需要注章的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。

小总结:

在这里插入图片描述

1.11、死锁

图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。

在这里插入图片描述

代码展示:

public class DeadLock { 
     public static void main(String[] args) { 
     Object o1=new Object(); Object o2=new Object(); //t1和t2俩个线程共享o1、o2 Thread t1=new MyThread1(o1,o2); Thread t2=new MyThread22(o1,o2); t1.start(); t2.start(); } } class MyThread1 extends Thread{ 
     Object o1; Object o2; public MyThread1(Object o1, Object o2) { 
     this.o1 = o1; this.o2 = o2; } public void run(){ 
     synchronized (o1){ 
     try { 
     System.out.println("MyThead1-begin"); Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } synchronized (o2){ 
     System.out.println("MyThead1-end"); } } } } class MyThread22 extends Thread{ 
     Object o1; Object o2; public MyThread22(Object o1, Object o2) { 
     this.o1 = o1; this.o2 = o2; } public void run(){ 
     synchronized (o2){ 
     try { 
     System.out.println("MyThead22-begin"); Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } synchronized (o1){ 
     System.out.println("MyThead22-end"); } } } } 

结果:卡住了
在这里插入图片描述

我们以后开发中应该怎么解决线程安全问题?

1.12、守护线程:

图示:

在这里插入图片描述

代码展示:

package Package03; public class ThreadTest04 { 
     public static void main(String[] args) { 
     Thread t=new BakDataThread(); //启动线程之前,将线程设置为守护线程 t.setDaemon(true); //启动线程 t.start(); //主线程 for (int i=0;i<10;i++){ 
     System.out.println(Thread.currentThread().getName()+"--->"+i); try { 
     Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } } } } class BakDataThread extends Thread{ 
     public void run(){ 
     int i=0; while (true){ 
     System.out.println(Thread.currentThread().getName()+"--->"+(++i)); try { 
     Thread.sleep(1000); } catch (InterruptedException e) { 
     e.printStackTrace(); } } } } 

结果:主线程即main线程结束,守护线程也结束(其死循环结束)
在这里插入图片描述

1.13、定时器:

了解:

定时器:Timer类
在这里插入图片描述
注意schedule类:
在这里插入图片描述






定时器的作用:

间隔特定的时间,执行特定的程序。

列如:每周要进行银行账户的总账操作、每天要进行数据的备份操作。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)

在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

代码展示:

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerTest { 
     public static void main(String[] args) throws Exception { 
     //创建定时器对象 Timer timer=new Timer(); //指定定时任务 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); Date firstTime=sdf.parse("2022-02-14 11:15:00"); timer.schedule(new LogTimerTask(), firstTime, 1000*10); } } //编写一个定时任务类 class LogTimerTask extends TimerTask{ 
     public void run(){ 
     SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); String strTime=sdf.format(new Date()); System.out.println(strTime+":成功完成了一次数据备份!"); } } 

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

1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)

在这里插入图片描述

图示:

在这里插入图片描述

(生产者和消费者模式!):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述




代码展示:

import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class ThreadTest01 { 
     public static void main(String[] args) throws Exception{ 
     //创建1个仓库对象,共享的 List list=new ArrayList(); //创建俩个线程对象 //生产者线程 Thread t1=new Thread(new Producer(list)); //消费者线程 Thread t2=new Thread(new Consumer(list)); t1.setName("生产者线程"); t2.setName("消费者线程"); //启动线程 t1.start(); t2.start(); } } //生产线程 class Producer implements Runnable{ 
     //仓库 private List list; //数组大小 private int size=0; //构造方法 public Producer(List list) { 
     this.list = list; } @Override public void run() { 
     //一直生产(使用死循环来模拟一直生产) while (true){ 
     //给仓库对象List加锁 synchronized (list){ 
     if (list.size()>0){ 
     try { 
     list.wait(); } catch (InterruptedException e) { 
     e.printStackTrace(); } } //程序能够执行到这里说明仓库是空的,可以生产 Object obj=new Object(); //获取数组大小 for(int i = 0 ; i < list.size() ; i++) { 
     size=i+1; } obj=size; list.add(obj); System.out.println(Thread.currentThread().getName()+"--->"+obj); //唤醒消费者进行消费 list.notify(); } } } } //消费线程 class Consumer implements Runnable{ 
     //仓库 private List list; //数组大小 private int size=0; //构造方法 public Consumer(List list) { 
     this.list = list; } @Override public void run() { 
     //一直消费 while (true){ 
     //给仓库对象List加锁 synchronized (list){ 
     if (list.size()==0){ 
     try { 
     //仓库已经空了 //消费者线程等待 list.wait(); } catch (InterruptedException e) { 
     e.printStackTrace(); } } //程序能够执行到这里说明仓库是有数的,可以消费 //获取数组大小 for(int i = 0 ; i < list.size() ; i++) { 
     size=i; } Object obj=list.remove(size); System.out.println(Thread.currentThread().getName()+"--->"+obj); //唤醒生产者进行消费 list.notify(); } } } } 

结果:(生产和消费交替运行)
在这里插入图片描述

总结

以上就是本文的内容,记录了一些关于java“多线程”的内容,本人也是刚开始接触java,不能保证总结内容的正确性,若是有错误的话,欢迎大家指出,谢谢!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210573.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午7:21
下一篇 2026年3月19日 上午7:21


相关推荐

  • android浏览器自动全屏,Android开发实现浏览器全屏显示功能

    android浏览器自动全屏,Android开发实现浏览器全屏显示功能本文实例讲述了 android 开发实现浏览器全屏显示功能 分享给大家供大家参考 具体如下 业务需求 浏览器设置中支持全屏显示的功能 分析 只需要在设置界面上增加是否全屏的 checkbox 然后 browseractiv 中读取这个值 来设置窗口的 style 修改 1 修改项目下的 res xml 文件夹下的 browser preferences xml 文件 添加 android

    2026年3月18日
    2
  • Docker设置2375端口「建议收藏」

    Docker设置2375端口「建议收藏」Docker设置2375端口1.查看docker.service文件位置2.修改docker.service文件1.查看docker.service文件位置systemctlstatusdockerdocker.service路径为:/usr/lib/systemd/system/docker.service2.修改docker.service文件…

    2022年5月5日
    317
  • docker离线安装配置

    docker离线安装配置1、下载docker的安装文件下载地址这里下载docker-20.10.8.tgz,将docker-20.10.8.tgz文件上传到系统上:将解压出来的docker文件内容移动到/usr/bin/目录下进入/etc/systemd/system/目录,并创建docker.service文件编辑docker.service:打开docker.service文件,将以下内容复制:[Unit]Description=DockerApplicationContainerEngin

    2026年4月13日
    4
  • docker 运行tomcat_docker和tomcat区别

    docker 运行tomcat_docker和tomcat区别在学习狂神的docker内容网络学习这一步的时候,按照步骤启动tomcat镜像,但是执行ipaddr之后发现下面问题,经过分析这说明我们下载的Tomcat镜像是精简版的,利用这个镜像去打开一个容器的时候发现没有ipaddr这个命令。所以到导致我们上述报错。上图执行的命令敲错了,重新执行aptinstall-yiproute2…

    2022年7月27日
    13
  • html制作进销存,手把手教你定制属于自己的进销存软件

    html制作进销存,手把手教你定制属于自己的进销存软件接着上一步的继续来更新,上一步设置了入库单和出库单的选择录入问题下面来说一下入库单和出库单的数据保存转移问题在入库单和出库单分别插入两个按钮,然后再模块里写入一下代码Sub入库单录入()a=Sheet3.Range(“a65536”).End(xlUp).RowIfSheet3.Range(“b2”)=””ThenMsgBox”请选择录入供应商名称!”ExitSubEndI…

    2022年5月31日
    45
  • 大数据学习之Linux基础[通俗易懂]

    大数据学习之Linux基础[通俗易懂]大数据学习之Linux基础自定义Linux虚拟机安装网络配置1.node1网络配置2.通过快照克隆虚拟机3.配置其他三个节点虚拟机Linux简单命令shell命令运行原理图1.关机与重启2.判断命令的命令3.常用功能命令4.文件系统命令文件系统层次化标准(FileSystemHierarchyStandard)5.文本操作命令vi全屏文本编辑器全屏编辑器模式1.打开文件2.关闭文件3.编辑…

    2022年6月3日
    86

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号