java线程学习总结(一)

本文详细介绍了线程和进程的概念,包括它们之间的区别、线程的创建方式及其优缺点,并探讨了线程的生命周期和状态转换。

(声明:并非原创,只是一个简单总结)

一、线程和进程的概念:

           进程:进程是处于运行过程中的程序,并且具有一定的对功能,是系统进行资源分配和调度的一个独立单位。

           线程:线程是进程的组成部分,一个进程至少拥有一个线程;线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它和父进程的其线程共享该进程的全部资源。(较为官方的定义)

以下解释来源于知乎网友回答,觉得非常好,在此引用:

1、单进程单线程:一个人在一个桌子上吃菜。

2、单进程多线程:多个人在同一个桌子上吃菜。

3、多进程单线程:多个人每个人在自己的桌子上吃菜。

           a.多线程的问题就是多个人同时吃一道菜的时候发生争抢,例如两个人同时夹一道菜,一个人刚深处筷子,结果伸到的时候菜已经被夹走了。。此时就必须等一个人夹一口之后,再夹菜,也就是说资源共享就会引发冲突争抢。

          b. 对于windows用户来说,【开桌子】的开销很大,因此windows鼓励大家在一个桌子上吃菜。因此windows多线程学习的重点是面对资源争抢与同步方面的问题。

          c.对于linux系统来说,【开桌子】的开销很小,因此linux鼓励大家尽量每个人都开自己的桌子吃菜。这带来的新的问题是:坐在两张不同的桌子上说话不方便。因此,linux下学习的重点是“进程”间的通讯方法。(这里需要强调的是,linux下并没有真正意义上的线程)

二、线程创建的三种方式以及对比(疯狂java讲义,P710)

          1.继承自Thread类创建线程类

              (1).定义thread类的子类,并重写该run方法,该run方法的执行体就代表了线程要完成的任务。因此run方法成为执行体。

              (2).创建了Thread子类实例,即创建了线程对象。

              (3).调用线程对象的start()方法来启动该线程。

public class ThreadTest1 extends Thread{
	private int i;
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getName()+"----"+i);
		}
	}
	
	public static void main(String[] args){
		for (int i = 0; i < 100; i++) {
			//main()执行,会创建主线程,主线程时候main()确定的
			System.out.println(Thread.currentThread().getName()+"===="+i);//获取主线程的名字
			if(i==20){
				new ThreadTest1().start();//启动第一个线程
				new ThreadTest1().start();//启动第二个线程
			}
		}
	}
}
运行结果(部分):

main====20
main====21
main====22
Thread-0----0
Thread-1----0
main====23
Thread-1----1
Thread-0----1
Thread-0----2
         分析:主线程中存在判断,当i==20的时候,就启动第一个子线程Thread-0,但是为什么会在i==22时,才启动子线程呢?

这就要考虑到线程线程的生命周期了,线程有5种状态:新建、就绪、运行、消亡、阻塞。new ThreadTest1(),线程就被创建出来了,当调用start()方法时,线程就会处于就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是执行了t.start()此线程就会立即执行。(一步步深入,一口吃不成胖子)

        为什么Thread-0和Thread-1两个线程输出的i不连续?使用继承自Thread的方式来创建线程实例,多个线程之间是无法共享实例变量的。

        2.实现Runnable接口来创建线程类

           (1).定义Runnable接口的实现类,并重写该接口的run方法,该run()方法同样式该线程的执行体。

           (2).创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。(感觉不好理解,结合代码就so easy了)

public class ThreadTest2 implements Runnable{
	
	private int i;
	
