目录
一,线程的概念
线程包含在进程中,线程是进程的一条执行路径。
在进程内部,可以同时运行多个“子任务”,也就是多个线程。在一个进程中,多个线程共享内存。线程是进程的最小单位。
二,线程的特点
线程与线程之间是相互独立的。
三,线程分类
单线程:当进程中仅包含一个执行指令的线程时,这样的进程成为单线程(也叫主线程)。
多线程:一个进程拥有多个线程,线程之间相互协作,共同执行一个应用程序。
四,多线程涉及到的概念
并发
从生活中看:
并发指在一段时间内宏观上同时去处理多个任务。
从CPU看:
在一段时间内,有多条指令在单个CPU上快速轮流交替执行,使得在宏观上具有多个进程同时执行的效果。
并发概念:
指在一段时间内宏观上同时(交替执行的)去处理多个任务。
并行
从生活中看:
指同一个时刻,多个任务确实真的同时运行。 -在同一时刻,多个人同时的做多件事情
从CPU看:
指在同一时刻,有多条指令在多个CPU上同时执行。比如:多个人同时做不同的事。
并行概念:
并行:指同一个时刻,多个任务确实真的同时运行。 -在同一时刻,多个人同时的做事情
多线程程序的优点
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
五,线程的创建
Thread对象的创建(线程的创建)
- 在Java中创建线程就是创建Thread对象,常用构造方法有:
- Thread()
- Thread(Runnable target)
第一种实现方式-继承Thread
Java通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
public class SubThread extends Thread {
String threadName;
public SubThread(String threadName){
this.threadName = threadName;
}
//2) 重写run()方法
//run()方法体就是子线程要执行的代码, 线程是程序的一个执行单元,线程需要完成某个任务, 把线程完成任务的代码放在run()方法体中
@Override
public void run() {
//打印100行字符串
for (int i = 0; i < 100; i++) {
System.out.println( this.threadName+"------> " + i );
}
}
}
public class Test01 {
public static void main(String[] args) {
//3) 创建线程对象
//Thread t = new SubThread();//Thread父类引用赋值子类对象,对象自动向上转型
SubThread t = new SubThread("线程t1");//new出来一个SubThread对象,调用SubThread()构造方法,在子类构造方法体执行前,系统会先执行父类构造方法,默认情况下调用父类的无参构造Thread()
//4)开启线程
t.start();//start()方法仅仅是告诉操作系统的线程调度器,当前t线程准备就绪, 等线程调度器选择执行t线程时, 会为t线程分配线程栈, 新分配 的线程就会自动执行run()方法
//t.run();//注意直接调用run()就是普通实例方法的调用, 不会开启新的线程
//创建第二个线程
SubThread t2 = new SubThread("线程t2");
//开启线程
t2.start();
/*
1) 运行程序, 两个线程在并发执行
2) 注意线程执行结果是随机的,每次运行结果可能都不一样
3) start()调用的先后顺序并不一定就是线程开启的先后顺序
4) 注意一个线程只能 start()一次
5) 如果同学们每次运行结果都一样,适当的把循环次数增加到10000次
*/
//t.start();//IllegalThreadStateException
}
}
第二种实现方式-实现Runnable接口
public class Prime implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println( " Prime -----------------> " + i );
}
}
}
public class Test01 {
public static void main(String[] args) {
Prime prime = new Prime();
Thread t1 = new Thread(prime);
t1.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(" annonymous ----> " + i);
}
}
}).start();
}
}
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现Runnable接口比继承Thread类所具有的优势:
1.避免了单继承的局限性
2.多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
第三种实现方式-实现Callable接口
特点:可以获得到多线程的结果/
1,创建一个MyCallable实现Callable接口。
2,重写call。(有返回值,表示多线程运行的结果)。
3,创建的对象(表示多线程要执行的任务)。
4,创建FutureTask的对象(左右管理多线程运行结果)。
5,创建Thread类的对象,并启动(表示线程)。
public class NumThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0;i <= 100;i++){
if (i % 2 ==0){
sum += i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask<>(numThread);
/*调用Thread( Runnable ) 构造方法创建Thread对象, 借助FutureTask类FutureTask类
实现了RunnableFuture接口, 而RunnableFuture接口继承了Runnable接口,
所以 FutureTask类是Runnable接口的实现类, 在调用Thread(Runnable)
构造方法时, 实参可以传递FutureTask对象调用 FutureTask(Callable)
构造方法创建对象, 形参是Callable接口,实参传递接口实现类对象. 通过
泛型指定任务返回值类型为Integer
*/
Thread t1 = new Thread(futureTask);
t1.start();
// 接收返回值
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
第四种实现方式-实现使用线程池
问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
解决思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread2 implements Callable {
@Override
public Object call() throws Exception {
int evenSum = 0;//记录偶数的和
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
evenSum += i;
}
}
return evenSum;
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(50); //设置线程池中线程数的上限
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
try {
Future future = service.submit(new NumberThread2());//适合使用于Callable
System.out.println("总和为:" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
//3.关闭连接池
service.shutdown();
}
}
六,线程常用方法
构造方法
public Thread():分配一个新的线程对象。
public Thread(String name):分配一个指定名字的新的线程对象。
public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法.
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
其他方法
public void run() :此线程要执行的任务在此处定义代码。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public String getName() :获取当前线程名称
public void setName(String name):设置该线程名称。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
线程的生命周期
在java.lang.Thread.State的枚举类中这样定义:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW新建,RUNNABLE可执行,Teminated被终止,死亡(BLOCKED锁阻塞,TIMED_WAITING计时等待,WAITING无限等待)。