1. 概念
在操作系统中运行的程序是进程,一个进程可以有多个线程。线程是CPU调度和执行的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核。
- 线程就是独立执行的路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
- main()为主线程,为系统的入口,用于执行整个程序
- 在一个进程中开辟多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相连的,先后顺序不能人为干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程的三种创建方式:
继承Thread类、实现Runnable接口、实现Callable接口
2. 继承Thread类
步骤:(线程不一定立即执行,由CPU进行调度)
1. 自定义线程类继承Thread类
2. 重新run()方法,编写线程执行体
3. 创建线程对象,调用start()方法启动线程
public class TestThread extends Thread{
@Override
public void run(){
for(int i = 0; i <200; i++){
System.out.println("Thread2" + i);
}
}
public static void main(String[] args){
TestThread tt = new TestThread();
tt.start();
}
}
3. 实现Runnable接口
步骤:(推荐使用Runnable接口,因为Java是单继承)
1. 定义MyRunnable类实现Runnable接口
2. 实现run()方法,编写线程执行体
3. 创建线程对象,调用start()方法启动线程
public class TestThread implements Runnable{
@Override
public void run(){
for(int i = 0; i <200; i++){
System.out.println("Thread2" + i);
}
}
public static void main(String[] args){
TestThread tt = new TestThread();
new Thread(tt).start();
}
}
4. 实现Callable接口
- 实现Callable接口需要返回值类型
- 重写call方法,要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行: Futhure<Boolean) result1 = ser.submit(1);
- 获取结果:boolean r1 =result.get()
- 关闭服务:ser.shutdownNow()
5. 静态代理
代理的原则:
- 真实对象和代理对象要实现同一个接口
- 代理对象要代理真实角色
代理的好处: - 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
场景:电影院卖电影票,在播放电影的同时电影院播放广告赚取额外的收入
/*
* 先有一个接口
* */
public interface Movie{
void play();
}
/*
* 真正的电影,带有播放功能
* */
public class RealMovie implements Movie{
@Override
public void play(){
System.out.println("播放电影");
}
}
/*
* 代理类,电影院
* */
public class Cinema implements Movie{
//代理对象要代理真实角色
RealMovie realMovie;
//构造函数,带真实角色
public Cinema(RealMovie realMovie){
this.realMovie = realMovie;
}
@Override
public void play(){
before();
realMovie.play();
}
public void before(){
System.out.println("播放广告");
}
}
/*
* 实现
* */
public class Test{
public static void main(String[] args){
RealMovie realMovie = new RealMovie();
Movie movie = new Cinema(realMovie);
movie.play();
}
}
6. Lamda表达式
作用:避免匿名内部类定义过多;实质属于函数式编程的概念
函数式接口:
任何接口,如果只包含唯一一个抽象方法,那么它是一个函数式接口
对于函数式接口,可以通过lamda表达式来创建接口的对象。
(params) -> expression[表达式]
(params) -> statement[语句]
(params) -> {statements}
总结:
1. lamda表达式只有一行代码,可以去掉花括号,如有多行用代码块包裹
2. lamda表达式的前提是函数式接口
3. 多个参数也可以去掉参数类型,要去掉就全去掉,必须加上括号
7. 线程状态
7.1 线程的五种状态
7.2 线程方法
其他方法:
获取当前线程的名字:Thread.currentThread().getName();
7.3 线程停止
【已废弃】不推荐使用JDK提供的stop() destroy()方法
推荐线程自己停止下来
建议使用一个标志位进行终止变量,如flag = false
pubulic class TestStop implements Runnable{
private boolean flag=true;
//重写run方法
@Override
public void run(){
while(flag){
System.out.println("自定义线程---->"+i++);
}
}
//写stop方法
public void stop(){
this.flag = false;
}
public static void main(String[] args){
TestStop ts = new TestStop();
new Thread(ts).start();
for(int i=0; i<1000; i++){
Sysem.out.println("主线程----->" +i);
if(i=900){
//这样主线程会跑1000次,自定义线程跑900次
ts.stop();
}
}
}
}
7.4 线程休眠sleep
每个对象都有一个锁,sleep不会释放锁
用处:模拟网络延时、模拟倒计时
用法:Thread.sleep(); 抛出异常
7.5 线程礼让yield
比如有两个线程A和B,CPU正在执行A,在礼让后A跳出CPU,A和B同时等待CPU调度,具体调度哪个还是要看CPU。因此礼让不一定成功,看CPU心情。
7.6 线程强制执行join
本质是插队,强制执行的线程执行完了才能执行其他线程,独占CPU。
代码中少用,避免线程阻塞
7.7 观察线程状态
Thread.State state = thread.getState();
线程状态:
NEW:尚未启动的线程
RUNABLE:在Java虚拟机中执行的线程
BLOCKED:被阻塞等待监视器锁定的线程
WAITING:正在等待另一个线程执行特定动作的线程
TIMED WAITING:正在等待另一个线程执行动作达到指定等待时间的线程
TERMINATED:已退出的,同一个线程已退出,不能再次启动
8. 线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照优先级决定应该调度哪个线程来执行。注意,设置优先级高不一定真的先执行,只是代表权重比较大,具体要执行哪个,还是CPU决定。
线程优先级用数字表示:1~10
Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY=10;
Thread.NORM_PRIORITY=5;
相关方法:
getPriority() setPriority(int XXX)
main的优先级是不能设置的,默认优先级是5,优先级要先设置再启动才能生效!
9. 守护daemon线程
线程分为用户线程和守护线程
虚拟机必须保证用户线程执行完毕
虚拟机不用等待守护线程执行完毕
守护线程有如:垃圾回收、内存监控、记录操作日志等