	@Override
	public void run() {
		for (; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"----"+i);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"=="+i);
			if(i==20){
				ThreadTest2 target=new ThreadTest2();
				new Thread(target).start();//这里:将ThreadTest2的对象作为,Thread类的target
				new Thread(target).start();//即new Thread(target)
			}
		}
	}
}
结果:
Thread-0----0
Thread-1----1
main==24
Thread-1----3
Thread-0----2
Thread-1----4
main==25
Thread-1----6
Thread-0----5
           分析:结果有两个点很重要,第一点:i 输出变成连续的了;第二点:i 输出虽然连续,但是顺序是乱的,为什么?

           首先由这么个规定:采用Runnable接口的方式创建的多个线程可以共享线程类的实例属性。这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以多个线程可以共享同一个线程(实际是线程的target类)的实例属性。

          为什么i的输出顺序是乱的,线程获取资源的方式:抢占式策略。当Thread-0本该输出Thread-0---2时,恰巧被Thread-1抢到了cpu,所以Thread-1---3先打印输出。

          3.使用Callable和Future创建线程(这种创建线程的方式经常被忽略,我也搞不懂。。。)

           前面两种方式已经很全面,很实用了,但是为什么还要有第三种方式呢?书中这样说到:通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体。那么是否可以直接把任意方法包装成线程的执行呢?Java目前不行!但是java的模仿者C#可以。受此启发,从java5开始,java提供了Callable接口,该接口怎么看都像是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程的执行体,但call()方法比run()方法更强大。call()方法可以有返回值,可以声明抛出异常。
          因为Callable接口是java5新增的接口,而且不是Runnable的子接口,所以不能作为Thread的target,为此Java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口---可以作为Thread类的target.

          创建并启动有返回值的线程步骤如下:

          (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程的执行体,且该方法有返回值。

          (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

          (3)使用FutureTask对象作为Thread对象的target创建并启动线程。

          (4)调用FutureTask对象的get()方法来获得子线程结束后的返回值。

public class ThreadTest3 implements Callable<Integer>{
	
	//该方法作为线程的执行体
	@Override
	public Integer call() throws Exception {
		int i=0;
		for (; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"---"+i);
		}
		return i;
	}
	
	public static void main(String[] args) {
		//创建Callable对象
		ThreadTest3 rt = new ThreadTest3();
		//使用FutureTask来包装Callable对象
		FutureTask<Integer> task = new FutureTask<>(rt);
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"=="+i);
			if(i==20){
				new Thread(task).start();
			}
		}
		try{
			//获取线程的返回值
			System.out.println("子线程的返回值"+task.get());
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}	
}

           4. 创建线程三种方式的对比:

            采用实现Runnable、Callable接口的方式创见多线程时,优势是:

            线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

            在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

           劣势是:

           编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。


           使用继承Thread类的方式创建多线程时,优势是

           编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

           劣势是

           线程类已经继承了Thread类,所以不能再继承其他父类。



三、线程的生命周期

         线程在被创建以后,在它的整个生命周期中,存在着以下五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Boocked)和死亡(Dead)。 

        1.新建和就绪状态

            (1)当程序使用New关键字创建了一个线程之后,该线程就处于新建状态,此时的线程对象没有任何的动态特征。

            (2)当线程对象调用了strat()方法之后,该线程处于就绪状态,处于就绪状态的线程并没有真正的运行,至于什么时候开始运行,取决于JVM里线程调度器的调度。                 (3)注意事项:启动线程是调用线程的start()方法,而不是run()方法,run()方法是线程的执行体---------线程要做的事情。如果我们调用了run()方法,会出现什么结果呢?那么就是线程对象被当作了普通对象,而线程的执行体run()方法,也被当成了普通的方法。代码如下:

public class ThreadRunTest implements Runnable{
	
	private int i=0;
	
	@Override
	public void run() {
		for (; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"---"+i);
		}
	}
	
	public static void main(String[] args) {
		ThreadRunTest target = new ThreadRunTest();
			
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
			if(i==20){
				Thread thread = new Thread(target);
				new Thread(target).run();    //调用的不是start()方法了
				new Thread(target).run();
			}
		}
	}
}

            打印结果:

