进程与线程
进程:内存中运行的应用程序,每个进程拥有独立的运行空间
线程:进程的一个执行路径,共享一个内存空间,线程之间可以任意切换,并发执行,又叫轻量级进程
线程调度
1.分时调度
所有线程轮流使用cpu,平均分配cpu调度时间
2.抢占式调度
线程优先级高的先使用cpu,线程优先级相同时会随机选择。Java使用的是抢占式调度
在一核(1个cpu)情况下其实多线程并不能提高程序的运行速度,但可以提高程序运行效率
,让cpu使用率更高
同步、异步 、并发、并行
同步:排队执行,效率低,但安全
异步:同时执行,效率高,但数据不安全
并发:多个事件在同一个时间段内发生
并行:多个事件在同一时刻发生
线程
1.继承Thread类,重写run()方法
public class MyThread extends Thread {
public void run(){
}
}
//启动线程
Mythread th = new MyThread();
th.start();
2.实现 Runnable 接口
public class MyThread extends Thread {
public void run(){
}
}
//启动线程
//1.创建任务
Mythread th = new MyThread();
//2.创建一个线程并为其分配任务
Thread thread = new Thread(th);
//3.执行这个线程
thread.start();
实现Runnable接口实现线程的优势:
- 通过创建任务,然后给线程分配的方式,更适合多个线程执行相同的任务
- 避免单继承的局限性
- 任务与线程本身是分离的,提高程序健壮性
- 后续学习的线程池技术,接收Runnable类型的任务,不接收Thread类型的线程
线程休眠与阻塞
- 休眠:在run()方法中调用sleep(休眠毫秒数)方法
- 阻塞:休眠会导致阻塞,并且在线程执行时,消耗大量时间的操作,也会导致线
程进入阻塞状态(如等待用户输入)
用户线程和守护线程
- 用户线程:当一个进程不包含任何存活的用户线程时,进程结束。直接创建的线程
都是用户线程 - 守护线程:当最后一个用户线程结束时,所有守护线程自动结束。
- 守护线程设置:在守护线程启动前调用setDaemon(true)方法.
线程不安全的解决
1.同步代码块:
// synchronized(锁对象){//此时锁对象可以指定,但想要实现同步
//所有线程必须看同一把锁
// }
2.同步方法:
// 权限修饰符 synchronized 返回类型 方法名(){
// 类是非静态,此时锁对象是this,如果类中有多个同步方法,则它们共用this锁
// 一个方法被上锁时(正在执行),其它方法不能被执行
// 当类是静态时,锁对象为: 类名.class
//
// }
3.显式锁(Lock类)Lock 子类 ReentrantLock
/*
private Lock lok = new ReentrantLock();
public void run(){
lok.lock();
//要上锁的代码块
lok.unlock();
}
*/
公平锁和非公平锁
以上三种上锁方式都是非公平的,如果想要实现公平锁,可以在创建显式锁时传
入一个boolean参数:true,其默认为false(非公平)
线程死锁
- 如何解决:当一个对象方法会产生锁时,尽量不要去调用另一个对象会产生锁的方法
多线程通信问题(生产者和消费者问题)
线程的六种状态
1.NEW状态:被创建但未运行
2.Runnable状态:运行状态
3.Blocked状态:阻塞状态
4.Waiting状态:无限等待状态,未设置等待时间
5.timedWaiting状态:计时等待状态,设置等待时间,等待时间过后自动唤醒
6.Terminate状态:死亡状态
带返回值的线程callable
class MyCallable implements Callable<T> {
@Override
public T call() throws Exception {
return Object类型对象;
}
}
//启动线程
MyCallable my = new MyCallable();
FutureTask task = new FutureTask(my);
//task.isDone();//判断线程是否执行完,会返回一个boolean类型的值
new Thread(task).start();
线程池 ExecutorService
- 线程池就是可以容纳多个线程的容器,线程池中的线程可以反复使用省去了频繁
创建线程的操作,节省了大量时间和资源 - 线程池好处:降低资源消耗,提高响应速度,提高线程的可管理性
1.缓存线程池 (长度无限制)
任务接收后的执行流程:判断线程池是否存在空闲,存在则使用,不存在则创建线
程并放入线程池,然后使用
- 缓存线程池使用:
//ExecutorService service = Executors.newCachedThreadPool();
//service.execute(传入一个任务对象)
// 无法接收一个继承了Thread的线程对象
2.定长线程池
任务接收后的执行流程:判断线程池中是否有空闲线程,存在则使用;不存在且
线程池未满,则创建一个新线程并放入线程池并使用;不存在且线程池满,则排队
等待线程池中的线程空闲
- 定长线程池使用:
//ExecutorService service = Executors.newFixedThreadPool(
// 线程池大小);
//service.execute(传入一个任务对象)
// 无法接收一个继承了Thread的线程对象
3.单线程线程池
任务接收后的执行流程:判断线程池中是否有空闲线程,存在则使用;不存在则
等待线程池中的线程空闲
- 单线程线程池使用:
//ExecutorService service = Executors.newSingleThreadExecutors();
//service.execute(传入一个任务对象)
// 无法接收一个继承了Thread的线程对象
4.周期定长线程池
任务接收后的执行流程:判断线程池中是否有空闲线程,存在则使用;不存在则
等待线程池中的线程空闲
- 周期定长线程池使用:
class Demo{
public static void main(String[] args)
throws ExecutionException, InterruptedException {
ScheduledExecutorService service =
Executors.newScheduledThreadPool(2);//指定线程池长度
/**
* 1.定时执行一次
* 参数1:一个任务对象
* 参数2:时长数值
* 参数3:时长的单位,使用TimeUnit的常量指定
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("lcl");
}
},5,TimeUnit.SECONDS);
/**
* 2。周期性执行任务
* 参数1:一个任务对象
* 参数2:延迟时长数值(第一次执行在多长时间以后)
* 参数3:周期时长数值(第一次执行完,间隔多久执行一次)
* 参数4:时长单位,使用TimeUnit的常量指定
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("床前明月光");
}
},3,3,TimeUnit.SECONDS);
}
}
Lambda表达式(JDK8引入)
public class LambdaDemo {
public static void main(String[] args) {
print(new MyMath() {
@Override
public int sum(int x, int y) {
return x + y;
}
}, 100, 200);
/**
* Lambda表达式:函数式编程思想
* 当接口只有一个抽象方法时才能使用Lambda表达式
*/
print((int x, int y) ->
{
return x + y;
}, 100, 200);
}
public static void print(MyMath math, int x, int y) {
System.out.println(math.sum(x, y));
}
static interface MyMath {
int sum(int x, int y);
}
}
- 当接口只有一个抽象方法时才能使用Lambda表达式