线程和进程的概念
1.每个运行中的程序就对应一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能。当一个程序运行时,内部可能包含多个顺序执行流,每个顺序执行流就是一个线程。
2.线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
3.进程之间拥有独立的内存资源,而线程之间则是共享父进程里的全部资源。
4.线程是独立运行的,它并不知道进程中是否还有其他线程存在。线程是抢占式的运行,也就是我们所说的并发性。
线程的创建和启动
1.继承Thread类创建线程
public class FirstThread extends Thread{ //重写run方法,run方法的方法体就是线程执行体 public void run(){ //线程体... } } //启动线程 FirstThread ft = new FirstThread(); ft.strat();//调用strat方法启动一个线程
2.实现Runnable接口创建线程
public class FirstRunnable implements Runnable{ //重写run方法 public void run(){ //线程体... } } //启动线程 FirstRunnable fr = new FirstRunnable(); Thread thread = new Thread(fr); thread.strat();//开启线程,开始执行子线程中的线程体
两种创建线程的比较:
使用继承Thread类的方式实现线程,编写比较简单,而且可以通过this直接访问当前线程。可是因为Java点继承的特点,将导致该类无法再继承其他类。
使用实现Runnable接口的方式实现线程,代码稍微繁琐,访问当前线程必须通过Thread.currentThread()方法,而且可以多个线程共享一个Runnable对象,非常适合多个相同线程来处理同一份资源的情况,而且可以继承其他的类。
因此大多数的情况下,我们都推荐使用Runnable接口的方式来创建线程
线程的一些基本方法
void start(); 使线程进入“就绪”状态
static Thread currentThread(); 总是返回当前正在执行的线程对象的引用
boolean isAlive(); 判断线程是否处于活动状态
String getName(); 返回该线程的名称
void setName(String name); 设置线程的名称
线程的生命周期
1.新建(New):
使用new关键字创建一个线程对象之后,该线程就处于了新建状态,这时它和其他Java对象一样,仅仅由Java虚拟机分配了内存,并初始化其成员变量的值。这个时候线程对象还没有表现出线程的动态特征,线程体也不会被执行
2.就绪(Runnable)
当线程对象调用start()方法以后,线程进入就绪状态,线程的run方法体并不会立刻执行,只是告诉程序该线程可以运行了。至于何时运行,则取决于JVM的线程调度器的调度。
3.运行(Running)
如果一个处于就绪状态的线程获得了CPU,则该线程会开始执行run()方法内的线程执行体,此时线程进入了运行状态。通常来讲一个线程不会一直处于运行状态(除非线程的线程体足够短,立刻就执行完成了),线程在运行过程中会被中断,目的是使其他线程获得执行的机会,线程调度取决于底层平台所采用的策略。
4.阻塞(Blocked)
当一个运行状态下线程除了被强制中断以外,还有可能被阻塞,从而进入到阻塞状态,当前执行的线程进入阻塞状态后,其他线程就可以获得执行的机会。被阻塞的线程在合适的时候会解除阻塞,从而再次进入就绪状态(不是运行状态),等待线程调度器再次调度它
可能引起线程阻塞的情况:
a.线程调用sleep()方法主动放弃所占用的处理器资源
b.线程调用一个阻塞式的IO方法,在该方法返回之前,线程被阻塞
c.线程在等待某个通知
线程的解除阻塞:
a.调用sleep()方法的线程经过了指定的时间
b.线程调用的阻塞式IO方法已经返回
c.线程正在等待某个通知时,其他线程发出了一个通知
5.死亡(Dead)
线程结束后,就会进入死亡状态。
如何结束线程?
a.当线程的线程体执行完成,线程正常结束
b.线程抛出一个未捕获的Exception或Error
c.直接调用线程stop()方法来结束该线程,此方法已经过时,并且容易导致死锁,所以不推荐使用
注意:
1.可以通过isAlive()方法来判断当前线程的状态,如果线程处于就绪、运行、阻塞状态,该方法返回true;
如果处于新建、死亡状态,该方法返回false;
2.只能对新建状态的线程调用start()方法,只能调用一次
线程的控制
1.join方法
join线程是一个线程等待另一个线程完成的方法。当某个线程A调用了其他线程B的join()方法时,那么线程A将被阻塞,直到线程B执行完毕后,线程A才重新进入就绪状态
重载方法:
join(long millis); 等待被join的线程时间最长为millis毫秒,如果在millis毫秒内被join的线程还没有执行结束,则不再等待
2.sleep方法
Thread类有一个静态方法sleep()方法,该方法可以使当前正在执行的线程暂定一段时间,进入阻塞状态。sleep()方法有一个参数,用来设置线程暂停多少毫秒,经过指定时间后,线程重新进入就绪状态
处于sleep()休眠中的线程不会被执行,即便当前没有任何线程在执行,该线程也不会执行
3.yield方法
Thread类提供了一个静态方法yield(),该方法可以让当前正在执行的线程暂停,但是不会阻塞该线程,而是直接将该线程转入到就绪状态。
注意:
a.完全有可能一个线程调用了yield方法后,线程调度器又调用该线程重新执行
b.当某个线程调用了yield()方法暂停后,只有优先级大于等于该线程的,并且处于就绪状态的线程才会获得执行的机会
线程的优先级
每个线程执行时都具有一定的优先级,优先级高的线程可能会比优先级低的线程获得更多的执行机会。
Thread类提供了setPriority(int newPriority);方法来设置线程的优先级,范围是1-10,数字越大优先级越高。
另外Thread还提供了三个常量来表示优先级:
MAX_PRIORITY:其值是10。
NORM_PRIORITY:其值是5。
MIN_PRIORITY:其值是1。
注意:
a.每个线程的优先级默认都和创建它的父线程优先级相同
b.我们通常都应该使用Thread提供的三个常量来设置优先级,这样程序才会有更好的移植性
线程的同步
在实际编程过程中,可能需要多个线程访问同一份资源的情况。但是由于调度器的随机性,可能会出现一些错误情况,这就是我们所说的线程安全问题。为了解决线程的安全问题,就需要线程的同步。
1.同步代码块
语法:
synchronized (obj) { //需要同步的代码 }
同步代码块的作用
上面语法中 synchronized是一个同步关键字,后面括号中的obj就是同步监视器,任何一条线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定,这样其他线程就能访问同步代码块中的内容了。总之任何时刻都只会有一个线程会获得对同步监视器的锁定。
注意:
Java中允许使用任何对象作为同步监视器,但是通过我们应该使用可能并发访问的共享资源充当同步监视器。
同步方法
就是用synchronized关键字修饰的方法,称为同步方法。同步方法无需指定同步监视器,同步监视器默认为this,也就是对象本身。
死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,而且Java虚拟机并没有检测死锁的机制,也不会采取任何措施来处理死锁,所以多线程编程时应该避免死锁的出现,一旦发生死锁,程序不会有任何异常,也不会给任何提示,只是两个线程处于阻塞状态,无法继续。
线程的并发协作
在java系统中,线程的调度有一定的随机性,通常无法准确的控制线程的轮换执行,但是我们可以通过一些机制来保证线程的协调运作,这就是线程的并发协作。
1.线程通信
a.wait()方法
该方法导致当前线程等待,直到其他线程调用该同步锁对象的notify()方法或者notifyAll()方法来唤醒该线程。
b.notify()方法
唤醒该同步锁对象上等待的某个线程,如果有多个线程在此同步锁对象上等待,则随机唤醒其中一个。
c.notifyAll()方法
唤醒在此同步锁对象上等待的所有线程
注意: wait、notify、notifyAll三个方法必须被在同步代码中被调用,并且只能由同步锁对象来调用
线程池
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存周期很短的线程时,更应该考虑使用线程池。
线程池工作机制:
线程池在系统启动是即创建大量的空闲的线程,
程序将一个Runnable对象传给线程池,线程池就会调用一个线程来执行它们的run方法,当run方法执行完成后,该线程也不会死亡,而是再次返回线程池中称为空闲线程,等待执行下一个Runnabe对象中的run方法
Java提供的线程池
1、调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
2、创建Runnable实现类作为线程执行对象
3、调用ExecutorService对象的submit()方法来提交Runnable实现类对象,交给一个线程池中线程执行
4、在需要的时候调用shutdown()方法关闭线程池
ThreadLocal类
1.ThreadLocal类的作用
ThreadLocal代表了一个线程局部变量,通过把数据放在ThreadLocal中就可以让每个线程持有一个独立的变量副本,从而避免并发访问的线程安全问题
2.ThreadLocal类的常用方法
a.T get(); 返回此线程中的变量副本值
b.void remove(); 删除此线程局部变量中当线程的值
c.void set(T t); 设置此线程局部变量中当前线程副本中的值
3.什么时候需要使用ThreadLocal
如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果需要隔离多个线程之间的共享冲突,则可以使ThreadLocal