main===17
main===18
main===19
main===20  //这里只是开始执行普通对象的普通方法
main---0
main---1
main---2
main---3
main---4
..........
main---96
main---97
main---98
main---99   //普通对象的普通方法即将执行完毕
main===21
main===22
main===23
main===24
main===25

              结果分析:从结果可以清晰的看出,整个程序只有一个主线程main,没有子线程。线程对象是普通对象,run()方法是普通方法,run()方法一经调用立即执行,在执行完毕之前不会并发执行其它线程。补充一点:正常线程对象调用run()方法之后,若已处于运行状态,则不能再调用start()方法了,否则会引发IllegalThreadStateException异常。

           2.运行和阻塞状态

              (1)如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。

              (2)当一个线程运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是为了给其它线程获得执行的机会。这时被中断的线程就处于阻塞态

              (3)当发生如下情况时,线程会进入阻塞状态。

                     》线程调用sleep方法主动放弃所占用的处理器资源。

                     》线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。

                     》线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有。(后面再写吧。。)

                     》线程等待某个通知(notify)

                     》程序调用了线程的suspend()方法将该线程挂起。但这个方法容易死锁,所以应该尽量避免使用该方法。  

               (4)当发生如下情况时,线程会解除上面的阻塞,重新进入运行状态:

                     》调用sleep()方法的线程超过了指定时间。

                     》线程调用阻塞式IO方法已经返回。

                     》线程成功获得了试图取得的同步监视器。

                     》线程正在等待某个通知时,其他线程正好发出一个通知。

                     》处于挂起的状态的线程被调用了resume()回复方法。  

             3.线程死亡:

             线程会以如下三种方式结束:

                     》run()或call()方法执行完成,线程正常结束。

                     》线程抛出一个未捕获的异常或ERROR。

                     》直接调用该线程的stop方法来结束该线程-----该方法容易导致死锁。   

四、线程的生命周期之状态转换

               

          1.初始状态(新建)------>可运行状态(就绪)
          2.运行状态---------->终止状态(死亡)

          3.运行状态---------->可运行状态(就绪)

                yield()方法:让当前正在执行的线程暂停,但它不会阻塞该线程,只是让线程转为就绪状态。系统调度器完全可以再重新对该就绪态的线程进行调度,但是要考虑到其它处于就绪状态线程的优先级,优先级高的线程,才会获得被执行的机会,从而进入运行状态。

          4.运行状态---------->阻塞状态

               (1)join()方法:目的是让一个线程等待join进来的线程,只有当join进来的线程完全执行完毕,等待的线程才可以继续执行。下面代码:
public class JoinThread extends Thread{
	//提供一个有参数构造器,用于设置该线程的名字
	public JoinThread(String name){
		super(name);
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName()+"---"+i);
		}
	}
	
	public static void main(String[] args) throws Exception {
		
		for (int i = 0; i < 100; i++) {
			if(i==20){
				JoinThread jt = new JoinThread("join进来的线程");
				jt.start();
				jt.join();
			}
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
	}
}

main===16
main===17
main===18
main===19
join进来的线程---0
join进来的线程---1
。。。。。。。
。。。。。。。
join进来的线程---97
join进来的线程---98
join进来的线程---99
main===20
main===21
main===22
main===23
             (2)sleep()方法:让当前正在执行的线程暂停,并进入阻塞状态。其重载方法static void sleep(long millis)代表睡眠时间。
public class SleepThread extends Thread{
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10; i++) {
			System.out.println("打印中"+i);
			if(i==5){
				sleep(3000);
			}
		}
	}
}
              当i==5时,打印输出会停顿3秒,然后继续打印。
            (3)IO阻塞:简单的例子,使用Scanner对象,调用nextInt()方法等待用户输入数字,只有用户输入数字完成,程序才会继续往下走。
 
