目录
基本概念
线程的创建和使用
Thread类
API中创建线程的两种方式
Thread类的有关方法
线程的调度
线程的优先级
总结
基本概念
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。
单核CPU和多核CPU的理解
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
- 1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 2. 提高计算机系统CPU的利用率
- 3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时
线程的创建和使用
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。
Thread类的特性
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
Thread类
- Thread():创建新的Thread对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
- Thread(Runnable target, String name):创建新的Thread对象
API中创建线程的两种方式
JDK1.5之前创建新执行线程有两种方法:
- 继承Thread类的方式
- 实现Runnable接口的方式
方式一:继承Thread类
- 1) 定义子类继承Thread类。
- 2) 子类中重写Thread类中的run方法。
- 3) 创建Thread子类对象,即创建了线程对象。
- 4) 调用线程对象start方法:启动线程,调用run方法。
注意点:
- 1.. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- 2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 3. 想要启动多线程,必须调用start方法。
- 4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException
方式二:实现Runnable接口
- 1) 定义子类,实现Runnable接口。
- 2) 子类中重写Runnable接口中的run方法。
- 3) 通过Thread类含参构造器创建线程对象。
- 4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
区别
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Thread类的有关方法
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法
- join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 ,低优先级的线程也可以获得执行
- static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive():返回boolean,判断线程是否还活着
线程的调度
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级等级
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5
涉及的方法
- getPriority() :返回线程优先值
- setPriority(int newPriority) :改变线程的优先级
说明
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
总结
Java中的线程分为两类:一种是守护线程,一种是用户线程。
- 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
- 守护线程是用来服务用户线程的,通过在start()方法前
- 调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
- Java垃圾回收就是一个典型的守护线程。
- 若JVM中都是守护线程,当前JVM将退出。
package com.jyc.p1;
/*
多线程的创建 方式-,继承于Thread类
1.创建一个继承于 Thread 类的子类
2.重写thread类的run()方法---> 将此线程执行的操作声明在run中
3.创建thread类的子类对象
4.通过对象调用start()
Thread中的常用方法
1.start()启动当前线程,调用当前线程的run方法
2.run() 通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread()静态方法:返回执行当前代码的线程
4.getName()获取当前线程的名字
5.setName()设置当前线程的名字 (也可以使用构造器)
6..join() 在线程a中调用线程b的join()此时线程a就进入了阻塞状态,直到线程b完全执行完以后,线程a
才结束阻塞状态
7.yield() 释放当前cpu执行权
8.stop() 已过时,当执行此方法时,强制结束当前线程
9. sleep(lang millitime) 让当前线程睡眠指定的是毫秒,在指定的时间内线程是阻塞状态
10. isAlive 判断当前线程是否还存活
线程的优先级等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5---> 默认优先级
getPriority获取线程的优先级
setPriority(int p)设置线程优先级
说明:高优先级的线程抢占低优先级的线程cpu执行权,但是只是从概率上将,高优先级的线程高概率的情况下被
执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
创建多线程的方式二
1.创建一个实现了 Runnable 接口的类
2.实现类去实现 Runnable中的抽象方法
3.创建实现类的对象
4.将此对象作为参数传递到 Thread 类的构造器中 创建 Thread 类的对象
5. 通过 Thread 类的对象 调用 start方法
比较创建线程的两种方式
开发中优先选择实现Runnable接口的方式
原因:
实现的方式吗,没右类的单继承性的局限性
实现的方式更适合来处理多个线程共享数据的情况
相同点两种方式都需要重写run将线程要执行的逻辑声明在run中
*
* */
class MyThread1 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(i);
}
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
if(i%2==0){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +i);
}
System.out.println( isAlive());
System.out.println(Thread.currentThread().getPriority());
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
Thread t2=new Thread(myThread1);
t2.start();
MyThread t1=new MyThread();
/*
* 通过对象调用start()1.启动当前线程,2.调用当前线程的run方法
* 我们不能通过直接调用run的方式启动线程
* 不可以让已经start的线程去执行(需要重新创建一个线程对象)
* */
t1.setName("线程一");
t1.start();
//如下操作仍是main线程中执行的
System.out.println("hello");
for (int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName() +i);
}
if (i==20){
try{
t1.join();
}catch ( InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getPriority());
}
}}