线程概念
* 操作系统支持多进程,使多个程序能并发执行,已改善资源利用率和提高系统效率
* 作系统支持多线程进程,能够减少并发时多付出的时空开销,使得并发粒度更细
并发性更好
* 多线程进程的操作系统支持多进程的并发程序设计
* 多线程的并发程序设计:即一个程序可分成若干个并发执行的进程,一个进程可再分成若干个
并发执行的线程
java支撑多线程的并发程序设计。java语言提供线程类和接口,用于创建、管理和控制线程对象
程序 、进程、线程
程序:是指令的集合,包括对数据的描述和操作,是一段静态代码 ,是应用程序执行的蓝本
进程:
——》进程是一个具有一定独立功能的程序在一定集合上的一次动态执行过程
——》进程是存储器、外设资源的分配单位,也是处理的调度对象
——》系统可分为一个程序创建若干个进程,每个进程有独立的内存空间和资源,进程间不会共享系统资源
——》进程是由代码、数据内核状态和一组寄存器组成
线程:
计算机的科学术语
——》操作系统中能够独立执行的实体(控制流),是处理器调度和分配的基本单位
——》一个进程课被划分为多个线程
——》线程不拥有自己系统资源,共享进程的全部资源
——》线程共享相同地址空间
——》每个进程都必须至少有一个线程(主线程)(java中main,垃圾回收处理机制)
——》可由主线程开启多个自己线程,并发执行不同的任务,称为多线程
——》线程间可以彼此独立执行
java中的多线程
按照时间片轮流执行
在操作系统每次分给java程序的一个时间片的CPU时间内,在若干个独立的可控制的线程间切换
每个java程序有一个默认的主线程
JVM加载代码,发现main方法后,就会启动一个线程,称为主线程
若在main方法中创建了其他线程,JVM就在主线程和其他线程间轮流切换,JVM等到所有线程结束后才能结束应用程序
程序,进程与线程的区别
程序是静态的,进程,线程是动态的
一个程序可以对应多个进程,一个进程可以有多个线程
程序不负责申请系统资源,进程申请系统资源,线程共享进程的全部资源
并发多线程程序设计:在一个进程中包含多个并发执行的控制流
线程的创建和启动
线程的创建通过以下两种方式:
1.实现Runnable接口
2.继承Thread类
Runnable接口:该接口只包含了一个run()方法
实现多线程,定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程
run()是线程的执行方法,一个线程对象必须实现run()方法以描述该线程的所有活动以及执行的操作,已实现
的run()方法称为该线程对象的线程体
Thread的常用方法
public Thread() 构造方法
public Thread (String name) name指定线程名
public Thread (Runnable target) target指定线程的目标对象
public Thread (Runnable target,String name) 指定线程的目标对象,指定线程
创建线程
public class A implements Runnable(){
public void run(){
System.out.println("我是一个线程");
}
}
public class testThread(){
public static void main(String[ ] args){
A a = new A();
Thread thread = new Thread(a);
thread.start();
}
}
控制台输出 : 我是一个线程
Thread类和Runnable接口的异同
相同点: 都是“多线程的实现方式 ”,线程的运行都是执行run()方法,线程的启动都是 start()方法
不同:
Thread是类,而Runnable是接口;Thread本身实现了Runnable接口的类
Java只支持单继承,而支持接口的实现,因此Runnable接口具有更好的扩展性
Runnabe多线程都是基于Runnable对象建立的,所以可以共享Runnable对象上的资源
推荐使用Runnable接口实现多线程
Callable
java5开始,Java提供了Callable接口,这个Callable接口像是Runnable接口的增强版本 ,Callable接口提供了一个 call()方法作为线程的执行主体,call()比run()更加强大
Callable与Runnable的区别
Callable规顶的方法是call(),Runnable规定的方法是run()
Callable的任务执行后可返回值,Runnable的任务是不能返回值得
call() 可以抛出异常,run()不行
运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果使用Callable接口和Future创建线程
创建callable接口实现类,并实现call()方法,该call()方法将做线程的执行体,且该call()方法有返回值。
创建Callable实现类的实列,使用FutureTask类来包装Callable对象,该FutureTask对象封装该Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象参数创建并启动线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
实例代码如下
class Mycallable implements Callable<String>{
private String name;
public Mycallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
if(name.equals(null)) {
throw new RuntimeErrorException(null, "name不能为空");
}
return name;
}
}
public class TestCallable {
public static void main(String[] args) {
Mycallable mc = new Mycallable("song");
FutureTask<String> ft = new FutureTask<>(mc);
new Thread(ft).start();
try {
String name = ft.get();
System.out.println(name);
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
控制台输出 : song
守护线程:
Java中有两类线程,User Thread(用户线程,即前面讲到的线程),Daemon Thread(守护线程)
任何一个守护线程都是JVM机中非守护线程的保姆,也就是说,守护线程时为非守护线程服务
只要当前JVM机中只要有任何一个非守护线程有结束,守护线程就全部工作,只有所有非守护线
程全部结束,守护线程随着JVM一起结束(比如垃圾回收线程)
守护线程和用户线程几乎没有区别,唯一的区别就是 守护线程随着JVM的结束结束
守护线程不是只有虚拟机提供,也可以在程序中设置守护线程
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
注意:!!!!
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑
小思考 ,守护线程不能用来进行读写操作
虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)
创建一个守护线程
实例代码如下
class MyDaemon implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是守护线程。。。。。。。");
}}
public class TestDaemon {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
Thread t = new Thread(new MyDaemon());
t.setDaemon(true);
t.start();
}
}
控制台输出 : 123456789
可以看出,守护进程的System.out.println,并没有执行,
在普通线程运行结束后,守护线程随着JVM退出而退出
再次提醒不要在守护线程中进行逻辑操作
线程的生命周期
一个线程从创建到死亡的过程
新建状态(new)
线程对象已实例化,但尚未运行。
就绪状态(runnable)
一旦线程实例调用start()方法,线程就进行此状态,进入就绪线程队列,排队等待CPU时间片,此时已具备运行条件(但并没有真正运行)。
另外,原来处于阻塞状态的线程,解除阻塞后也是进入就绪状态。
运行状态(running)
当就绪状态的线程被轮到使用CPU时间片时,便进入运行状态。
开始/继续运行线程实例的run()方法。
阻塞状态(blocked)
一个正在执行的线程在某些情况下,如被人为挂起或需要执行费时的输入输出操作时,就让出CPU并暂时中止自己的执行,进入阻塞状态。当阻塞原因消除时,转入就绪状态。
消亡状态(Dead)
线程执行结束,不具备继续运行的能力。
原因有两个:正常完成全部工作自然消亡;被其他线程提前强行终止。
线程的调度
线程有两种调度模型
——》分时调度模式 所有线程轮流使用CPU的使用权,平均分配没个线程占用CPU的时间片
——》抢占式调度模式 优先让优先级高的线程使用CPU,如果优先级相同会随机选择一个
优先级高的线程获取的cpu时间片相对多点
java使用以下方法进行调度
setPriority(int)
getPriority()
sleep(long)
interrupt()
isAlive()
join()
yield()
Java采用了改良的基于时间片的调度算法,加上线程优先级的概念。每个线程在创建时都被分配一个优先级,处于就绪状态的线程,优先级高的线程能够优先获得时间片执行。
Java调度算法 = 基于时间片 + 固定优先级
线程的优先级用数字表示,范围从1-10,默认优先级都是5
Thread.NORM_PRIORITY = 5 (默认值)
Thread.MAX_PRIORITY = 10
Thread.MIN_PRIORITY = 1
使用以下方法get和set线程的优先级
int getPriority();
void setPriority(int new Priority)
线程优先级 代码示例
package com.gem.day16.test;
public class TestFirst {
public static void main(String[] args) {
Mythead mt1 = new Mythead();
mt1.setName("线程一");
Mythead mt2 = new Mythead();
mt2.setName("线程二");
Mythead mt3 = new Mythead();
mt3.setName("线程三");
//获取三个线程默认优先级
System.out.println("线程1的优先级"+mt1.getPriority());
System.out.println("线程2的优先级"+mt2.getPriority());
System.out.println("线程3的优先级"+mt3.getPriority());
// 设置正确的线程优先级
mt1.setPriority(10);
mt2.setPriority(1);
mt1.start();
mt2.start();
mt3.start();
}
}
class Mythead extends Thread{
@Override
public void run() {
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread()
.getName()+" "+i);
}
}
}
控制台打印会发现 默认优先级都为5
优先级高的会优先执行
线程的休眠sleep()
当前线程休眠指定毫秒时间,静态方法,可以直接调用。
当前线程指的是: 当前正在执行这行代码的那个线程.
代码示例如下
package com.gem.day16.test;
import java.util.Date;
public class TestSleep {
public static void main(String[] args) {
sSleep ts = new sSleep();
Thread thread = new Thread(ts);
thread.start();
}
}
class sSleep extends Thread{
@Override
public void run() {
System.out.println("我休息1000毫秒"+new Date().getTime());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("过了一秒了"+new Date().getTime());
}
}
控制台打印如下
我休息1000毫秒1522069547325
过了一秒了1522069548326
使用sleep()方法也可以实现类似定时器功能
while(true){
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程打断 interrupt()
N个线程并发的情况下, 执行到 sleep() 方法, 都会睡眠指定的时间
InterruptedException:
正在处于阻塞状态的线程, 被其他线程调用了 interrupt() 方法,就会报这个异常.
并且这个被 Interrupted 的线程, 会变为 就绪状态
在程序运行中,线程休眠时可以调用Interrupt()打断线程休眠,继续运行
实例代码如下
package com.gem.day16.test;
public class TestInterruptThread {
public static void main(String[] args) throws InterruptedException {
myThread mt = new myThread();
Thread t = new Thread(mt);
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
class myThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("线程开始执行。。。。");
System.out.println("线程休眠20s");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("线程被打断。。。");
}
System.out.println("继续执行。。。。");
}
}
控制台打印
线程开始执行。。。。
线程休眠20s
线程被打断。。。
继续执行。。。。
Join()
让主线程等待子线程结束后才能运行
实例代码如下
package com.gem.day16.test;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
join j = new join();
Thread t = new Thread(j);
t.start();
t.join();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println(i);
}
}
}
class join extends Thread{
@Override
public void run() {
System.out.printf("%s start\n",
this.getName());
// 延时操作
for(int i=0; i <100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.printf("%s finish\n", this.getName());
}
}
}
控制台输出
在没有使用join()时,子线程与主线程轮流使用时间片
在使用join()方法时,在子线程执行完成后,主线程在开始执行
yield():
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态
里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了
代码示例如下
package com.gem.day16.test;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
join j = new join();
Thread t = new Thread(j);
t.start();
//t.join();
Thread.yield();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println(i);
}
}
}
class join extends Thread{
@Override
public void run() {
System.out.printf("%s start\n",
this.getName());
// 延时操作
for(int i=0; i <100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.printf("%s finish\n", this.getName());
}
}
}
在没有使用yield()时 子线程,主线程轮流使用时间片
在使用yield时 不是轮流使用时间片,下一个时间片的执行几率从原来的100%变为了50%