采用PyQt5框架与Python编程语言构建图书信息管理平台 本项目基于Python编程环境,结合PyQt5图形界面开发库,设计实现了套完整的图书信息管理解决方案。该系统主要面向图书馆、书店等机构的日常运营需求,通过模块化设计实现了图书信息的标准化管理流程。 系统架构采用典型的三层设计模式,包含数据存储层、业务逻辑层和用户界面层。数据持久化方案支持SQLite轻量级数据库与MySQL企业级数据库的双重配置选项,通过统的数据库操作接口实现数据存取隔离。在数据建模方面,设计了包含图书基本信息、读者档案、借阅记录等核心数据实体,各实体间通过主外键约束建立关联关系。 核心功能模块包含六大子系统: 1. 图书编目管理:支持国际标准书号、中国图书馆分类法等专业元数据的规范化著录,提供批量导入与单条录入两种数据采集方式 2. 库存动态监控:实时追踪在架数量、借出状态、预约队列等流通指标,设置库存预警阈值自动提醒补货 3. 读者服务管理:建立完整的读者信用评价体系,记录借阅历史与违规行为,实施差异化借阅权限管理 4. 流通业务处理:涵盖借书登记、归还处理、续借申请、逾期计算等标准业务流程,支持射频识别技术设备集成 5. 统计报表生成:按日/月/年周期自动生成流通统计、热门图书排行、读者活跃度等多维度分析图表 6. 系统维护配置:提供用户权限分级管理、数据备份恢复、操作日志审计等管理功能 在技术实现层面,界面设计遵循Material Design设计规范,采用QSS样式表实现视觉定制化。通过信号槽机制实现前后端数据双向绑定,运用多线程处理技术保障界面响应流畅度。数据验证机制包含前端格式校验与后端业务规则双重保障,关键操作均设有二次确认流程。 该系统适用于中小型图书管理场景,通过可扩展的插件架构支持功能模块的灵活组合。开发过程中特别注重代码的可维护性,采用面向对象编程范式实现高内聚低耦合的组件设计,为后续功能迭代奠定技术基础。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
《基于SSM架构的学籍数据管理平台技术解析》 在当代数字化教育背景下,数据管理平台已成为教育机构运营的核心支撑。本系统以SSM技术组合为基础架构,构建了套完整的学籍信息处理体系,通过系统化的技术方案实现教育数据的规范化管理与智能分析。以下从架构设计、技术实现与功能模块三个维度展开说明。 、系统架构设计 该平台采用分层式架构设计,充分体现模块化与可维护性特征。Spring框架作为核心容器,通过依赖注入机制实现组件解耦;SpringMVC架构负责前端请求的路由与响应处理;MyBatis数据层框架则封装了数据库交互过程,通过映射配置简化SQL操作。三层架构协同工作,形成高内聚低耦合的技术体系。 二、技术实现要点 1. Spring容器:基于控制反转原则管理业务对象生命周期,结合面向切面编程实现事务控制与日志管理 2. SpringMVC模块:采用模型-视图-控制器设计范式,规范Web层开发流程,支持RESTful接口设计 3. MyBatis组件:通过XML配置实现对象关系映射,提供动态SQL生成机制,显著减少冗余编码 三、核心功能模块 1. 学籍信息维护:实现学员基本资料的增删改查操作,涵盖学籍编号、个人信息、所属院系等关键字段 2. 学业成绩管理:支持课程分数录入与批量处理,提供多维度统计分析功能 3. 教学组织管理:建立班级体系与学员关联关系,实现分级数据管理 4. 权限控制机制:基于角色访问控制模型,划分管理员、教职工、学员三级操作权限 5. 系统审计功能:完整记录用户操作轨迹,构建安全追踪体系 四、系统开发方法论 在项目生命周期中,采用结构化开发流程。前期通过需求调研确定系统边界,中期完成数据库范式设计与接口规范制定,后期采用迭代开发模式配合自动化测试,确保系统交付质量。 五、技术演进展望 当前系统虽未集成智能算法,但为未来升级预留了扩展接口。可预见的技术演进方向包括:基于学习行为数据的智能预警、个性化学习路径推荐等深度应用场景。 综上所述,该平台通过SSM技术体系实现了教育管理数据的标准化处理,既展示了现代软件开发范式的实践价值,也为教育信息化建设提供了可复用的技术方案。这种系统化的问题解决思路,充分体现了软件工程方法在教育领域的应用潜力。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值