线程(一)——线程基础

线程是学习java编程必须要学习的知识模块,其重要程度毋庸赘言。本篇的目的就是尽量全面地介绍一下程相关的基础知识,包括一些概念定义、使用方式、注意事项等。废话少说,现在开始。

 

一、为什么要使用多线程

        大部分人都知道多线程,并知道如何使用多线程,但是使用多线程的原因,一般都不会去管。为了更好地理解多线程,我们有必要去弄清楚这个问题。也就是弄清楚使用多线程的好处是什么。我认为使用多线程的好处大致有两点:

1、提高系统资源利用率

        想象一下这个场景:一个程序需要从硬盘中读取文件,然后处理文件。假设读取一个文件需要5秒,处理一个文件需要2秒。读取并处理两个文件,那么在单线程模式下,完成任务所需时间是:5+2+5+2 = 14秒;由于磁盘的IO速度比CPU和内存的IO速度慢很多,所以在磁盘读取文件的时候,CPU大部分时间是空闲的。所以在多线程的情况下,我们可以使用一个线程读取文件,当一个文件读取完后另起一个线程去处理文件,此时文件读取线程继续读物第二个文件。这样文成任务所需时间是:5+5+2 = 12秒。

          还有一个更加直观的场景是:一个单线程的程序不可能使得CPU的使用率达到100%,可能只有10%-20%,那么剩下的80%-90%的CPU资源就空闲了下来。此时如果有另外一个线程并发运行,自然就提高了CPU的占用率。

2、提高响应速度

        假设这样一个场景:有一个服务器,可以接收多个客户端的请求。如果服务器是单线程,那么只能在处理完一个客户端的请求后,才能响应下一个客户端的请求。如果客户端请求比较耗时,或者请求较多,那么后面的客户端的请求就会长时间得不到响应。如果是多线程的话,可以将客户请求分配给多个线程去执行,自然对后面客户端的请求响应更快。

 

 

二、线程与进程的定义

程序:系统要完成一个任务就是一个程序

进程:每个程序都运行在一个单独的进程中。

线程:每个进程可以有多个顺序执行流,每个执行流就可以看做是一个线程。

 

1、进程的特性

         1)独立性:每个进程都是系统中独立存在的实体,每个进程有自己独立的资源和空间地址,一个进程不能访问另外一个进程空间里面的数据。一个进程至少有一个线程。

        2)动态性:进程和程序一个是动态的一个是静态的。程序是一组静态指令的合集,而进程是系统正在运行的指令集合。

        3)并发性:多个进程可以交替执行,提高程序执行效率和系统资源利用率

2、线程的特性

        1)线程是进程中负责执行的单元,依靠程序运行。一个进程里面可以有多个线程。创建一个线程时,系统不会为这个线程重新分配内存,线程使用的是所属进程的内存资源。

        2)不同于进程的独立性,线程之间是可以共享内存中的数据的。

        3)创建一个线程的代价比创建一个进程的代价小很多,所以用多线程代替多进程实现多任务并发效率会高很多。

 

三、并发与并行

并发:在一个时间段中有多个程序都处在开始和完成的阶段之间,且这几个程序都在同一个CPU上运行,但在一个时间点上只有一个程序在运行,这就叫做并发。并发的定义是基于时间段而不是时间点的。

并行:当一个系统同有多个CPU时,系统的运行可能并不是并发,而是并行。也就是在同一个时间点上,一个 CPU执行一个线程时,另一个CPU可能执行另一个线程,两个线程时同时执行的,这就叫并行。

 

并行和并发的区别是:并发是针对时间段来说的,并行是针对时间点来说的。并发的话,一个时间段可以有多个线程运行,但是一个时间点上只能有一个线程执行;而并行的话,在一个时间点上,不同的CPU可以执行不同的线程。

这里还要说一下CPU的使用率是怎样定义的。

CPU使用率:系统会把一个CPU运行时间划分成若干个时间片,把这些时间片分配到不同的程序,让他们交替运行。假设在一个时间段中程序A占用了CPU 30ms的运行时间,程序B占用10ms,然后CPU休息60ms,那么CPU的使用率就是40%。

 

四、线程的实现

创建一个线程后,我们可以定义这个线程需要执行的任务。也就是要实现Runnable接口的run方法。线程的实现方式分为两种:

1、通过继承Thread类

        Thread类已经实现了Runnable接口,并重写了run()方法。这个实现线程的方法要求继承Thread类并重写run()方法,如下:

        public class MyThread extends Thread {

            override

            public void run() {

                Log.d("test", "run in MyThread!");

            }

        }

 

        定义好线程类后,创建一个线程类对象

        MyThread thread = new MyThread();

      

        然后调用线程对象的start()方法,才会启动线程。注意,启动线程不是调用线程的run()方法。

thread.start();

 

2、通过实现Runnable接口

       这种方式是我们创建一个子类实现Runnable接口,然后在创建Thread的对象的时候,将Runnable子类的实例作为参数传入。如下:

        public class MyRunnable implements Runnable {

            override

           public void run() {

                Log.d("test", "run in MyRunnable!");

            }

        }

 

        在创建Thread对象的时候传入MyRunnable实例:

        Thread thread = new Thread(new MyRunnable());

        thread.start();

 

3、Thread类常用方法


 

项目 B sleep(long millis):这是个静态方法,调用这个方法后,当前线程会交出CPU的执行权限,并进入blocked状态,但这个方法不会释放锁。也就是说如果当前对象持有对某个对象的锁,即使当前线程sleep了,其它线程也不能访问这个对象。通过sleep方法交出CPU执行权限后,低优先级的线程也有机会获得CPU执行权限。

 

项目 B yield():这和sleep()方法一样也是静态方法,调用后也会交出CPU执行权限,并且不会释放锁。只不过和sleep()方法不同的是,调用yield()方法后线程是进入就绪(Runable)状态,只需等待下一次分配CPU执行权限就好。并且通过yield()方法释放CPU执行权限后,低优先级的线程是没有机会获取CPU执行权限的,之后同优先级或者高优先级的线程才有机会

 

项目 B setDaemon(boolean on):用来设置线程是否为守护线程。需要注意的是,如果要将一个线程设置为守护线程,则必须在线程调用start()方法之前,而不能在线程运行的时候调用setDaemon()方法,否则会抛出异常。

        守护线程与用户线程的区别在于,守护线程是依赖于创建它的线程的,而用户线程不依赖。举个例子:如果在main线程中创建了一个守护线程和一个用户线程,当main执行完毕后,守护线程也会随之消亡,而用户线程不会,除非它的run方法执行完毕。

 

项目 B join()和join(long milli):调用join()方法后,要等到调用join()方法的线程执行完毕后,其它线程才能执行。join(long milli)的参数用于指定线程此次最长的执行时间,超过这个时间后,即使线程没有执行完,也会交出CPU执行权限。

 

其它的方法比较常用,也比较简单,这里就不再细说了。

 

 

五、线程状态

1、线程状态定义及相互装换

线程的状态有五个,分别是:New(新建状态)、Runable(就绪状态)、Running(运行状态)、Blocked(阻塞状态)和Dead(死亡状态)

新建状态(New):通过new语句创建的线程,还没有调用start()方法之前的状态

就绪状态(Runnable):New状态的线程调用start()方法后就变为Runable状态。这个状态的线程随时可能被CPU调度执行,也就是获得CPU执行权限。

运行状态(Runable):获得CPU执行权限的线程就处于运行状态。值得注意的是,线程只能从就绪状态进入到运行状态。

阻塞状态(Blocked):阻塞状态是因为线程因为某种原因,放弃CPU使用权而进入的状态。阻塞状态只有先进入到就绪状态之后,才有可能获取CPU执行权限,进入到运行状态。

阻塞的情况分为三种:

1)等待阻塞:当线程调用wait()方法时,线程进入阻塞状态,直到有对象调用notify()或者notifyAll()才能进入到就绪状态

2)同步阻塞:当线程竞争同步锁失败(因为锁被其它线程占用)时,线程进入到阻塞状态。只有当获得同步锁的线程释放了同步锁之后,其它的竞争同步锁的线程才能进入到就绪状态。

3)其它阻塞:当线程调用sleep()、join()或者发出了I/O请求时,导致线程阻塞。当sleep()状态超时、join()等待线程中止或者I/O操作执行完毕时,线程才重新进入到就绪状态。

死亡状态(Dead):当线程的执行完毕(也就是线程的run方法执行完毕)后,或者在执行run()方法的时候因异常退出,线程就进入到死亡状态。

 

我们看一下线程状态转换图:


 

       线程状态及状态之间的转化就简单介绍到这里。清楚线程的状态及其转化关系对于理解线程同步是非常有帮助的。所以应该对线程的转化关系和转化条件有比较清楚的认识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值