线程 和 多线程
- 线程是程序内部一条可执行路径,线程在一个进程中可以存在多个,也可以并发执行,是系统可调度的最小执行单位
- 进程是一个正在执行的程序,是一个应用程序,它里面存在多个可执行调度的线程,是系统进行资源分配的最小单位
- 进程中包含线程,而且可以包含多个,一个进程中至少含有一个线程
- Java中线程最少有三个:主线程,gc线程,异常处理线程
- 程序:程序是一段指令集,是死的
关系和区别
-
进程、线程、协程本质上都是程序,进程中可以包含多个线程,线程中又可以分为多个协程,进程是系统资源的最小分配单位,线程是操作系统上的最小的调度单位,协程是用户态的执行单位
-
线程和进程区别:
- 进程有独立的地址空间,同一进程内的线程公用进程的地址空间
- 进程是资源分配的基本单位,线程是调度/执行的最小单位
并发和并行
- 并发指的是单核多任务,一个CPU同时运行多个线程,这些线程来回之间切换,看起来就像一起执行一样
- 并行指的是多核多任务,多个CPU同时工作,一个CPU对应一个线程运行
线程的基本实现
-
4中创建多线程的方式
-
1.5之前是两种一般是继承Thread和实现Runnable接口
-
继承Thread是重写run方法,start启动
- 子类继承Thread类,重写run方法
- 创建Thread类对象,并调用start方法
-
Runnable接口(方式好一些),接口可以多继承
- 实现Runnable接口,重写run方法
- 创建实现类的对象,创建Thread类对象
- 把实现类的对象传给Thread构造器
- Thread类的对象启动start方法
//直接创建一个线程对象
Thread()
//创建runnable接口实现类的线程对象,传给线程构造器
Thread(Runnable target)
-
start方法的两个作用:
- 启动该线程,一个线程对应一个对象,一个线程被启动后必能再次启动
- 启动线程后,会自动执行run方法
-
yield方法:线程a在执行时遇到yield方法,会让出cpu,让空闲的线程抢占执行,线程进入可运行(就绪)状态
-
join方法:线程a在执行时遇到某线程的join方法,会让出cpu,让调用了该方法的线程先执行,知道人家执行完
-
sleep方法:可以有时间参数,让线程睡眠,时间参数就代表间隔时间,比如线程a执行了sleep(3000),那就意味着a线程先等3秒,此线程进入阻塞状态,同样进入阻塞的有wait和join
- sleep():不释放锁
- wait():释放锁
-
stop方法:直接暂停线程
-
isAlive方法:查看线程是否存活,线程对象.isAlive()
线程的优先级设置
- max 10,min 1,普通 5
- 设置线程优先级 setPriority()
线程状态
- new:新建
- runnable:就绪
- running:运行(会获得cpu的使用权)
- blocked:运行过程中可能会
- waiting:等待,
- time_waiting:按时等待
- terminated:死亡
线程安全问题
- 两个线程对同一个资源的改动,线程1改动后,没有及时通知线程2时,会导致线程2错误修改数据
- 比如两个人取同一个账户的3000块钱,第一个人取了2000,因为线程阻塞,钱还没取出,第二个人也取了2000,这时,第二个取出,第一个人刚好取出2000,会导致3000超额,这就是线程安全
- 排队上厕所问题
解决线程安全的具体操作
-
加同步sychronized块,注意Runnable接口和Thread的小小区别(非共享锁)
synchronized(同步监视器){ //需要同步的代码 }
- 操作共享数据的代码就是需要被同步的代码
- 共享数据:多个线程共同操作的变量,比如多个人上厕所,人没出来,下一个就进去
- 同步监视器:就是锁,任何一个类的对象都可以充当锁。
- 要求:多个线程必须共用一个锁,也就是说这个对象是该进程的对象,线程必须共享这个对象
- 缺点:多线程搞的像单线程,效率低
- 优点:解决了线程安全问题,
- 注意点:该线程获得锁后,其他线程进入阻塞状态
-
给方法加同步关键字synchronized
- 如果一个操作共享数据的代码完整的声明在一个方法中,可以把这个方法声明成同步方法
- 添加synchronized关键字
- runnable和thread的区别在于锁不一致,static就行了
- static修饰的锁是类的对象
-
给共享数据加volatile关键字
-
使用显示锁Lock (JDK5.0新增)
- 创建Lock的实例对象,Lock是接口,可以用其实现类ReentrantLock()
- 再在同步代码块中对象调用lock()方法,即为获得锁
- 在代码完成处对象调用unLock()方法,是释放锁
-
Lock的优势:
- 手动加锁,手动释放锁
-
synchronized的优势:
- 会自动的释放锁
死锁
- 不同的线程分别占用对方需要同步的资源不放,都在等待对方放弃自己的同步锁
- 死锁需要避免
- 利用专门的算法,规避死锁
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
- 产生死锁的必要条件:
- 循环等待
- 资源不可抢占
- 资源互斥
- 占有等待
线程通信(同步代码块间的)
- wait():执行此方法,当前线程进入阻塞状态,并释放同步监视器
- notiy():唤醒被wait的一个线程,如果有多个线程,会唤醒优先权高的
- notifyAll():唤醒全部线程
- 这三个方法必须使用在同步代码块或者同步方法中,Lock锁有其他的方法
- 这三个方法的调用者必须是同步代码块或者同步方法中的同步监视器(锁对象)
sleep()和wait()的异同?
- 相同点:两个方法都能使线程进入阻塞状态
- 不同点:
- 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同,sleep()可以在任何场合下调用,wait()只能在同步代码块或者同步方法中
- 如果两个方法在同步代码块或者同步方法中,sleep()不释放锁,wait()释放锁
新增的创建线程的方式(JDK5.0后新增)
-
实现Callable接口
- 重写call方法,有返回值,返回值是泛型
- 该方法可以抛出异常
- 创建Callable接口的实现类的对象
- 创建FutureTask对象,把实现类对象传给这个FutureTask构造器
- 将FutureTask对象传给Thread类的构造器中,然后start
- 也可以获取call()的返回值
-
创建线程池(executor(new MyThread()),submit(new MyTrhread()))
- 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 提前创建好多个线程,放进线程池,使用时可直接获取,使用完放回线程池,可以避免频繁的创建和销毁,实现充分利用
- 好处:
- 提高了响应速度(减小了响应速度)
- 降低了资源消耗
- 便于线程管理:核心池大小,最大线程数,没有线程任务时多久会终止
- 一般使用ThreadPoolExecutor,该类中有管理线程池的一些属性
总结:
-
画图说明线程的基本状态及各个线程切换使用的方法等
- 生命周期:就是从状态A到状态B时,哪些方法执行了(回调方法)。从创建到消亡
- 多线程的周期:调用了某些方法,线程从什么状态到什么状态
-
谈谈对同步代码块中涉及到的同步监视器和共享数据的理解
- 同步监视器:在同步代码块中的括号中写的,他是一个对象,这个对象必须要被线程共享
- 共享数据:被多个线程操作的数据,
- 同步代码块中的{}中放的是:操作共享数据的代码
- 同步方法:同步方法的同步监视器
- 如果是静态static方法,监视器就是类本身的class对象
- 如果是非static方法,监视器就是该类对象:this
-
sleep和wait的区别?
- 两个方法的所属位置不同,sleep声明在Thread中,wait声明在Object中
- sleep不会释放锁,wait会释放锁
- 使用位置也不同。sleep可以在任何场合使用,wait只能在同步代码块或者同步方法中使用
-
实现线程的几种方式?
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池