多线程
第一章:进程和线程
-
进程的介绍
只有真正运行时程序才被成为进程。单核的cpu在任何时间点上只能运行一个进程,宏观上并行,微观上串行。是一个程序运行状态和资源占用(内存,cpu)的描述。进程是程序的一个动态过程,他指的是从代码加载到执行完毕的一个完整的过程。
a.独立性: 不同的进程之间是相互独立的。
b. 动态性: 进程在系统中不是静止不动的,而是在系统中一直活动的。
c. 并发性: 多个进程可以在单个处理器上同时进行,且相互不影响。
-
线程的介绍
线程是进程的一部分,一个进程可以分为多个线程,每个线程去处理一个特定的子任务。
线程的执行是抢占式的,多个线程在同一个进程中并发执行,其实就是cpu快速的在不同的线程之间的切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另一个线程可以运行。
并发执行,轮转交替。
举例:迅雷是一个进程,当中多个下载任务即是多个线程。
Java虚拟机是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立的线程,与Main并发执行。线程的组成部分:
- cpu时间片:操作系统(os)会为每个线程分配执行的时间
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象(重要)
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈。(重要)
- 线程的逻辑代码
-
进程和线程的关系和区别
a.一个程序运行后至少有一个进程
b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
c.进程间不能共享资源,但线程之间可以
d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进
程的效率高
第二章:多线程的实现
-
继承Thread类
继承Thread类,Thread类是所有线程的父类,实现了对线程的抽取和封装。 继承Thread类创建并启动多线程的步骤: 1. 定义一个类,继承Thread类,重写run方法,该方法的方法体就表示了该线程要完成的任务 2. 创建Thread子类的对象,即创建了子线程 3. 用线程对象的start方法启动该线程
代码实现:
public class TestCreateThread { public static void main(String[] args) { MyThread my = new MyThread(); my.start(); } } class MyThread extends Thread{ public void run(){ for (int i = 0; i < 50; i++) { System.out.println("MyThread:"+i); } } }
-
实现Runnable接口
实现Runnable接口创建并启动多线程的步骤: a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体 b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象 c.调用线程对象的start方法来启动该线程
代码实现:
public class TestCreateThread { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t2 = new Thread(mr); t2.start(); } } class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i <= 50; i++) { System.out.println(i); } } }
线程创建的2种方法的比较
实现Runnable接口的方式
a.线程类只是实现了Runnable接口,还可以继承其他类【一个类在实现接口的同时还可以继承另外一个类】
b.可以多个线程共享同一个target对象,所以非常适合多个线程来处理同一份资源的情况
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
继承Thread类的方式
a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还
可以使用 super关键字
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】 实际上大多数的
多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
调用start和run方法的区别
当调用start()方法时将创建新的线程,并且执行run()方法里的代码,但是如果直接调用start()方法,不会创 建新的线程也不会执行调用线程的代码
第三章:线程中常用的方法
1. 休眠:
public static void sleep(long millis)
//当前线程主动休眠millis毫秒,使得当前正在执行的线程休眠一段时间,释放时间片,导致 线程进入阻塞状态 sleep(5000),5000的单位是毫秒,设置了sleep就相当于将当前 线程挂起5s,这个操作跟线程的优先级无关, 当对应的时间到了之后,还会再继续执行
2. 放弃(让步):
public static void yield()
//当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片,可以让当前正在执行的线程暂 停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个 线程调 用了yield方法暂停之后,线程调度器又将其调度出来重新执行 实际上,当某个线程 调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪 状 态的线程才会获得执行的机会
3. 结合:
public final void join()
//允许其他线程加入到当前线程中,并优先执行
在执行原来线程的过程中,如果遇到了合并线程,则优先执行合并进来的线程,执行完合并进 来的线程后,再回到原 来的任务中,继续执行原来的线程 特点: a.线程合并,当前线程一定 会释放cpu时间片,cpu会将时间片分给要Join的线程 b.哪个线程需要合并就在当前线程中,添 加要合并的线程 c.join之前,一定要将线程处于准备状态start
4. 守护线程 setDaemon(true) 判断是否是守护线程isDaemon()
特征:如果所有的前台线程都死亡,后台线程会自动死亡,必须要在start之前执行
5. 设置线程的优先级setPriority() 默认为5
在java中线程优先级分为1~10,如果小于1或者大于10,则jdk报 illegalArgumentException()异常非法参数异常
6. 设置线程的名字:thread.setName("主线程");
第四章:线程的生命周期
对于线程,当线程被创建并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执
行状态,在线程的生命周期中,他会经历各种不同的状态【在一个进程中,多个线程同时运行,是在争抢CPU时间片】
New(新生):线程被实例化,但是还没有开始执行
Runnable(就绪):没有抢到时间片
Running(运行):抢到了时间片,CPU开始处理这个线程中的任务
Blocked(阻塞): 线程在执行过程中遇到特殊情况,使得其他线程就可以获得执行的机会,被阻塞的线程会等待合适的时机 重新进入就绪状态
Dead(死亡):线程终止
1. run方法执行完成,线程正常结束【正常的死亡】
2. 直接调用该线程的stop方法强制终止这个线程