文章目录
线程与进程
进程
进程是指,一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是在进程的基础之上的进一步划分,一个进程启动之后,里面的若干个执行路径又可以划分成若干个线程
线程的调度
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)Java使用的是抢占式调度
多线程并不能提高程序的运行速度,但是能提高程序的运行效率,让CPU的使用率更高
同步与异步,并发与并行
同步——>线程安全
排队执行,效率低但是数据安全
异步——>线程不安全
同时执行,效率高但是数据不安全
并发
两个或者多个事件在同一时间段内发生
并行
两个或者多个事件,在同一时刻发生
实现多线程
方法1——Thread
通过继承Thread,重写run方法
run方法就是线程要执行的任务方法
这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务
每个线程都拥有自己的栈空间,共用一份堆内存
方法2——Runnable
- 创建一个任务对象Runnable
- 创建一个线程,并为其分配一个任务
- 执行这个线程
相对于Thread:
实现Runnable和继承Thread相比,有如下优势
- 通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况
- 可以避免单继承带来的局限性
- 任务与线程本身是分离的,提高了程序的健壮性
- 后续学习的线程池技术,接受Runnable类型的任务,而不接收Thread类型的线程
Thread类
getName——返回线程的名称
getId——返回线程的标识符
getPriority——返回线程的优先级,setPriority——设置线程的优先级
getState——返回线程的状态
start——启动一个线程
sleep——休眠线程
setDaemon——标记是守护线程或者用户线程
用户线程可以自定义死亡时间,但是守护线程不可以
当所有用户线程全部死亡以后,守护线程才会死亡
设置和获取线程名称
方法一
通过新建任务的方式,初始化线程的名称
方法二
通过,Thread.currentThread().setName()设置线程的名称
currentThread()——当前正在执行的线程对象
线程中断
一个线程是一个独立的执行路径,他是否应该结束,应该由其自身决定
线程.interrupt——给线程添加终端标记
守护线程
用户线程:当一个进程不包含任何存活的用户线程时,进程结束
守护线程:当最后一个用户线程结束时,所有守护线程自动死亡
线程安全问题
处理线程不安全的方法
1. synchronized同步代码块(隐式锁)
!!! 看同一把锁,才能保证排队执行,才能实现数据安全
//语法
synchronized(锁对象){
}
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Runnable r = new MyThread();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
class MyThread implements Runnable {
private int count = 10;
private Object o = new Object();//创建一个锁对象,这表示同一个锁
@Override
public void run() {
while (true) {
synchronized (o) {//上锁
if (count > 0) {
System.out.println("正在准备卖票");
count--;
System.out.println(Thread.currentThread().getName() + "余票" + count);
} else {
break;
}
}
try {
Thread.sleep(1000);//结束后,休眠1秒,让其他线程能抢到时间片,防止上一个线程快速抢到时间片
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2. 同步方法(隐式锁)
同步方法的锁有两种
- this
- 如果是静态方法就是类名.class
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
Runnable r = new MyThread2();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
class MyThread2 implements Runnable {
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true) {
boolean sale = sale();
if (!sale)
break;
try {
Thread.sleep(1);//结束后,休眠1秒,让其他线程能抢到时间片,防止上一个线程快速抢到时间片
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized boolean sale() {
if (count > 0) {
System.out.println("正在准备卖票");
count--;
System.out.println(Thread.currentThread().getName() + "余票" + count);
return true;
}
return false;
}
}
3. Lock(显示锁)
Lock子类ReentrantLock
通过lock方法和unlock方法,加锁和解锁
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Runnable r = new MyThread3();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
class MyThread3 implements Runnable {
private int count = 10;
private Object o = new Object();
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (count > 0) {
System.out.println("正在准备卖票");
count--;
System.out.println(Thread.currentThread().getName() + "余票" + count);
}
l.unlock();
try {
Thread.sleep(1);//结束后,休眠1秒,让其他线程能抢到时间片,防止上一个线程快速抢到时间片
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count <= 0)
break;
}
}
}
公平锁,非公平锁
公平锁:先来先得到
非公平锁:抢时间片——Java基本上都是
线程死锁
在任何有可能产生锁的方法里,不要再调用另外一个方法,产生另外一个锁
线程的六种状态
- BLOCKED——线程的线程状态被阻塞等待监视器锁定
- NEW——尚未启动的线程的线程状态
- RUNNABLE——可运行的线程的线程状态
- TERMINATED——终止线程的线程状态
- TIMED_WAITING——具有指定等待时间的等待线程的线程状态
- WAITING——等待线程的线程状态
带返回值的线程Callable——第三种实现多线程的方式
Callable的使用步骤
- 编写类实现Callable接口,实现call方法
calss XXX implements Callsble<T>{
@Override
public <T> call() throws Exception{
return T;
}
}
- 创建FutureTask对象,并传入第一步编写的Callable对象
FutureTask<Integer> future = new FutureTask<>(callable);
- 通过Thread.start()来启动线程
new Thread(future).start();
Runnable和Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable和Callable的不同点
- Runnable没有返回值,Callable可以返回执行结果
- Callable接口的call()允许抛出异常,Runnable的run()不能抛出
线程池——ExecutorService
ExecutorService service = Executors.newCachedThreadPool();
//通过这个代码创建线程池
频繁的创建线程和关闭线程会浪费大量资源时间
使用线程池的好处
- 降低资源的消耗
- 提高响应速度
- 提高线程的可管理性
缓存线程池(长度无限制)
任务加入后的执行流程
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在,则创建线程,并放入线程池,然后使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 缓存线程池
*/
public class Demo10 {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行");
}
});
try {
Thread.sleep(100);//休眠100ms,确定休眠后执行的任务使用的是线程池中的缓存的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行");
}
});
}
}
定长线程池(长度是执行的数值)
任务加入后的执行流程
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在空闲线程,且线程池未满的情况下,创建线程,并放入线程池然后使用
- 不存在空闲线程,且线程池已满的情况下,等待线程池存在空闲线程
ExecutorService service = Executors.newCachedThreadPool(线程池长度);
单线程线程池(长度只有1)
执行流程
- 判断线程池 的那个线程,是否空闲
- 空闲则使用
- 不空闲,则等待,池中的单个线程空闲之后,使用
ExecutorService service = Executors.newCachedThreadPool(线程池长度);
或
ExecutorService service2 = Executors.newSingleThreadExecutor();
周期定长线程池
执行流程
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后使用
- 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
周期性任务执行时:
定时执行,当某个时机出发时,自动执行某任务
ScheduledExecutorService service3 = Executors.newScheduledThreadPool(2);
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo11 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行一次任务
* 参数1,定时执行的任务
* 参数2,时长数字
* 参数3,时长数字的单位TimeUtil工具类的时间常量来指定
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
},3, TimeUnit.SECONDS);//3秒后执行
/**
* 周期执行任务
* 参数1.任务
* 参数2.延迟时长数字(第一次执行在什么时间以后)
* 参数3.周期时长数字(每隔多久执行一次)
* 参数4.时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行");
}
},2,2,TimeUnit.SECONDS);
}
}
行
/**
* 周期执行任务
* 参数1.任务
* 参数2.延迟时长数字(第一次执行在什么时间以后)
* 参数3.周期时长数字(每隔多久执行一次)
* 参数4.时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(“执行”);
}
},2,2,TimeUnit.SECONDS);
}
}