[TOC]
多线程
进程和线程
进程: 正在执行的程序
线程: 一条独立的执行路径
一个进程可以只有一条线程,也可以有多条线程
为什么要开启多线程?
1.执行某些耗时任务
2.希望某些程序看起来像同时执行
3.希望完成某个特定的子任务
4.防止线程阻塞
开启多线程是提高了效率吗?
不是,反而降低执行效率,但是提高了cpu的使用率
合理利用CPU的使用率
Java中开启了几个线程?
至少有两个
1.主线程
2.垃圾回收线程
多线程: 具有完成特定功能的执行路径,是CPU最先执行单位
CPU在某个时间刻度上只能够执行一条线程的一条原子性语句
int a = 1; 原子性
a ++; 不是
a.将a的值读取出来
b.将a的值+1
c.重新将新值赋值给a
System.out.println(a);
CPU的执行原理
1.真实环境下,CPU能够同时执行多个程序,本质只是在同一个时间刻度上执行一条线程的一条原子性语句,
只不过CPU切换执行速度非常快,我们无法察觉以为是同时执行
2.并发和并行
并发: 在同一个时间段同时执行
并行: 在同一个时间刻度上同时执行
3.同步和异步
同步: 并发情况下会出现同步问题
异步: 能够同一个时间段能够处理多个任务,例如ajax请求
多线程和多进程的好处
1.多线程提高了进程的使用率,从而提高CPU的使用率
2.多进程提高了CPU的使用率
方式一:继承Thread类
1.自定义类MyThread继承Thread类。
2.MyThread类里面重写run()方法。
3.创建线程对象。
4.启动线程。
注意:
1、启动线程使用的是start()方法而不是run()方法
2、线程能不能多次启动
方式二:实现Runnable接口
1.自定义类MyRunnable实现Runnable接口
2.重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
5.启动线程
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,
较好的体现了面向对象的设计思想。 后面讲解线程同步的时候讲解
方式三:实现Callable方式开启线程
实现Runnable和实现Callable接口的区别
1.有返回值
2.可以声明异常
这里的返回值和异常抛出都是给到线程的启动者
方式四:匿名内部类开启线程
方式五开启线程: Lambda表达式开启线程
Lambda表达式是JDK1.8之后引入
本质就是方便匿名内部类的书写
函数式接口
只有一个抽象方法的接口就是函数式接口
例如 Runnable
Lambda表达式的语法
主要由三部分组成:
1.形参列表: 形式参数允许省略参数类型
2.箭头 ->
3.方法体: 由大括号包裹,当方法体中只有一条语句,{}可以省略
当一个方法有返回值的时候,如果只是返回一条语句,那么return和{}都可以省略,这个表达式结果自动作为返回值的结果返回
如何设置和获取线程的名称
设置和获取线程名称
通过构造方法
Thread(String name) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
通过线程的成员方法
public final String getName()
public final void setName(String name)
通过静态方法
public static Thread currentThread()
可以获取任意方法所在的线程名称
可以获取主线程的线程名称:Thread.currentThread().getName();
long getId() 返回该线程的标识符。
线程调度
Java是如何对线程进行调度的?
Java使用的是抢占式调度模型
抢占式调度模型
优先让优先级高的线程使用 CPU,如果线程的优先级相同,
那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
设置和获取线程的优先级
public final int getPriority()
public final void setPriority(int newPriority)
线程休眠
public static void sleep(long millis)
中断线程
public final void stop()
public void interrupt()
线程加入
public final void join()
线程礼让
public static void yield()
让出CPU的执行权一小会
这里让出了CPU的执行权,并不是说把执行权交割了其他线程
而是释放了自己的执行权,自己还可以重新和其他线程抢夺执行权
后台线程
public final void setDaemon(boolean on)
一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程和后台线程。
所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,
并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束的时候,
也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。
反过来说,只要有任何非后台线程还在运行,程序就不会结束。比如执行main()的就是一个非后台线程。
基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。
线程安全
产生线程安全的因素:
1.必须存在多线程环境
2.至少有两条语句操作了共享数据
3.如果多个线程中有一个线程对共享数据进行了写操作
综合来说:
在多线程环境下,至少有两条以上的原子性语句操作了共享数据,
并且这个操作是写操作,肯定会出现线程安全问题
如何来解决线程安全问题?
1.同步代码块
2.同步方法
3.Lock锁
1.同步代码块
格式:
synchronized(锁对象){需要同步的代码;}
注意:
a.锁对象是任意对象
b.不同线程共享同一把锁
c.同步方法的锁对象是 this
d.如果方法是静态方法,锁对象是字节码文件对象
同步的好处
解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题
2.同步方法
格式:
public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}
3.Lock锁