线程
定义:所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变为一个进程。进程是处于运行过程中的程序,并且具有一定的独立性。一个进程至少有一个线程,也可以有多个线程 。
什么时候使用Runnable或Thread?
- 取决于这个类需不需要重写run之外的方法。
- Runnable不影响本类的继承关系,以及类组成
特征:
- 独立性:进程可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。没有经过进程本身允许的情况下,一个用户进程不能直接访问其他进程的地址空间。
- 动态性:进程和程序的区别在于程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。进程中加入了时间的概念。进程具有自己的生命周期和状态。
- 并发性:多个进程可以在单个处理器上并发执行,不会互相影响。
线程的创建:
- 通过继承Thread类来创建并启动线程的步骤:
- 定义Thread类的子类,重写该类的
run()
方法(线程执行体) - 创建Thread子类的实例(创建子线程)
- 调用线程对象的
start()
方法来启动线程。
- 定义Thread类的子类,重写该类的
- 通过实现Runnable接口来创建并启动线程的步骤:
- 定义Runnable接口的实现类,重写该接口的
run()
方法 - 创建Runnable实现类的实例
- 调用线程对象的
start()
方法来启动线程
- 定义Runnable接口的实现类,重写该接口的
- 通过创建匿名内部类来创建和启动线程
- 通过继承Thread类来创建并启动线程的步骤:
线程的停止
interrupt :唤醒一个被sleep或者被wait的线程(原理是改变循环条件)线程的五种状态:
新建 就绪 运行 阻塞 死亡
注意:
线程从阻塞状态只能进入就绪状态,不可直接进入运行状态
线程进入死亡状态后不可再次调用start()
方法后台线程(守护线程,精灵线程):
setDaemon(boolean)
设置线程为守护线程
该方法必须在启动之前调用
如果所有的前台线程都死亡,则后台线程会自动死亡。如垃圾回收线程。
多线程
多线程出现安全问题的前提:
- 对于多个线程来说,必须同时操作共享资源
- 操作资源的代码有多条语句,如果出现挂起,未完整执行语句,由于数据未完成,导致数据发生异常
解决办法:synchronized
(上锁)
每一把锁都是唯一的,所以指定一个唯一的对象,虽然允许使用任何对象作为同步监视器(锁),但是锁存在的意义是阻止两个线程对同一个共享资源进行并发访问,所以通常建议使用可能被并发访问的共享资源充当锁同步监视器(锁)。
- 同步代码块(互斥区)(对象为监视器(锁))
synchrinized(对象){
执行代码1;
执行代码2;
}
什么情况使用:只要操作了共享数据就应该使用同步代码块
- 同步方法(方法)
public synchronized void test{
}
同步方法的锁对象就是当前类的对象(this)
- 同步静态方法
public static synchronized void test{ }
同步方法的同步监视器是this
即调用该方法的对象 。
静态同步方法的同步监视器(锁对象)是当前类.class
wait和sleep的区别
1. wait会放弃执行资格,会释放锁。不占用cpu
2. sleep放弃执行资格,但是不会释放锁,sleep后进入了阻塞状态,占用cpu
线程池:
wait()
—让线程冻结
notify()
—唤醒第一个冻结线程
notifyAll()
—唤醒所有冻结线程
使用前提是有锁,因为与锁有关,且锁可以是任意对象,所以将这三个方法定义在Object中。
线程间通信时产生的线程安全问题解决办法:
1. 只要是操作了共享的数据,都需要使用同步代码块,且同步代码块的锁必须统一
2. 先判断是否继续循环,使用while来控制线程被唤醒后,必须判断是否执行,notifyAll全部唤醒线程,防止进程唤醒本类线程,导致安全问题。
生产者、消费者模式
jdk1.5之前使用while notifyAll方法,之后使用lock Condition方法实现,相比较,Lock方式更加灵活,可以指定需要启动的线程。
死锁
死锁出现的必要条件:
1. 资源匮乏
2. 有多个锁,多个线程都操作了此资源
3. 锁之间有使用对方资源的情况,必须有两把锁以上,且需锁中有锁。