多线程输出

一、认识线程
计算机的操作系统大多采用多任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序,例如,可以在使用QQ聊天的同时听音乐,即有多个独立运行的任务,每个任务对应一个进程,每个进程又可以产生多个线程。
1.进程
认识进程先从程序开始。程序是对数据描述与操作的代码的集合,如Office中的Word、暴风影音等应用程序。
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整的过程,这个过程也是进程本身从产生、发展到消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享操作系统的其他资源。
进程有如下特点:
进程是系统运行程序的基本单位。
每一个进程都有自己独立的一块内存空间,一组系统资源。
每一个进程的内部数据和状态都是完全独立的。
2.线程
线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程,而线程必须在某个进程内执行。
线程是进程内部的一个执行单元,是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。
线程和进程既有联系又有区别,具体如下:
一个进程中至少有一个线程。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。
处理机分配给线程,即真正在处理机上运行的是线程。
3.多线程的优势
多线程程序可以带来更好的用户体验,避免因程序执行过慢而导致出现计算机死机或者白屏的情况。
多线程程序可以最大限度地提高计算机系统的利用效率,如迅雷的多线程下载。
二、编写线程类
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。Java程序中的public static void main()方法是主线程的入口,运行Java程序时,会先执行这个方法。
开发中,用户编写的线程一般都是指出了主线程之外的其他线程。
使用一个线程的过程可以分为如下4个步骤。
(1)定义一个线程,同时指明这个线程所要执行的代码,即期望完成的功能。
(2)创建线程对象。
(3)启动线程。
(4)终止线程。
定义一个线程类通常有两种方法,分别是继承java.lang.Thread类和实现java.lang.Runnable接口。
1.使用Thread类创建线程
Java提供了java.lang.Thread类支持多线程编程,该类提供了大量的方法来控制和操作线程。
void run() 执行任务操作的方法
void start() 使该线程开始执行
void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
String getName() 返回该线程的名称
int getPriority() 返回线程的优先级
void setPriority(int newPriority) 更改线程的优先级
Thread.State getState() 返回该线程的状态
boolean isAlive() 测试线程是否处于活动状态
void join() 等待该线程结束
void interrupt() 中断线程
void yield() 暂停当前正在执行的线程对象,并执行其他线程
创建线程时继承Thread类并重写Thread类的run()方法。Thread类的run()方法是线程要执行操作任务的方法,所以线程要执行的操作代码都需要写在run()方法中,并通过调用start()方法来启动线程。
2.使用Runnable接口创建线程
使用继承Thread类的方式创建线程简单明了,符合大家的习惯,但它有一个缺点,如果定义的类已经继承了其他类则无法再继承Thread类。使用Runnable接口创建线程的方式可以解决上述问题。
Runnable接口中声明了一个run()方法,即public void run()。一个类可以通过实现Runnable接口并实现其run()方法完成线程的所有活动,已实现的run()方法称为该对象的线程体。任何实现Runnable接口的对象都可以作为一个线程的目标对象。
三、线程的状态
线程的生命周期可以分为4个状态,分别为新生状态、可运行状态、阻塞状态和死亡状态。一个具有生命的线程,总是处于这4种状态之一。
1.新生状态
创建线程对象之后,尚未调用其start()方法之前,这个线程就有了声明。此时线程仅仅是一个空对象,系统没有为其分配资源。此时只能启动和终止线程,任何其他操作都会引发异常。
2.可运行状态
当调用start()方法启动线程之后,系统为该线程分配除CPU外的所需资源,这个县城就有了运行的机会,线程处于可运行的状态,在这个状态当中,该线程对象可能正在运行,也可能尚未运行。对一直有一个CPU的机器而言,任何时刻只能有一个处于可运行状态的线程占用处理机,获得CPU资源,此时系统真正运行线程的run()方法。
3.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态。阻塞状态是一种“不可运行”状态,而处于这种状态的线程在得到一个特定的事件之后会转回可运行状态。
导致一个线程被阻塞有以下原因:
调用了Thread类的静态方法sleep()。
一个线程执行到一个I/O操作时,如果I/O操作尚未完成,则线程将被阻塞。
如果一个线程的执行需要得到一个对象的锁,而这个对象的锁正被别的线程占用,那么此线程会被阻塞。
4.死亡状态
一个线程的run()方法运行完毕、stop()方法被调用或者在运行过程中出现未捕获的异常时,线程进入死亡状态。
四、线程调度
当同一时刻有多个线程处于可运行状态,它们需要排队等待CPU资源,每个线程会自动获得一个线程的优先级,优先级的高低反映线程的重要或紧急程度。可运行状态的线程按优先级排队,线程调度依据建立在优先级基础上的“先到先服务”原则。
线程调度管理器负责线程排队和在线程间分配CPU,并按线程调度算法进行调度。当线程调度管理器选中某个线程时,该线程获得CPU资源进入运行状态。
线程调度是抢占式调度,即在当前线程执行过程中如果有一个更高优先级的进程进入可运行状态,则这个更高优先级的线程立即被调度执行。
线程的优先级可以通过setPriority(int grade)方法更改,此方法的参数表示要设置的优先级,它必须是一个1~10的整数。
join()方法使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程。
sleep()方法会让当前线程(停止执行)millis毫秒,线程由运行中的状态进入不可运行状态,睡眠时间过后线程会再次进入可执行状态。
yield()方法可让当前线程暂停执行,允许其他线程执行,但该线程仍处于可运行状态,并不变为阻塞状态。此时,系统选择其他相同或更高优先级线程执行,若无其他相同或更高优先级线程,则该线程继续执行。
调用了yield()方法之后,当前线程并不是转入被阻塞状态,它可以与其他等待执行的线程竞争CPU资源,如果此时它又抢占到CPU资源,就会出现连续运行几次的情况。
五、实现线程同步
当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用的方式称为线程同步。
采用同步来控制线程的执行有两种方式,即同步方法和同步代码块。这两种方式都使用synchronized关键字实现。
1.同步方法
通过在方法声明种假如synchronized修饰的方法控制对类成员变量的访问。每个类实例对应一把锁,方法一旦执行,就独占锁,直到该方法返回时才能将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对应每一个实例,其所有声明为synchronized的方法和i能有一个处于可执行状态,从而有效地避免了类成员变量的访问冲突。
同步方法的语法格式如下:
访问修饰符 synchronized 返回类型 方法名{}
或者
synchronized 访问修饰符 返回类型 方法名{}
synchroinzed时同步关键字。
访问修饰符是指public、private等。
使用synchronized修饰方法,该方法成为同步方法后,当一个线程已经在执行此方法时,这个线程就得到了当前对象的锁,该方法执行完毕以后才会释放这个锁,在它释放这个锁之前其他的线程时无法同时执行此对象的同步方法。这样就完成了这个方法的同步。
如果将一个运行时间比较长的方法声明成synchronized将会影响效率。例如,将线程中的run()方法声明为synchronized,由于在线程的整个生命周期内它一直运行,这样就有可能导致run()方法会执行很长时间,那么其他的线程就得一直等到run()方法结束了才能执行。
2.同步代码块
同步代码块的语法格式如下:
synchronized(syncObject)
{}
synchronized块中的代码必须获得对象syncObject的锁才能执行,具体实现机制与同步方法一样。由于可以针对任意代码块,且可任意指定上锁的对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值