线程简介
程序、进程、线程
- 程序是一段静态的代码, 是应用软件执行的蓝本
- 进程是程序的一次动态执行过程, 对应了从代码加载、执行至执行完毕的一个完整过程, 这个过程也是进程本身从产生、发展至消亡的过程
- 线程是比进程更小的执行单位。
进程在其执行过程中,可以产生多个线程
, 形成多条执行线索。每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。
线程和进程的区别
Java中的线程
-
每个程序都有一个默认的主线程:当JVM加载代码发现main方法后,就会立即启动一个线程,该线程称为主线程
-
主线程的特点:
(1)是产生其他子线程的线程
(2)不一定是最后完成执行的线程 -
多线程的优势
(1)减轻编写交互频繁、涉及面多的程序的困难
(2)程序吞吐量
会得到改善
(3)由多个处理器
的系统,可以并发运行
不同的线程
(4)“同时”执行只是人的感觉,在线程之间实际上轮换执行
线程的生命周期
线程完整的生命周期包括五个状态:新建、就绪、运行、阻塞和死亡
- 新建状态:线程对象
已经创建
, 还没有在其上调用start()方法 - 就绪状态:当线程
调用start()方法
, 但调度程序还没有把它选定为运行程序
时线程所处的状态 - 运行状态:线程调度程序可从运行池中选择一个线程作为
当前线程
时线程所处的状态,这也是线程进入运行状态的唯一方式 - 阻塞状态:线程仍旧是活的,但是当前
没有条件运行
。它是可运行的,当某件事件出现,他可能返回到可运行状态 - 死亡状态:当线程的
run()方法完成
时就认为它死去。线程一旦死亡,就不能复生。 一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
操作系统中睡眠、阻塞、挂起的区别形象解释
首先这些术语都是对于线程来说的。对线程的控制就好比你控制了一个雇工为你干活。 你对雇工的控制
是通过编程来实现的。
(1)挂起线程的意思就是你对主动对雇工说:“你睡觉去吧,用着你的时候我主动去叫你,然后接着干活”。(阻塞)
(2) 使线程睡眠的意思就是你主动对雇工说:“你睡觉去吧,某时某刻过来报到,然后接着干活”。
(3) 线程阻塞的意思就是,你突然发现,你的雇工不知道在什么时候没经过你允许,自己睡觉呢,但是你不能怪雇工,肯定你这个雇主没注意,本来你让雇工扫地,结果扫帚被偷了或被邻居家借去了,你又没让雇工继续干别的活,他就只好睡觉了。至于扫帚回来后,雇工会不会知道,会不会继续干活,你不用担心,雇工一旦发现扫帚回来了,他就会自己去干活的。因为雇工受过良好的培训。这个培训机构就是操作系统
Java中创建多线程
在此留下之前写过的一篇创建多线程的博客链接
创建线程的三种方式
以下为补充内容。
-
每个线程都有优先权,具有较高优先级的线程优先于优先级较低的线程执行
-
创建线程的方式对比
1.继承Thread类实现多线程
优点:编写简单,如果需要访问当前线程,直接使用this即可获得当前线程
缺点:Java为单继承的继承机制,由于已经继承了Thread类,不能再继承其他的父类
2.实现Runnable接口
优点:
1)只实现了Runnable接口,还可继承其他的类
2)该方式可多个线程共享同一个目标对象
,非常适合多个线程来处理同一份资源的情况,从而可以将代 码与数据分开,形成清晰的模型,较好地体现了面向对象的思想
缺点:
编程稍微复杂,需要访问当前线程,必须使用Thread.currentThread()
-
相关的细节吧:
(2)一个线程的run方法执行结束后,该线程结束。一个线程只能被启动一次
(3)运行状态需要CPU调度,JVM去操控的。Run不是程序员调用的,是JVM调用的 -
相关的方法
(1)isAlive():测试线程的状态,新建、死亡、阻塞状态的线程返回false
(2)interrupt():“吵醒”休眠的线程,唤醒“自己”
(3)yield():暂停正在执行的线程,让同等优先级的线程运行,进入就绪状态
礼让线程
让同等优先级的线程运行“,有可能当前线程依然被调用
(4)join():当前线程等待调用该方法的线程结束后,再排队等待CPU资源,进入阻塞状态
插队线程
在线程A中调用B.join()。让一个线程A“加入”到线程B的尾部。在B执行完毕之前,A不能工作 join方法还带有超时限制的重载版本
(5)sleep方法实现线程的睡眠,暂停执行,在苏醒之前不会返回到可运行状态,当睡眠时间到期,则返回
可运行状态
sleep()中指定的时间是线程不会运行的最短时间。 因此,sleep()方法不能保证该线程睡眠到期后就开始执行; sleep()是静态方法,只能控制当前正在运行的线程
(6)stop()终止线程
-
线程的优先级
(1)多线程运行时,JVM的调度策略为按优先级调度
,级别相同时由操作系统按时间片来分配。(线程优先级通常表示为1~10的数字)
(2)设置线程优先级(线程默认优先级是创建它的执行线程的优先级)setPriority()设置线程的优先级, getPriority()得到线程优先级
(3)当线程池中线程都具有相同的优先级,调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止;二是时间分片,为池内的每个线程提供均等的运行机会
(4)线程让步:yield方法
(5)线程阻塞:join方法
多线程的同步和死锁
-
多线程程序在设计上最大的困难在于,各个线程的控制流彼此独立,使得各个线程之间的代码是 乱序执行 的,而且各个线程共享资源,所以多线程会带来
线程调度、同步、死锁
等一系列问题 -
线程同步:当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用
java引入了 对象互斥锁 的概念,来保证共享数据操作的完整性,从而避免上述问题 -
每个对象都有一个 “互斥锁”
关键字synchronized
与对象互斥锁联合起来使用保证对象在任意时刻只能由一个线程访问
synchronized可以修饰方法,表示这个方法在任意时刻只能由一个线程访问
Java中每个对象都对应一个称为“互斥锁”的标记
下面这个代码对于同一个对象,两个方法只能运行一个
class Stack{
int idx=0;
char[ ] data = new char[6];
public synchronized void push(char c){
data[idx] = c;
idx++;
}
public synchronized char pop(){
……
}
}
synchronized可以修饰类,则表明该类所有对象
共用一把锁
class Stack{
int idx=0;
char[ ] data = new char[6];
public void push(char c){
synchronized(Stack.class){
data[idx] = c;
idx++;
}
}
public char pop(){
……
}
}
举一个栗子,12306卖票,不同窗口之间要实现票数的共享
在实现Runnable接口的类中,写了四种卖票的方法,区别为锁的对象不同。可尝试运行看看效果,锁的应该为共享的资源
package ch01_thread_1;
import java.util.logging.Handler;
public class My12306 implements Runnable{
private int num;
boolean flag = true;
public My12306(int num) {
this.num = num;
}
//方法里锁的this, 锁的范围为这个方法,只有一个卖
public synchronized void sale1() throws InterruptedException {
if(this.num <= 0) {
this.flag = false;
return ;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "---卖出" + num --);
}
//同步代码块, 范围太小,出现超卖,锁不住
public void sale2() throws InterruptedException {
if(this.num <= 0) {
this.flag = false;
return ;
}
synchronized (this) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "---卖出" + num --);
}
}
//线程安全,实现多线程
public void sale3() throws InterruptedException {
if(this.num <= 0) { //有没有票
this.flag = false;
return ;
}
synchronized (this) {//考虑最后一张票
if(this.num <= 0)
{
this.flag = false;
return ;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "---卖出" + num --);
}
}
//锁
public void sale4() throws InterruptedException {
if(this.num <= 0) { //有没有票
this.flag = false;
return ;
}
synchronized ((Integer)this.num) { //锁不住,一个数字是一个对象
if(this.num <= 0)
{
this.flag = false;
return ;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "---卖出" + num --);
}
}
@Override
public void run() {
while(flag) {
try {
sale4();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package ch01_thread_1;
public class demo_12306 {
public static void main(String[] args) {
My12306 my12306 = new My12306(20);
Thread t1 = new Thread(my12306);
t1.start();
Thread t2 = new Thread(my12306);
t2.start();
Thread t3 = new Thread(my12306);
t3.start();
}
}
-
死锁
当两个或两个以上的线程在执行过程中,因争夺资源而造成了互相等待,并且若无外力作用,它们都将无法推进下去的现象称为系统处在死锁状态或系统产生了死锁
资源占用是互斥的,当某个线程提出申请资源后,使得有关线程在无外力协助下,永远分配不到必需的资源而无法继续运行
【tips:Eclipse设置智能提示,会加快我们敲代码的速度】