多线程
多线程概述
是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
线程和进程
进程:(Process) :一个程序的运行,程序在内存中分配的那片空间
线程:(Thread) : 进程中的一个执行单元,执行路径
进程: 至少有一个线程
如果进程中有多个线程----> 多线程的程序
并行: 某一时间点,有多个程序同时执行, 多核cpu
并发: 某一个时间段,有多个程序同时执行,并不是真正意义的同时执行----多线程
并发(多线程)真的是同时执行吗?
不是,而是时间间隔很短,造成了同时执行的错觉
多线程的程序有什么优点:?
提高了用户的体验,提高了程序的运行效率? 提高了CPU的使用率
前面的代码都是单线程的程序,都是在main方法中依次执行
主线程: 用于执行main方法的线程
实现多线程的第一种方式
一. 继承:
1. 继承Thread
2. 重写run(线程要做的事,定义在该方法中)
3. 创建子类的对象
4. 使用该对象调用start方法
run 方法和start方法的区别:
run: 只是普通方法的调用,不会开启新的线程
start: 开启新的线程,并自动调用run方法
setName 给线程设置名称
getName 获取线程名称
Thread.currentThread() 获取当前正在运行的线程对象
线程中常用方法
线程阻塞
public static void sleep(long millis) 静态方法,进入阻塞状态,时间结束后进入可执行
public final void join() 成员方法,被谁调用,让哪个线程先执行,执行完毕后,在执行join方法所在的线程
public static void yield(),让步,让其他线程先执行,不一定生效,因为是CPU决定的
中断线程
public final void stop() 停止一个线程,已过时
public void interrupt() 打断线程的阻塞状态,进入可执行状态,会抛出异
sleep方法
实现多线程的第二种方式
实现多线程方式二:
1. 实现Runnable接口
2. 重写run方法
3. 创建Runnable接口的子类对象
4. 创建Thread类的对象,把第三步的对象传到构造方法中
5. 使用Thread类的对象,调用start方法
既然有了第一种实现方式,为什么还要有第二种?
解决单继承的局限性
好处:
解决单继承的局限性
符合面向对象的思想: 把线程任务和线程的对象分离开了
线程安全问题
产生原因
在卖票的过程中,出现了多个窗口卖同一张票,或者票数为0及负数的情况,这就是传说中的线程安全问题
线程安全问题产生的条件是什么?
1. 具备多线程的环境
2. 多个线程操作共享数据
3. 操作共享数据的代码有多条
加锁!,让某一时刻只能有一个线程进行操作:
同步代码块
同步方法
Lock
解决方案
同步代码块
同步方法
Lock
同步代码块
1. 同步代码块
synchronized(锁对象){
容易产生线程安全问题的代码
}
锁对象: 可以是任意对象, 要求多个线程使用的是同一个对象( 看该对象所在的类被new了几次)
使用Thread的方式实现多线程,没办法使用this对象作为锁
1.通过构造方法从传入一个相同的对象
2.使用class对象,用来描述字节码文件的,在jvm中只有一个
3.使用静态的对象
4.还可以使用字符串常量
同步方法
把synchronized 放到方法的修饰符中 , 锁的是整个方法
默认的锁对象
成员方法: this
静态方法: 类名.class
Lock
jdk5产生的新特性. ReentrantLock
lock
unlock
死锁
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
线程池
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
public static ExecutorService newCachedThreadPool()
创建一个具有缓存功能的线程池(60秒)(21亿)
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用的,具有固定线程数的线程池
public static ExecutorService newSingleThreadExecutor()
创建一个只有单线程的线程池,相当于上个方法的参数是
wait/notify/notifyAll
a. 得到锁和释放锁
当一个线程进入了同步代码块,并开始执行了里面的代码,就叫做获得锁,当他执行完毕后,退出代码块,允许其他程序进入同步代码块就叫做释放锁
b. wait,notify,notifyAll 这三个方法都是Object中的方法,并且这三个方必须在同步方法或同步代码块中使用.(锁对象)
wait: 让线程进入等待状态,进入等待状态的线程会释放锁对象(也就是允许其他线程进入同步代码块),直到使用相同的锁对象调用notify/notifyAll方法才会结束阻塞状态.
notify: 唤醒,会从等待池中唤醒一个处于等待状态的线程,使其进入可执行状态
notifyAll: 唤醒全部,会将等待池中所有处于等待状态的线程唤醒,使其进入可执行状态
c. notify或notifyAll以后,被唤醒的线程并不是立马执行,需要等到notify,notifyAll所在的代码块执行完毕后才会执行,因为只有同步代码块执行完毕后,才会释放锁对象,其他线程才可以进来.
d. wait方法会释放锁对象,也就是一个线程使用wait进入等待状态后,允许其他线程进入同步代码块
sleep方法不会释放锁对象,到时间自己醒
e. wait,notify,notifyAll 三个方法为什么位于Object类中?
因为这三个方法必须使用锁对象调用,锁对象可以是任意对象,所在在Object类中!
定时器
框架: framework
Timer
public Timer() 得到Timer对象
public void schedule(TimerTask task, long delay) 延迟多少毫秒后执行定时任务
public void schedule(TimerTask task,Date date) 指定时间执行定时任务
public void schedule(TimerTask task,long delay,long period) 延迟执行,指定间隔后循环执行
public void cancel() 取消定时任务
TimerTask abstractclass
public abstract void run()
schedule: 调度
多线程面试题
多线程有几种实现方案,分别是哪几种?
同步有几种方式,分别是什么?
启动一个线程是run()还是start()?它们的区别?
调用start方法会帮助我们自动调用run方法
sleep()和wait()方法的区别
线程的生命周期图
线程中的原子性