程序、进程、线程
程序是一段静态的代码,是应用软件执行的蓝本。
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。
线程是比进程更小的执行单位,每个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。如果在一个进程中只有一个执行线索,则称为单线程。如果在一个进程中可以同时运行多个不同执行线索,执行不同的任务,则称为多线程。
进程 | 线程 |
---|---|
一个应用程序一个进程 | 进程可以创建多个子线程 |
程序是作为独立运行的进程 | 线程是程序执行的最基本的单位 |
一个应用程序一个进程 | 一个进程可以有多个线程 |
进程之间不可共享数据 | 线程共享一块内存空间 |
Java中的线程
每个Java程序都有一个默认的主线程
当JVM加载代码发现main方法之后,就会立即启动一个线程,这个线程称为主线程。
- 主线程的特点:
– 是产生其他子线程的线程
– 不一定是最后完成执行的线程 - 多线程的优势:(同时处理多件事;多个线程分解大任务)
– 减轻编写交互频繁、涉及面多的程序的困难。
– 程序的吞吐量会得到改善。
– 由多个处理器的系统,可以并发运行不同的线程.(否则,任何时刻只有一个线程在运行)
– “同时”执行是人的感觉,在线程之间实际上轮换执行。
线程的生命周期
线程完整的生命周期包括五个状态:新建、就绪、运行、阻塞和死亡。
- 新建状态:线程对象已经创建,还没有在其上调用start()方法。
- 就绪状态:当线程调用start()方法,但调度程序还没有把它选定为运行线程时线程所处的状态。
- 运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一方式。
- 等待/阻塞/睡眠sleep()状态:线程仍旧是活的,但是当前没有条件运行。它是可运行的,当某件事件出现,他可能返回到可运行状态。
isAlive()为true:存活状态 - 死亡状态:当线程的run()方法完成时就认为它死去。一个死去的线程上调用start()方法,会抛出异常。stop()终止线程
Java中创建多线程
Java中两种创建线程的方式:
- 继承Thread类
• 重写run() 方法(无参)
• new一个线程对象(不同)
• 调用对象的 start() 启动线程
- 实现Runnable接口
• 实现run() 方法
• 创建一个Runnable类的对象r,new MyRunnable()
• 创建Thread类对象并将Runnable对象作为参数,new Thread( r )(不同)
• 调用Thread对象的start()启动线程
- 两种方式的区别:
• 继承Thread类实现多线程
– 编写简单,如果需要访问当前线程直接使用this即可获得当前线程.
– 继承了Thread类,不能再继承其他的父类.
– 不能实现多线程间的资源共享
• 实现Runnable接口
– 只实现了Runnable接口,还可以继承其他的类,便于扩展
– 很方便实现多线程间的资源共享
– 如果需要访问当前线程,必须使用**Thread.currentThread()**方法。
相同点:都需要创建对象,并调用start()方法启动线程执行
在一个程序里多个线程只能保证其开始时间,而无法保证其结束时间,执行顺序也无法确定。
一个线程只能被启动一次
线程中常用的方法
• start():启动线程,让线程从新建状态进入就绪队列排队;
• run():线程对象被调度之后所执行的操作;
• sleep():暂停线程的执行,让当前线程休眠若干毫秒;
• currentThread():返回对当前正在执行的线程对象的引用;
• isAlive():测试线程的状态,新建、死亡状态的线程返回false。
• interrupt():“吵醒”休眠的线程,唤醒“自己”;
• yield():暂停正在执行的线程,让同等优先级的线程运行;
• join():调用该方法的线程先执行;
• stop():终止线程。
• wait():线程等待,直到被其他线程唤醒
• notify(), notifyAll():唤醒其他线程
阻止线程执行
Java中通过Thread的静态方法sleep来实现线程的睡眠。当线程睡眠时,它暂停执行,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。
阻止线程执行:线程睡眠,线程等待,线程阻塞
sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
sleep()是静态方法,只能控制当前正在运行的线程。
线程的优先级
- 设置线程优先级
– 线程默认优先级是创建它的执行线程的优先级.
– 通过Thread实例调用setPriority()方法设置线程优先级
• Thread.MIN_PRIORITY (1)
• Thread.NORM_PRIORITY (5)
• Thread.MAX_PRIORITY (10)
– 通过Thread示例调用getPriority()方法得到线程优先级 - 当线程池中线程都具有相同的优先级,调度程序的操作有两种可能:
– 一是选择一个线程运行,直到它阻塞或者运行完成为止。
– 二是时间分片,为池内的每个线程提供均等的运行机会。 - 线程让步通过yield方法来实现,暂停当前正在执行的线程对象,并执行同等优先级的其他线程。
但yield()将导致线程从运行状态转到可运行状态,有可能没有效果无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
- 线程阻塞
在线程A中调用B.join()。让一个线程A“加入”到线程B的尾部。在B执行完毕之前,A不能工作。
互斥锁
多线程程序在设计上最大的困难在于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,而且各个线程共享资源,所以多线程会带来线程调度、同步、死锁等一系列的问题。
- Java中每个对象都对应一个称为“互斥锁”的标记
– 关键字synchronized 与对象互斥锁联合起来使用保证对象在任意时刻只能由一个线程访问
class Stack{
int i=0;
char[ ] data = new char[6];
public void push(char c){
synchronized(this){
data[i] = c;
i++;
}
}
}
public char pop(){
synchronized(this){
i--;
char c = data[i];
data[i] = ' ';
return c;
}
}
对当前对象加锁 synchronized(对象或属性)
- synchronized可以修饰方法,表示这个方法在任意时刻只能由一个线程访问;
class Stack{
int i=0;
char[ ] data = new char[6];
public synchronized void push(char c){
data[i] = c;
i++;
}
public synchronized char pop(){
……
}
}
- synchronized可以修饰类,则表明该类的所有对象共用一把锁。
class Stack{
int i=0;
char[ ] data = new char[6];
public void push(char c){
synchronized(Stack.class){
data[i] = c;
i++;
}
}
public char pop(){
……
}
}
当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。
死锁
当两个或两个以上的线程在执行过程中,因争夺资源而造成了互相等待,并且若无外力作用,它们都将无法推进下去的现象称为系统处在死锁状态或系统产生了死锁.
资源占用是互斥的,当某个线程提出申请资源后,使得有关线程在无外力协助下,永远分配不到必需的资源而无法继续运行。
产生死锁的必要条件
- 互斥条件:指线程对所分配到的资源进行排它性使用.
- 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求.
- 不可剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放.
- 环路等待条件:指在发生死锁时,必然存在一个线程—资源的环形链.