首先看一张关于多线程的图
该图转载于http://blog.youkuaiyun.com/evankaka
![]()
看完之后会对多线程的流程有个大概的理解,稍后的学习也可以重新查看该图,进行加深理解
关于线程和进程的概念
进程:将程序加载到内存中,系统为它分配资源后才能执行的程序称之为进程。(是系统进行资源分配和调度的一个独立单位,每个进程都有独立的地址空间)
线程:是进程的一个实体(是CPU调度和分配的基本单位,比进程更小的能独立运行的基本单位.)
多进程是指电脑能同时运行多个程序
多线程是指电脑在一个程序中能同时运行顺序流
线程和进程的状态和相关转换
线程和进程都有五个状态:
创建,就绪,运行,堵塞,终止
- 新建状态:新创建的一个线程对象
- 就绪状态:线程创建后,其他线程调用该线程对象的start()方法。将该线程放入可运行线程池中,等待CPU调度。(线程调用调用start()方法事实上不是把线程转换为运行状态,而是从新建状态转换为就绪状态)
- 运行状态:就绪状态中的线程获得CPU调度,开始该线程程序执行
- 阻塞状态:是线程因为某些原因放弃了CPU使用权,暂时停止运行。只用当程序重新进入就绪状态,才有机会转为运行状态。阻塞状态分为三种:
(1) 等待阻塞:指运行的线程执行了wait()方法,当前线程会放弃对该对象的锁,JVM将该线程放入等待池中(wait会释放该线程所持有的锁)
(2) 同步阻塞:指运行的线程在获取对象的同步锁时,该锁被其他线程占用,JVM会将该线程放入锁池
(3)其他阻塞:运行的线程执行sleep()或join()方法,进入阻塞状态,等待CPU调度,线程重新进入就绪状态(sleep不会释放该线程所持有的锁) - 死亡状态:该线程执行完了,或者调用了某些方法退出了run()方法,结束了生命周期
一 如何实现多线程
实现多线程有两种方式,一种是继承Thread类,一种是实现Runnable接口。推荐使用实现Runable接口的方式
继承Thread类的方式
class Thread1 extends Thread{
private String name;
public Thread1(String name){
this.name=name;
}
@Override
public void run() {
System.out.println(name+"开始运行:");
for(int i=0;i<5;i++){
System.out.println(name+"正在运行:"+i);
}
}
}
public class HelloThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始运行:");
Thread t1 = new Thread(new Thread1("线程A"));
Thread t2 = new Thread(new Thread1("线程B"));
t1.start();
t2.start();
}
}
-
运行结果如下
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程A正在运行:0
线程B正在运行:0
线程A正在运行:1
线程A正在运行:2
线程A正在运行:3
线程A正在运行:4
线程B正在运行:1
线程B正在运行:2
线程B正在运行:3
线程B正在运行:4
在运行一下,得出新的结果
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程A正在运行:0
线程B正在运行:0
线程A正在运行:1
线程B正在运行:1
线程A正在运行:2
线程B正在运行:2
线程A正在运行:3
线程A正在运行:4
线程B正在运行:3
线程B正在运行:4
实现Runnable接口的方式
class Thread1 implements Runnable{
private String name;
public Thread1(String name){
this.name=name;
}
@Override
public void run() {
System.out.println(name+"开始运行:");
for(int i=0;i<5;i++){
System.out.println(name+"正在运行:"+i);
}
}
}
public class HelloThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始运行:");
Thread t1 = new Thread(new Thread1("线程A"));
Thread t2 = new Thread(new Thread1("线程B"));
t1.start();
t2.start();
}
}
-
运行结果如下
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程A正在运行:0
线程B正在运行:0
线程A正在运行:1
线程B正在运行:1
线程A正在运行:2
线程B正在运行:2
线程A正在运行:3
线程B正在运行:3
线程A正在运行:4
线程B正在运行:4
再运行一下
-
main线程开始运行:
线程A开始运行:
线程A正在运行:0
线程A正在运行:1
线程A正在运行:2
线程A正在运行:3
线程A正在运行:4
线程B开始运行:
线程B正在运行:0
线程B正在运行:1
线程B正在运行:2
线程B正在运行:3
线程B正在运行:4
不管是哪种方式,都是通过线程start()方法,将线程置为就绪状态,然后会调用run()方法内的程序块来进行操作。
Thread类事实上也是实现了Runnable接口,我们可以看下Thread类的截图
- 在线程类中定义一个私有领域线程名name的属性,其他线程调用时,通过线程构造器传入,那么该线程将会有指定的线程名
二 线程的调度
java线程存在优先级,优先级较高的线程得到CPU资源更多,CPU优先执行较高的线程对象的任务块
java线程优先级用整数表示,取值范围1~10,Thread类有三个静态常量代表该线程优先级
public final static int MIN_PRIORITY = 1;
//线程具有的最低优先级
public final static int NORM_PRIORITY = 5;
//线程具有的默认优先级
public final static int MAX_PRIORITY = 10;
//线程具有的最高优先级
Thread类还有两个方法类设置和获取线程的优先级
public final void setPriority(int newPriority)
public final int getPriority()
三 线程阻塞状态转化相关概念
线程睡眠
Thread类的sleep(long millis)方法,将运行状态的线程转为其他阻塞状态。millis参数以毫秒为单位,设置睡眠时间。当睡眠结束,转为就绪状态。注意:sleep状态不解锁线程等待
Object类的wait()方法,导致该线程放弃对该对象的锁进行等待阻塞状态,置入等待池。只有其他线程调用该对象notify()方法,或者调用notifyAll()方法才能唤醒,进入锁池(同步阻塞),等待拿到该对象的锁后,才能重新回到就绪状态线程让步
Thread.yield()方法,暂停当前正在执行的线程对象,放入就绪状态,从就绪状态中重新调出相同或更高优先级的线程线程加入
join()方法,在当前线程中调用另一个线程的join()方法,使当前前程进入其他阻塞状态,直到另一个线程运行结束,当前线程才从其他阻塞状态转为就绪状态线程唤醒
Object类中的notify()方法,唤醒在此对象中等待池内的单个线程,将其从等待阻塞状态转为锁池状态,等到拿到对应的锁后转为就绪状态。
Object类中的notifyAll()方法,唤醒此对象中等待池内的所有线程。
四 线程相关的常用函数说明和代码示例
- sleep()
sleep()方法在Thread中的代码块如下
public static native void sleep(long millis) throws InterruptedException;
它允许传入一个毫秒为单位的参数,代表暂停的时间。同时它会抛出一个名为InterruptedException的异常,要求你在使用它的时候捕捉该异常,或者继续抛出该异常
作用:
让当前线程进入其他阻塞(–线程睡眠)状态,让就绪状态的线程有运行机会,该机会不考虑线程优先级。同时,该线程不会解除该线程占用的对象锁,所以其后其他线程如果用到该线程所占用的对象锁时,将无法使用该对象,只能进入同步阻塞状态,进入锁池
示例:
class Thread1 implements Runnable{
private String name;
public Thread1(String name){
this.name=name;
}
@Override
public void run() {
System.out.println(name+"开始运行:");
for(int i=0;i<3;i++){
System.out.println(name+"正在运行:"+i);
try {
System.out.println(name+"进入睡眠状态:");
Thread.currentThread().sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"结束睡眠状态:");
}
System.out.println(name+"进入死亡状态:");
}
}
public class HelloThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始运行:");
Thread t1 = new Thread(new Thread1("线程A"));
Thread t2 = new Thread(new Thread1("线程B"));
t1.start();
t2.start();
}
}
-
输出结果如下
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程A正在运行:0
线程B正在运行:0
线程B进入睡眠状态:
线程A进入睡眠状态:
线程B结束睡眠状态:
线程B正在运行:1
线程B进入睡眠状态:
线程A结束睡眠状态:
线程A正在运行:1
线程A进入睡眠状态:
线程B结束睡眠状态:
线程B正在运行:2
线程A结束睡眠状态:
线程A正在运行:2
线程A进入睡眠状态:
线程B进入睡眠状态:
线程B结束睡眠状态:
线程B进入死亡状态:
线程A结束睡眠状态:
线程A进入死亡状态:
从上面我们可以看到
-
线程A开始运行一个环节后,进入休眠状态。
然后CPU会从就绪状态中调一个线程B,线程B会开始一个环节后进入休眠状态。
在B线程运行完后,就绪状态中没有线程调用。
所以会等到A线程结束后(时间为休眠时间),回到就绪状态,结束了休眠状态。
CPU在继续从就绪状态中调A线程的下一个环节。
如此循环往复
看起来很有规律是吗?因为只有两个线程的缘故,所以两者是循环的,我们加上线程C后,就会得出,它是从就绪状态中调用一个线程,而不是直接承接上一个线程的睡眠状态
下面是新程序的输出结果
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程B正在运行:0
线程B进入睡眠状态:
线程A正在运行:0
线程A进入睡眠状态:
线程C开始运行:
线程C正在运行:0
线程C进入睡眠状态:
线程A结束睡眠状态:
线程A正在运行:1
线程A进入睡眠状态:
线程C结束睡眠状态:
线程B结束睡眠状态:
线程B正在运行:1
线程B进入睡眠状态:
线程C正在运行:1
线程C进入睡眠状态:
线程B结束睡眠状态:
线程B正在运行:2
线程B进入睡眠状态:
线程A结束睡眠状态:
线程A正在运行:2
线程C结束睡眠状态:
线程A进入睡眠状态:
线程C正在运行:2
线程C进入睡眠状态:
线程B结束睡眠状态:
线程B进入死亡状态:
线程A结束睡眠状态:
线程A进入死亡状态:
线程C结束睡眠状态:
线程C进入死亡状态:
我们可以重新看到线程的无序状态,CPU每次调用进程中的线程,都可能得出的线程执行顺序是不同
2.object对象的wait()方法
object对象的wait(),notify()和notifyAll()都需要放在同步代码块synchronized(object){}当中,object表示当前线程锁定的对象
作用:线程在获得对象锁后,主动释放对象锁,同时自身线程进入等待状态。只有当其他线程调用notify()或者notifyAll()方法时,才会唤醒
class Thread1 implements Runnable {
private Object o1;
private String name;
public Thread1(String name,Object o) {
this.name = name;
this.o1=o;
}
@Override
public void run() {
System.out.println(name + "开始运行:");
synchronized(o1){
try {
System.out.println(name + "进入等待状态:释放了对o1的锁的控制");
o1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "结束等待状态,进入锁池,等待对象解锁后进入就绪状态");
}
for(int i=0;i<3;i++){
System.out.println(name+"正在运行:"+i);
}
System.out.println(name + "进入死亡状态:");
}
}
class Thread2 implements Runnable {
private Object o1;
private String name;
public Thread2(String name,Object o) {
this.name = name;
this.o1=o;
}
@Override
public void run() {
System.out.println(name + "开始运行:");
//让唤醒其他线程的线程先休眠,让其他线程进入等待池后再唤醒,避免该线程进行唤醒行为后,
//再有其他线程进入等待池后,该线程无法将其唤醒的情况发生
try {
Thread.currentThread().sleep(1*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1){
o1.notifyAll();
System.out.println(name +"唤醒了o1对象等待池中的所有线程");
}
}
}
public class HelloThread {
private static Object o1 = new Object();
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "线程开始运行:");
Thread t1 = new Thread(new Thread1("线程A",o1));
Thread t2 = new Thread(new Thread1("线程B",o1));
Thread t3 = new Thread(new Thread2("线程C",o1));
t1.start();
t2.start();
t3.start();
}
}
-
运行结果:
-
main线程开始运行:
线程A开始运行:
线程C开始运行:
线程B开始运行:
线程A进入等待状态:释放了对o1的锁的控制
线程B进入等待状态:释放了对o1的锁的控制
线程C唤醒了o1对象等待池中的所有线程
线程B结束等待状态,进入锁池,等待对象解锁后进入就绪状态
线程B正在运行:0
线程A结束等待状态,进入锁池,等待对象解锁后进入就绪状态
线程A正在运行:0
线程B正在运行:1
线程B正在运行:2
线程B进入死亡状态:
线程A正在运行:1
线程A正在运行:2
线程A进入死亡状态:
3.join()方法
这是Thread类关于join方法的代码块,它将抛出InterruptedException 异常
public final void join() throws InterruptedException {
作用:在A线程中调用B线程的join()方法,那么A线程将进入其他阻塞状态,等待B线程终止运算才能继续运行
应用场景:主线程中生成子线程,子线程如果需要大量运算,那么主线程极有可能在子线程结束前已经结束。如果主线程中需要用到子线程运算后的结果,那么需要你在主线程中调用子线程的join()方法,让主线程进入阻塞状态,等到子线程运算完了,主线程在继续执行接下来的步骤
代码示例
class Thread1 implements Runnable {
private String name;
public Thread1(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "开始运行:");
for(int i=0;i<3;i++){
System.out.println(name+"正在运行:"+i);
}
System.out.println(name + "结束运算:");
}
}
public class HelloThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "线程开始运行:");
Thread t1 = new Thread(new Thread1("线程A"));
Thread t2 = new Thread(new Thread1("线程B"));
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程结束运行:");
}
}
-
运行结果
-
main线程开始运行:
线程A开始运行:
线程B开始运行:
线程A正在运行:0
线程B正在运行:0
线程A正在运行:1
线程B正在运行:1
线程A正在运行:2
线程B正在运行:2
线程A结束运算:
线程B结束运算:
main线程结束运行:
如果将join方法注释掉,那么运行结果会如下
-
main线程开始运行:
main线程结束运行:
线程B开始运行:
线程B正在运行:0
线程B正在运行:1
线程B正在运行:2
线程B结束运算:
线程A开始运行:
线程A正在运行:0
线程A正在运行:1
线程A正在运行:2
线程A结束运算:
4.yield()方法
这是Thread类关于yield方法的代码块,
public static native void yield();
作用:暂停当前正在运行的线程,让它回到就绪状态,在从就绪状态中相同优先级甚至更高优先级的线程得到运行机会
缺陷:有可能当前线程回到就绪状态后,还没给其他相同优先级运行机会,自己又抢得了CPU的调度,进行接下来的运算
class Thread1 implements Runnable {
private String name;
public Thread1(String name) {
this.name = name;
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(name+"正在运行:"+i);
if(i==40){
Thread.currentThread().yield();
System.out.println(name+"让步了");
}
}
}
}
public class HelloThread {
public static void main(String[] args) {
Thread t1 = new Thread(new Thread1("线程A"));
Thread t2 = new Thread(new Thread1("线程B"));
t1.start();
t2.start();
}
}
-
运行结果有两种,
一种是线程让步后,让另一个线程得到了运行机会
- 部分结果
线程B正在运行:39
线程A正在运行:37
线程B正在运行:40
线程A正在运行:38
线程B让步了
线程A正在运行:39
线程B正在运行:41
线程A正在运行:40
线程B正在运行:42
线程B正在运行:43
线程A让步了
线程B正在运行:44
线程A正在运行:41
线程B正在运行:45
线程A正在运行:42
-
一种是线程让步后,结果回到就绪状态后自己又得到CPU调度,再次运行
- 部分结果
线程B正在运行:30
线程A正在运行:40
线程B正在运行:31
线程B正在运行:32
线程B正在运行:33
线程B正在运行:34
线程B正在运行:35
线程B正在运行:36
线程B正在运行:37
线程B正在运行:38
线程B正在运行:39
线程B正在运行:40
线程A让步了
线程A正在运行:41
线程A正在运行:42
线程A正在运行:43
线程B让步了
线程B正在运行:41
线程B正在运行:42
线程B正在运行:43
线程B正在运行:44
线程B正在运行:45
五 关于线程函数的不同之点
- sleep()和wait()
(1).sleep()方法执行后,线程进入其他阻塞(睡眠)状态,期间对对象的锁并不会解开
wait()方法执行后,线程进入等待阻塞状态,期间对对象的锁会解开
(2)线程阻塞后,sleep()方法会在指定时间结束后转为就绪状态
wait()方法必须在经过其他线程调用notify()或notifyAll()方法唤醒后,转到锁池,等到得到对象的锁后,才会转为就绪状态
(3)wait()是Object类的方法,和notify()和notifyAll()需要在synchronized(object)同步代码块中才能使用,是根据对象的方式
sleep()是Thread类的方法,任何时候都可以用,是根据线程的方式 - sleep()和yield()
(1)sleep()在指定的时间内不可能再次获得执行机会
yield()在让步后,可能会再次获得执行机会
(2)sleep()不考虑优先级问题
yield()会考虑优先级问题
(3)sleep()在运行后,线程进入其他阻塞(睡眠)状态,等待时间结束才会转为就绪状态
yield()会直接转为就绪状态
结尾:这是我学习过程的一些总结,借鉴其他帖子,借鉴其他书后写出来的,例子可能不完美,语句也可能有误。主要用于以后面试初步复习的一个路径,可能接下来会重新修缮下,添加关于线程的其他知识点
1万+

被折叠的 条评论
为什么被折叠?



