多线程
- 并行和串行
- 并行:多个任务同时执行,效率高。
- 串行:多个任务依次执行,效率低
- 单核和多核CPU
- 单核:如:只有一个人帮干活
- 多核:有多个人帮你干活
- 线程和进程
- 进程(Process):正在运行的一个程序称为进程(word 、记事本)
- 线程(Thread):线程就是进程中的一个任务.
- 单线程和多线程
- 如果一个进程中只有一个线程,称为单线程的程序。
- 如果一个进程中包含两个或者两个以上的线程,称为多线程程序。
- 进程负责分配内存,多线程共享进程分配的内存
- 进程的任务之一是向操作系统申请内存
- 线程向进程申请内存
- 多线程共享进程分配的内存
- 线程调度器与调度算法
- 多个线程谁先执行?线程的运行过程是由线程调度器来负责的。
- 线程调度器有两个调度算法:时间片轮换调度与抢占式调度
Java对多线程的支持
- java多线程解决了程序并发执行
创建线程的方式
- 串行
public class DemoChuanXing {
public static void main(String[] args) {
/*
* 不使用线程,方法间的调用是串行的
* 串行执行时,不能实现多任务并行
*
* 串行关键点:
* 当a运行时,main等待a运行结束后才能运行main
* 当b运行时,a等待b运行结束后才能运行a
*/
a();
System.out.println("main");
}
private static void a() {
b();
System.out.println("a");
}
private static void b() {
System.out.println("b");
}
}
- 继承Thread类创建线程
public class MyThread extends Thread{
/*
* 线程并行
* 创建线程的方式1、创建类继承Thread类创建线程
* (1)创建一个子类继承Thread类,
* (2)重写run方法
* (3)调用start方法启动线程,启动后会调用run()方法
*/
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
public class Test {
public static void main(String[] args) {
/*
* main方法执行时,jvm会创建一个主线程
*
*/
MyThread myThread=new MyThread();
//启动线程,线程会自动调用run方法
myThread.start();
}
}
- 关于主线程
- main方法启动时会自动创建一个线程,称为主线程。
- 子线程需要在主线程中创建。
- 关于线程的名称
- Thread.currentThread()获取当前线程
- Thread.currentThread().getName()获取当前线程的名称
- 主线程名称默认是main
- 子线程名称默认是Thread-00 | 01 | 02
- 修改线程的名称,调用setName方法
- Thread.currentThread().setName(“主线程”);
- t.setName(“子线程”);
- 实现Runnable接口创建子线程
public class MyThread implements Runnable{
/*
* 创建线程2、实现Runnable接口创建线程
* 方法(1)定义类,实现Runnable接口
* (2)重写run方法
* (3)将Runnable接口包装到Thread类中
* (4)调用start启动线程
* Runnable是函数式接口
*
*/
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
public class Test {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
MyThread myThread=new MyThread();
//将Runnable接口包装到Thread类中
Thread t=new Thread(myThread);
t.setName("子线程");
t.start();
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
Runnable接口是函数式接口,因此可以使用lambda表达式实现线程。
public class Test1 {
public static void main(String[] args) {
/*
* Runnable是函数式接口,使用lambda表达式实现线程
*
*/
Runnable runnable=()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
};
new Thread(runnable).start();
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
实现Callable接口
- 与Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装。
ublic class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 45;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable myCallable=new MyCallable();
FutureTask<Integer> f=new FutureTask<>(myCallable);
Thread thread=new Thread(f);
thread.start();
System.out.println(f.get());
}
}
继承类与实现接口的方式有什么区别
- java不支持多重继承,因此继承了Thread类就无法继承其他类,但是可以实现多个接口
- 类可能只要求可执行就行,继承整个Thread类开销过大。
主线程与子线程
- main方法启动时,JVM自动创建了主线程
- main方法中new的线程是子线程
线程的基础机制
- 让步yield
- 一个运行中的线程想要放弃执行的权利
- 此时线程调度器会选择另一个可运行的线程来执行,不过,调度器也可以忽略这个通知
- yield只是放弃了本时间片的执行,放弃后又回到就绪队列重新排队。
public class ThreadYield {
public static void main(String[] args) {
/*
* 线程让步yield
* 如果一个运行中的线程想要放弃执行的权利,那么可以调用Thread类
* 中的静态方法yield来通知线程调度器
*
* 本线程要让出对CPU的占用,此时线程调度器会选择另一个可运行的线程来执行,
* 不过,调度器也可以忽略这个通知
* 注意:yield方法只是临时放弃当前线程的执行,并不代表该线程就不执行了,此时线程将转为就绪状态。
*/
Runnable run=()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
Thread.yield();
}
};
new Thread(run).start();
new Thread(run).start();
}
}
- 休眠sleep
- 当线程休眠时,它放弃了执行的权利,加入到等待对列,什么都不做,然后等到休眠时间到了再醒过来,加入可运行队列。
public class Sleep {
public static void main(String[] args) {
/*
* 线程休眠
* 与yield方法临时放弃线程执行权利的方式不同,sleep是真正的睡眠。
*
* 正常情况下,需要等线程睡眠了指定时间后,才会重新转为可运行状态,被线程调度器所调度。
*/
Runnable run=()->{
while(true) {
System.out.println(new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t=new Thread(run);
t.start();
}
}
解析:
- sleep时,线程休眠了,在休眠期间不会进入到就绪队列
- 当休眠时间结束后,进入就绪队列排队
- sleep方法会抛出编译时异常InterruptedException(中断)
- 优先级priority
Thread类中定义了三个优先级常量
- MAX_PRIORITY=10
- NORM_PRIORITY=5
- MIN_PRIORITY=1
线程优先级
- 决定了那个线程更容易获取CPU时间片
- 虽然线程调度器选择哪一个线程运行是由很多因素决定的,但是调度器确实会让优先级高的线程获得CPU
- 对于两个优先级不同的线程来说,优先级高的线程会得到更多的执行时间。
- 但是优先级高的线程不一定先执行
public class Priority {
public static void main(String[] args) {
Runnable run=()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
};
Thread t1=new Thread(run);
Thread t2=new Thread(run);
//设置优先级别
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(1);
t1.start();
t2.start();
}
}
- 后台线程(background:后台 daemon:守护)
进程退出是如何界定的?
- 当一个进程中的所有线程都结束了,这个进程就退出了.当一个进程中只要有一个线程还在运行,那么这个进程就不能退出
为什么要后台线程呢?
- 后台线程是否在运行,不影响进程的结束。所有的非后台进程都结束了,进程就退出了。
后台线程使用场景
- 监视程序
- JVM的垃圾回收线程
public static void main(String[] args) {
Runnable run=()->{
while(true) {
System.out.println(new Date());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t=new Thread(run);
//将t设置为守护线程,t是否在运行不影响程序的退出。
t.setDaemon(true);
t.start();
}
- 线程间的协作
线程联合join
- join 方法是让一个线程等待另一个线程执行完毕后再继续执行执行
class A extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
class B extends Thread{
private A a;
public B(A a) {
this.a = a;
}
@Override
public void run() {
for(int i=0;i<100;i++) {
try {
/*
* B调用了A的join方法,当a运行结束后,b开始运行
*
*/
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
public class Test {
public static void main(String[] args) {
A a=new A();
B b=new B(a);
a.setName("牛牛牛牛牛");
b.setName("*********");
a.start();
b.start();
}
}