一、线程
1、概念
进程:
就是在操作系统中打开的一个应用程序
线程:
就是应用程序的一个执行单元, 一条执行路径
一个进程至少有一个线程
一个进程也可以有多个线程, 这个进程就是多线程应用程序
在多线程应用程序中, 每个线程都是相互独立的, JVM分为栈区,堆区,方法区三块区域, 如果程序是多线程程序,JVM会给每个线程分配单独的一个栈空间, 即每个线程都有独立的线程栈
主线程:JVM是从main方法开始运行程序,运行main方法的线程就是主线程
子线程:也称用户线程, 就是在一个线程中开启的新的线程
守护线程:是为其他线程提供服务的线程, 如JVM中垃圾回收器就是一个守护线程, 当JVM中只剩下守护线程时,JVM就会退出
在多线程应用程序中, 操作系统是以进程为单位给程序分配系统资源, 进程中的多个线程争抢当前进程的系统资源。简单点儿说, 单核CPU在某个时间点只能执行一个线程, 因为CPU执行速度非常快, 可以快速的在各个线程之间进行切换, 对于用户来说,好像是多个线程在同时执行
2、创建线程
(1)定义Thread类的子类
class SubThread extends Thread{
@Override
public void run(){
子线程要执行的代码
}
}
SubThread t1 = new SubThread();
t1.start();
(2)定义Runnable接口的实现类
Class Prime implements Runnable{
@Override
public void run(){
子线程要执行的代码
}
}
Thread t2 = new Thread( new Prime() );
t2.start();
(3)义Callable接口的实现类
class Prime implements Callable<String> {
public String call() throws Exception{
子线程要执行的代码
return “”;
}
}
FutureTask<String> task = new FutureTask( new Prime() );
Thread t3 = new Thread(task);
t3.start();
String result = task.get();
3、线程的常用方法
返回值 | 方法描述 |
---|---|
static int | activeCount() 当前活动线程的数量 |
static Thread | currentThread() 返回当前线程 |
ClassLoader | getContextClassLoader() |
long | getId() 返回线程ID,每个线程都有唯一的ID |
String | getName() 返回线程名称 |
int | getPriority() 返回线程优先级 |
Thread.State | getState() 返回线程状态 |
ThreadGroup | getThreadGroup() |
void | interrupt() 中断线程 |
static boolean | interrupted() 判断线程是否被中断 |
boolean | isAlive() 判断线程是否为活动线程,是否结束 |
boolean | isDaemon() 判断线程是否为守护线程 |
boolean | isInterrupted() 判断线程是否被中断 |
void | join() 线程加入(合并) |
void | join(long millis) |
void | run() |
void | setDaemon(boolean on) 把线程设置为守护线程 |
void | setName(String name) 设置线程名称 |
void | setPriority(int newPriority) 设置线程优先级 |
static void | sleep(long millis) 线程睡眠 |
void | start() 开启新的线程 |
void | stop() 终止线程 |
String | toString() |
static void | yield() 线程让步 |
4、线程完整生命周期
5、线程调度
(1)线程优先级
线程优先级取值范围: 1 ~ 10
线程默认优先级: 5
优先级越高,获得CPU执行权的机率越大
t1.setPriority( 10 ) 设置优先级
t1.getPriority() 返回优先级
(2)线程睡眠
Thread.sleep( millis)
静态方法, 通过类名直接调用
睡眠单位是毫秒, sleep()所在的线程进行睡眠
sleep()方法有受检异常需要处理, 如果是在run()方法中,只能捕获不能抛出
(3)线程中断
interrupt()
中断线程,一般是把睡眠/等待中的线程给唤醒
静态方法Thread.interrupted()
判断线程的中断状态, 如果返回true之后 ,会清除线程的中断标志
实例方法isInterrupted()
判断线程的中断状态,返回true之后 不会清除中断标志
(4)线程加入(合并)
t1.join()
在当前线程中加入t1线程, 当前线程转为等待状态, 一直等到t1线程执行完后,当前线程再转为就绪状态
t1.join(millis),
如果在millis毫秒内t1线程还没执行完, 当前线程不等了转为就绪状态
(5)线程让步
Thread.yield()
可以把当前线程由运行状态转为就绪状态
(6)线程终止
stop()可以终止线程, 过时了
想办法让run()运行结束 , 一般是设置一个标志, 在run()方法中不断的判断这个标志来决定是否结束
6、线程同步
(1) 线程安全问题:当多个线程同时操作堆区/方法区同一个数据时,可能导致数据不一致的现象,称为线程安全问题
出现了线程安全问题,怎么办?
1)每个线程都访问自己的局部变量,不会产生线程安全问题
2)如果多线程必须同时操作堆区/方法区同一个数据时, 可以采用线程同步机制
(2)线程同步
synchronized ( 锁对象 ){
同步代码块
}
(3)工作原理:
1)任何对象都可以作为锁对象, 每个对象都有一个内置锁
2)线程想要执行同步代码块, 必须先获得锁对象
3)线程a获得了锁对象,可以执行同步代码块, 会一直持有这个锁对象, 直到执行完同步代码块再释放锁对象
4)一个锁对象,在某一时刻最多只能被一个线程持有
(4)工作过程描述:
1)线程a获得CPU执行权,获得锁对象, 执行同步代码块
2)线程a在执行同步代码块期间, CPU执行权被线程b抢走了, 线程a转为就绪状态
3)线程b抢到CPU执行权 , 假设线程b也想执行同步代码块, 必须先获得锁对象, 现在锁对象被线程a持有, 线程b就会转到等待锁对象池中
4)线程a重新获得CPU执行权, 执行完同步代码块后, 会释放锁对象
5)等待锁对象池中处于阻塞状态的线程b可能获得锁对象后, 转为就绪状态
(5)同步代码块
同步代码块想要实现同步必须保证使用同一个锁对象,同步代码块只要使用了同一个锁对象就可以实现同步
经常使用一个常量对象作为锁对象
有时在实例方法中也会使用this对象作为锁对象
有时在静态方法中也会使用当前类的运行时类作为锁对象, 有人称它为类锁
(6)同步方法
当某个实例方法的整个方法体都需要进行同步,并且锁对象是this对象时, 可以直接使用synchronized关键字修饰这个实例方法, 称为同步实例方法
同步实例方法把整个方法体作为同步代码块,默认锁对象是this对象
当某个静态方法的整个方法体都需要进行同步,并且锁对象是当前类的运行时类对象时, 可以直接使用synchronized关键字修饰这个静态方法, 称为同步静态方法
同步静态方法是把整个方法体作为同步代码块,默认锁对象是当前类的运行时类对象
(7)死锁
多个线程进行同步时, 如果获得锁的顺序不一致,可能导致线程相互等待的情况,称为死锁
死锁条件:
1)一个锁对象只能被一个线程持有,互斥
2)一个线程获得了锁对象之后 ,会一直持有, 直到执行完同步代码块再释放
3)a线程获得了锁对象1, 还想获得锁对象2, 但是锁对象2在线程b手里持有
4)出现了相互等待的情况
如何避免出现死锁??
多个线程进行同步时, 保证获得锁的顺序都一样, 就不会出现死锁问题