课程连接:B站狂神说
基本概念
进程
执行程序的一次执行过程。是系统资源分配的单位。
线程
线程是CPU调度和执行的基本单位。一个进程至少包含一个线程。即使没有再创建线程,程序执行也是多线程的,main,GC线程
线程的创建(推荐使用第二个,因为类的单继承局限性)
创建一个类 继承Thread,并重写run方法,调用时使用start开启线程
开启的时候使用的是start方法,如果使用run,依旧是单线程。线程开启之后不一定直接执行,而是根据CPU的调度。
创建一个类 实现Runnable接口,重写run方法,调用时先创建类,然后使用Thread.start开启线程
class c=new class;
new Thread(c).start();
//使用接口实现,可以避免单继承局限性,方便一个对象被多个线程使用
package thread;
import java.util.TreeMap;
public class demo01 implements Runnable {
private static int number=10;
@Override
public void run() {
while(number>0){
try {
System.out.println(Thread.currentThread().getName()+"拿到了第"+number+"票");
number--;
Thread.sleep(0);
}catch (Exception e){
System.out.println(e);
}
}
}
public static void main(String[] args) {
//出现并发问题
demo01 d=new demo01();
new Thread(d,"YH").start();
new Thread(d,"DC").start();
new Thread(d,"UI").start();
}
}
创建一个类,实现Callable方法,需要返回值,重写call方法,并且需要开启服务,关闭服务,抛出异常
package thread;
import java.util.concurrent.*;
public class demo02 implements Callable<Boolean> {
private static int number=10;
@Override
public Boolean call() throws Exception {
while(number>0){
try {
System.out.println(Thread.currentThread().getName()+"拿到了第"+number+"票");
//currentThread()获得当前线程
number--;
Thread.sleep(0);
}catch (Exception e){
System.out.println(e);
}
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
demo02 d1=new demo02();
demo02 d2=new demo02();
demo02 d3=new demo02();
demo02 d4=new demo02();
//创建服务
ExecutorService es= Executors.newFixedThreadPool(3);
//提交线程
Future<Boolean> submit1 = es.submit(d1);
Future<Boolean> submit2 = es.submit(d2);
Future<Boolean> submit3 = es.submit(d3);
Future<Boolean> submit4 = es.submit(d4);
//获得结果
Boolean res1 = submit1.get();
Boolean res2= submit2.get();
Boolean res3 = submit3.get();
Boolean res4 = submit4.get();
//关闭服务
es.shutdown();
}
}
线程底层的原理 静态代理
原理
- 真实对象和代理对象需要实现同一个接口
- 真实对象要作为参数传入到代理对象中(相当于类中再传入一个类,但是两个都实现同一个接口,外层可以对接口的方法重写进行增强)
- 优点:两个接口分开,真实对象可以实现其他功能,代理对象增强接口的功能
对比动态代理(AOP面向切面编程使用到)
- 静态代理在编译期,动态代理在运行期
- 静态代理手写,动态代理右JDK提供的工具类,需要按照格式进行写
- 静态代理只能代理实现接口的类,动态代理又分为基于接口的和基于子类的
- 静态代理一个代理对象只能代理一种被代理对象,动态代理可以代理多个,是基于反射实现的 看注解与反射的笔记
线程使用
线程的五个状态
线程的操作方法
线程的停止
- 建议线程正常停止:利用次数,不建议死循环
- 建议使用标志位停止:定义一个公共的方法,在主方法中进行控制,来结束线程
- 尽量不要使用stop和destory方法
线程的休眠
- sleep 指定当前线程阻塞的毫秒值 在时间到了之后,进入就绪状态。
- 需要抛出InterruptedException异常。
- sleep不会释放锁。
- 模拟倒计时:利用系统时间进行显示时间
- 模拟网络延时:方法问题的发生
线程的礼让
- yield方法,让当前线程暂停,进入就绪状态,然后交由CPU重新调度
- 礼让是从运行状态转到就绪状态,不会进入阻塞状态
- 礼让不一定成功,CPU重新调度,可能还是会执行礼让的线程
线程的合并(插队)
- join方法 直接执行合并的线程(插队),强制执行,其他线程阻塞,等该线程结束之后,才会继续执行其他线程(和方法的调用差不多)
线程的观测
Thread thread=new Thread(()->{
//方法体
})
Thread.State state=new thread().getState();//创建
state=thread.getState();//更新
线程的优先级
- thread.setPriority() 设置优先级
- thread.getPriority() 查询优先级 currentThread() 获得当前线程对象
- 一共10个优先级,优先级大不一定先执行 只能是被调度的可能大
- 主线程的优先级为5
- 需要设置时,先设置再start
线程的分类
- 守护线程:thread.setDeamon(true) 设置为守护线程 虚拟机不用等待守护线程结束
- 用户线程:默认为用户线程 虚拟机必须等待用户线程结束
线程的同步---->队列加锁解决安全性
并发
多个线程访问同一资源
同步
锁机制 synchronized
使用synchronized关键字实现同步机制
只读的代码不用加synchronized,需要修改的才需要,如果synchronized太多,就会降低效率
- 同步方法 在方法上,返回值之前加synchronized关键字 其实锁的就是调用方法的this
- 同步代码块 把需要锁的对象放入参数,然后写代码 锁的对象就是需要增删改查的量
synchronized(number//该对象称为同步监视器){
while(number>0){
try {
System.out.println(Thread.currentThread().getName()+"拿到了第"+number+"票");
//currentThread()获得当前线程
number--;
Thread.sleep(0);
}catch (Exception e){
System.out.println(e);
}
}
}
lock锁
private final ReentrantLock lock=new ReentrantLock();// ReentrantLock可重入锁
while(number>0){
try {
lock.lock();//开启锁
System.out.println(Thread.currentThread().getName()+"拿到了第"+number+"票");
//currentThread()获得当前线程
number--;
Thread.sleep(0);
}catch (Exception e){
System.out.println(e);
}finally{
lock.unlock(); //打开锁
}
}
对比
死锁
某个同步代码块同时拥有两个以上对象的suo,就可能发生死锁。
JOC
线程通信
线程池
可能的面试问题
start和run的区别
- run是在主线程中调用方法,实际上没有开启新的线程;
- start是开启一条新的线程,然后调用其中的主方法。
- 即run没有开启新的线程,start开启了新的线程,所以一般使用start方法,重写run方法
sleep和wait的区别
- sleep是Thread中的方法,表示的是线程休眠一段时间,之后进入就绪状态;wait是Object的方法,被final修饰,表示线程进入等待状态,直到被唤醒(notify或者notifyAll)
- sleep可以在任意地方使用,wait只能在同步方法或同步代码块中使用
- sleep不会释放锁,wait是释放锁的
- sleep需要捕获异常 InterruptedException wait不需要捕获异常