1、什么是线程
学习线程,我们首先要了解什么是进程,它和进程有什么样的区别和联系?
找了很多资料,大部分都是在说:进程是资源分配的最小单位,线程是操作系统调度的最小单元,这样理解起来还是很抽象,我们先看一副图,我们在解释下它们的概念。
如图所示,我们直观上看到,进程之间是相互独立的,正是因为这个原因,所以进程间的数据很难共享,进程间的切换也比较耗费资源;一个进程可以包含多个线程,如线程1和线程2,所以同一进程间的线程能够进行数据共享,又因为线程上的代码的执行需要被记录下来,每个线程都拥有各自的计数器、堆栈和局部变量。
总结进程和线程的特点:
进程
- 一个进程包含多个线程
- 不同进程间的数据很难共享
- 进程的上下切换比线程消耗更多的计算机资源
- 进程间是互相独立的
线程
- 线程依赖于进程
- 同一进程下的线程可以数据共享
- 线程的运行会影响进程
- 线程拥有自己的计数器、堆栈和局部变量等
二、线程的使用
创建线程有四种方式,分别为①继承Thread类创建线程②实现Runnable接口创建线程③使用Callable和Future创建线程④使用线程池。
因为这篇文主要是对线程做一些概念上的讲解,所以这里我们用前两种方式做参考,后两种方式,有兴趣的同学可以查阅一些资料进行学习。
- 继承Thread类创建线程
Thread thread= new Thread(){
@Override
public void run () {
//需要做的任务
}
};
thread.start();
- 实现Runnable接口创建线程
//自定义Runnable
class MyRunnable implements Runnable{
@Override
public void run () {
//需要处理的任务
}
}
//创建Runnable对象
Runnable r = new MyRunnable();
//创建Thread对象
Thread t = new Thread(r);
//启动线程
t.start();
三、为什么要使用多线程
进程使得不同程序之间的可以运行,我们在网站看新闻不影响我们听歌;而线程是在应用程序的不同功能并发执行,我们利用微信在和朋友视频聊天的时候不会影响我们发文件给对方。这是我们很直观的感受多线程的好处,事实上,使用多线程有以下优点
- 更好的利用资源
对于多核处理器的计算机,可以将多个线程分配到多个处理器上,这样就会减少程序的运行时间,能够更好的利用计算机的资源。
- 更快的提高程序的响应时间
对于一个应用程序,运行它需要耗费很长的时间,这是可以先响应窗口线程,一些加载功能可以放在后台线程上去做,这样有利于提升用户体验。
四、线程的优先级
在学习线程的优先级之前,我们需要了解线程的优先级是怎么定义的?
我们都了解线程的执行需要CPU分配时间片,当线程的时间片用完之后就会发生调度,并等待着下次分配,而分配时间片的大小决定着线程的优先级。
在Java程序中,线程的优先级是用数字表示的,优先级范围为[1,10],数字越大优先级也就越高,不在这个范围的优先级则会抛出异常,默认的优先级的是5,一般来说,优先级高的线程会比优先级低的线程先执行,我们用一段代码来讲解下线程优先级的设置和不同优先级线程的执行顺序。
public class ThreadPriority {
public static void main(String[] args){
//定义三个线程
ThreadA A=new ThreadA();
ThreadB B=new ThreadB();
ThreadC C=new ThreadC();
//设置线程优先级,C线程为默认优先级
A.setPriority(1);
B.setPriority(10);
//打印线程的优先级
System.out.println(A.getPriority());
System.out.println(B.getPriority());
System.out.println(C.getPriority());
A.start();
B.start();
C.start();
}
}
class ThreadA extends Thread{
@Override
public void run(){
System.out.println("A线程");
}
}
class ThreadB extends Thread{
@Override
public void run(){
System.out.println("B线程");
}
}
class ThreadC extends Thread{
@Override
public void run(){
System.out.println("C线程");
}
}
运行结果:
五、线程的状态
学习线程的状态前,我们先学习下进程有哪些状态,它和线程的状态有哪些区别?
进程的状态转换:
创建态–>就绪态:完成了进程的创建,满足计算机性能的要求
就绪态–>运行态:进程被调度
运行态–>就绪态:分配的时间片被用完
运行态–>终止态:进程结束被系统撤销,或者发生错误
运行态–>阻塞态:等待某一事情的发生而发生阻塞
阻塞态—>就绪态:等待事情的操作完成
线程的状态
线程共包括以下 5 种状态:
-
新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
-
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
-
运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
-
阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
-
等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
-
同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
-
其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
终止状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
锁池状态(阻塞队列)
- 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入锁池状态。简言之,锁池里面放的都是想争夺对象锁的线程。
- 当一个线程1被另外一个线程2唤醒时,1线程进入锁池状态,去争夺对象锁。
- 锁池是在同步的环境下才有的概念,一个对象对应一个锁池。
几个方法的比较
- Thread.sleep(long
millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。 - Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
- t.join()/t.join(longmillis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
- obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
- obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。