一、多线程到底是什么?
1.1、在生活中
- 对于一个人而言,可以一边看电影,一边吃爆米花
- 对于一个程序员而言,可以一边敲代码,一边听音乐
- 对于一对热恋的情侣而言,可以手牵着手肩并着肩,喝着红酒吃着炸鸡,聊着人生,漫步人生路
1.2、在程序中
多线程可以让程序具备多个并发的执行操作,类似于团队协作,多个人合作完成一个任务,这样一来可以大大提高效率,提高资源的利用率
二、线程的生命周期,从生到死,我这一生都经历了什么?
线程是存在生命周期的。线程的生命周期分为5中不同的状态,由始至终分别是:
2.1、新建状态
处于新建状态中的线程对象,并不是一个独立的线程,无法运行,只有当被触发start方法时才会进入准备状态。新建状态是线程生命周期中的第一个状态,也是初始状态
2.2、准备状态
处于新建状态的线程对象,被调用了start方法,将进入准备状态;处于准备状态的线程随时都可能被系统选中进入运行状态,从而执行线程;可能同时有多个线程处于准备状态,然而究竟哪一个线程将进入运行状态,这是不确定的;
被阻塞的线程再次被唤醒时,并不会进入运行状态,而是进入准备状态,等待运行时机;
2.3、运行状态
处于准备状态中的线程一旦被系统选中,获得了运行时机,就会进入运行状态;在运行状态中,程序将执行线程类中run方法中的语句块;处于运行状态的线程,可以随时被设置为阻塞状态;
在单核CPU中,同一时刻只能有一个线程处于运行状态。在多核CPU中就可以多个线程同时处于运行状态,这也是多核CPU运行速度快的原因
2.4、等待(阻塞)状态
Java中提供了许多线程调度的方法,包括睡眠、阻塞、挂起和等待,使用这些方法都会将处于运行状态的线程调度到阻塞(等待)状态。处于阻塞状态的线程被解除后,并不会直接进入运行状态,而是进入准备状态,等待运行时机
2.5、死亡状态
当程序的run方法执行结束后,或程序发生异常终止运行后,线程会进入死亡状态。
三、如何让自己的程序实现多线程?
3.1、如何创建线程对象?
3.1.1、继承Thread类
继承Thread类的子类具备了多线程的能力,重写Thread类的run方法,当线程被启动的时候,run方法中的程序就成为了一条独立的执行线程
示例:
public class NumberThread extends Thread {
public CalThread(String name){
setName(name); //给当前线程取名
}
/**
* 线程start()方法操作的方法
*/
@Override
public void run() {
for(int i=0; i<100; i++){
String name = getName(); //获取当前线程的名称
System.out.println(name + "==" + i);
}
}
}
@Test
public void test1(){
NumberThread cal = new NumberThread("老司机线程");
cal.start(); //启动线程
}
3.1.2、实现Runnable接口
实现Runnable接口,需要实现Runnable接口中提供的run方法,当线程被启动的时候,run方法中的程序就成为了一条独立的执行线程
示例:
public class NumberThread implements Runnable {
private String name;
public NumberThread(String name){
this.name = name;
}
/**
* 线程start()方法操作的方法
*/
@Override
public void run() {
for(int i=0; i<100; i++){
System.out.println(name + "==" + i);
}
}
}
@Test
public void test2(){
NumberThread number = new NumberThread("老司机线程");
Thread t = new Thread(number);
t.start(); //启动线程
}
3.2、如何启动线程?
Java中对于线程后,可以保证的只是让每个线程都启动,并且会执行结果,但是无法决定多个线程中哪个先执行,哪个后执行
四、如何控制(调度)线程?
4.1、睡眠的方法
当线程处于运行状态时,调用sleep方法将使线程从运行状态进入阻塞状态,从而使程序中断运行。该方法是使正在运行的线程让出CPU最简单的方法之一
示例:
public class NumberThread implements Runnable {
private String name;
public NumberThread(String name){
this.name = name;
}
/**
* 线程start()方法操作的方法
*/
@Override
public void run() {
for(int i=0; i<5; i++){
System.out.println(name + "==" + i);
try {
Thread.sleep(1*1000); //让线程睡眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test2(){
NumberThread number = new NumberThread("老司机线程");
Thread t = new Thread(number);
t.start(); //启动线程
}
4.2、让步的方法
Yield方法可以使当前正在运行的线程让出当前CPU,使线程由运行状态回到准备状态,让其他线程有进入运行状态的机会;然而将CPU让给哪一个线程是不确定的
示例:
public class YieldThread extends Thread {
public YieldThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 30) {
yield();
}
}
}
}
public static void main(String[] args) {
YieldThread yt1 = new YieldThread("张三");
YieldThread yt2 = new YieldThread("李四");
yt1.start();
yt2.start();
}
第一种情况:李四线程当执行到第30时会让出CPU时间,这时张三线程会抢到CPU时间并执行
第二种情况:李四线程执行到第30时会让出CPU时间,又重新抢到了CPU时间
五、综合案例:费玉清去健身房
/**
* 费玉清
*
* @author qin
*/
public class FeiYuqing extends Thread {
@Override
public void run() {
int race = 100;
while(race > 0){
try {
int x = (int) (Math.random()*20);
if(race - x >= 0){
race = race - x;
sleep(1*1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("¥费玉清还有"+race+"米就逃跑成功啦");
}
System.out.println("**********费玉清逃跑成功");
}
}
/**
* 大猩猩
*
* @author qin
*/
public class Chimpanzee extends Thread {
@Override
public void run() {
int race = 100;
while(race > 0){
try {
int x = (int) (Math.random()*20);
if(race - x >= 0){
race = race - x;
sleep(1*1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("!大猩猩还有"+race+"米就抓到费玉清啦");
}
System.out.println("**********大猩猩抓到了费玉清,祝你们幸福");
}
}
/**
* 健身房
*
* @author qin
*/
public class Gym {
public static void main(String[] args) {
FeiYuqing fei = new FeiYuqing();
fei.start(); //费玉清跑
Chimpanzee chi = new Chimpanzee();
chi.start(); //大猩猩追
}
}