Java 多线程(超详细讲解)上篇


多线程可以使程序在同一时间内执行多个操作,采用Java中的多线程机制可以使计算机资源得到更充分的利用,多线程技术在网络编程中有广泛的应用。

一、进程与线程

进程是程序的一次动态执行过程,它是从代码加载、执行中到执行完毕的一个完整过程,也就是进程本身从产生、发展到最终消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用中央处理器资源,或者共享操作系统的其他资源。由于CPU执行速度非常快,所有程序好像在“同时”运行一样。
在操作系统中可以多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程,进程是系统运行程序的最小单元。各进程之间是独立的,每个进程的内部数据和状态也是完全独立的。
线程是进程中执行运算的最小单位,是在进程基础上的进一步划分,一个线程可以完成一个独立的顺序控制流程。
与进程不同,同一进程内的多个线程共享同一块内存空间(包括代码空间、数据空间)和一块系统资源,所以系统在产生一个线程或在各线程之间切换工作,其负担要比在进程间切换小得多。

二、线程的优势

多线程作为一种多任务并发的工作方式,有着广泛的应用。合理使用线程,将减少开发和维护的成本,甚至可以改善复杂应用程序的性能。使用多线程的优势如下:

  1. 充分利用CPU的资源
  2. 简化编程模型
  3. 良好的用户体验

三、多线程编程

在Java语言中,实现多线程的方式有两种:一种是继承Thread类,另一种是实现Runnable接口。

1、Thread类介绍

Thread类提供了大量的方法来控制和操作线程。

方法描述类型
Thread()创建Thread对象构造方法
Thread(Runnable target)创建Thread对象,target为run()方法被调用的对象构造方法
Thread(Runnable target,String name)创建Thread对象,target为run()方法被调用的对象,name为新线程的名称构造方法
void run()执行任务操作的方法实例方法
void start()使该线程开始运行,JVM将调用该线程的run()方法实例方法
void sleep(long millis)在指定的毫秒数内让当前正在运行的线程休眠(暂停运行)静态方法
Thread currentThread()返回当前线程对象的引用静态方法

Thread类的静态方法currentThread()返回当前线程对象的引用。在Java程序启动时,一个线程立即随之启动,这个线程通常被称为程序的主线程。
Thread类的重要性:

  1. 主线程是产生其他子线程的线程
  2. 主线程通常必须最后完成运行,因为它执行各种关闭动作
    示例:
public class MainThreadTest {
    public static void main(String[] args) {
        Thread t=Thread.currentThread();
        System.out.println("当前线程:"+t.getName());
        t.setName("MainThread");
        System.out.println("当前线程:"+t.getName());
    }
}

以上示例中,使用Thread currentThread()方法获取当前线程,即主线程的Thread对象。在Thread中定义了name属性,记录线程名称,可以使用setName()方法或getName()方法对线程名称进行赋值和取值操作。

继承Thread类创建线程类

继承Thread类是实现线程的一种方式。在使用此方法自定义线程类时,必须在格式上满足如下要求:

  • 此类必须继承Thread类
  • 将线程执行的代码写在run()方法中

语法结构:

//继承Thread类的方式创建自定义线程类
public class MyThread extends Thread{
	//重写Thread类中的run()方法
	public void run(){
		//线程执行任务的代码
	}
}

示例:

public class WorkThread extends Thread{
    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");
    }

}
class ThreadTest{
    public static void main(String[] args) {
        WorkThread workThread=new WorkThread();
        workThread.setName("果农A");
        workThread.start();
        WorkThread workThread2=new WorkThread();
        workThread2.setName("果农B");
        workThread2.start();
    }
}

运行结果:

果农B开始采摘苹果树:
果农A开始采摘苹果树:
果农B进度:第1颗
果农A进度:第1颗
果农B进度:第2颗
果农A进度:第2颗
果农A进度:第3颗
果农B进度:第3颗
果农B进度:第4颗
果农A进度:第4颗
果农B进度:第5颗
果农A进度:第5颗
果农B已完成采摘任务!
果农A已完成采摘任务!

从以上示例可以看出,两个线程对象调用start()方法启动后,每个线程都会独立完成各自的线程操作,相互之间没有影响并行运行。

实现Runnable接口创建线程类

Runnable接口位于java.lang包中,其中只提供一个抽象方法run()的声明,Thread类也实现了Runnable接口。使用Runnable接口时离不开Thread类,这是因为它要用Thread类中的start()方法。在Runnable接口中只有run()方法,其他操作都要借助于Thread类。

语法结构:

//实现Runnable接口方式创建线程类
class MyThread implements Runnable{
	public void run(){
		//这里写线程内容
	}
}
//测试类
public class RunnableTest{
	public static void main(String[] args){
		//通过Thread类创建线程对象
		MyThread myThread=new MyThread();
		Thread thread=new Thread(myThread);
		thread.start();
	}
}

在上面的代码中,MyThread类实现了Runnable接口,在run()方法中编写线程所执行的代码。如果MyThread还需要继承其他类(如Base类),也完全可以实现。关键代码如下:

class MyThread extends Base implements Runnable{
	public void run(){
	//线程执行任务的代码
	}
}

示例:

public class OneGroupThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName() + "已完成任务!");
    }
}

class TwoGroupThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");
        for (int i = 0; i < 7; i++) {
            System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName() + "已完成任务!");
    }
}

class Test {
    public static void main(String[] args) {
        OneGroupThread thread = new OneGroupThread();
        TwoGroupThread thread2 = new TwoGroupThread();
        thread.setName("一组阿哲");
        thread.start();
        thread2.setName("二组洋洋");
        thread2.start();
    }
}

    }
}

运行结果:

一组阿哲开始为苹果树剪枝
二组洋洋开始为苹果树剪枝
一组阿哲进度:第1颗
二组洋洋进度:第1颗
二组洋洋进度:第2颗
一组阿哲进度:第2颗
二组洋洋进度:第3颗
二组洋洋进度:第4颗
一组阿哲进度:第3颗
二组洋洋进度:第5颗
一组阿哲进度:第4颗
二组洋洋进度:第6颗
二组洋洋进度:第7颗
一组阿哲进度:第5颗
二组洋洋已完成任务!
一组阿哲已完成任务!

2、线程调度相关方法

方法描述
int getPriority()返回线程的优先级
void setPrority(int newPriority)更改线程的优先级
boolean isAlive()测试线程是否处于活动状态
void join()进程中的其他线程必须等待该线程终止后才能运行
void interrupt()中断线程
void yield()暂停当前正在执行的线程类对象并运行其他线程

1、线程的优先级

范围是1~10;可以使用数字也可以使用以下三个静态常量

  1. MAX_PRIORITY:其值是10,表示优先级最高。
  2. MIN_PRIORITY:其值是1,表示优先级最低。
  3. NORM_PRIORITY:其值是5,表示普通优先级,也就是默认值。

优先级并不代表永远是该线程一直有运行的机会,而是,会获得更多的运行机会,优先级低,也是会获得运行机会的。

示例:

public class WorkThread2 implements Runnable{
    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗,优先级:"+Thread.currentThread().getPriority());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");
    }

}
class RunnableTest{
    public static void main(String[] args) {
        WorkThread2 work1=new WorkThread2();
        Thread t1=new Thread(work1,"果农A");
        WorkThread2 work2=new WorkThread2();
        Thread t2=new Thread(work2,"果农B");
        WorkThread2 work3=new WorkThread2();
        Thread t3=new Thread(work3,"果农C");
//        Thread t1=new Thread(new WorkThread2());
//        Thread t2=new Thread(new WorkThread2());
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.NORM_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

果农A开始采摘苹果树:
果农B开始采摘苹果树:
果农B进度:第1,优先级:1
果农C开始采摘苹果树:
果农A进度:第1,优先级:10
果农C进度:第1,优先级:5
果农A进度:第2,优先级:10
果农C进度:第2,优先级:5
果农B进度:第2,优先级:1
果农A进度:第3,优先级:10
果农C进度:第3,优先级:5
果农B进度:第3,优先级:1
果农A进度:第4,优先级:10
果农B进度:第4,优先级:1
果农C进度:第4,优先级:5
果农A进度:第5,优先级:10
果农B进度:第5,优先级:1
果农C进度:第5,优先级:5
果农C已完成采摘任务!
果农B已完成采摘任务!
果农A已完成采摘任务!

2、线程的强制运行

在线程操作中,可以使用join()方法让一个线程强制运行。在线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续运行。它有三个重载方法,定义如下:

public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)

注意:调用join()方法需要处理InterruptedException异常。

示例:

public class JoinThread implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");
        }
    }
}
//测试类
class Test3 {
    public static void main(String[] args) {
        //创建子线程并启动
        Thread t=new Thread(new JoinThread(),"大型商超");
        t.start();
        Thread.currentThread().setName("果商");//修改主线程名称
        //正常采购
        for (int i = 0; i < 10; i++) {
            if (i==5){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");
        }
    }
}

运行结果:

果商采购进度:第1车
大型商超采购进度:第1车
果商采购进度:第2车
大型商超采购进度:第2车
果商采购进度:第3车
大型商超采购进度:第3车
大型商超采购进度:第4车
果商采购进度:第4车
果商采购进度:第5车
大型商超采购进度:第5车
大型商超采购进度:第6车
大型商超采购进度:第7车
大型商超采购进度:第8车
大型商超采购进度:第9车
大型商超采购进度:第10车
果商采购进度:第6车
果商采购进度:第7车
果商采购进度:第8车
果商采购进度:第9车
果商采购进度:第10

在以上代码中,创建子线程类对象t代表大型商超,主线程代表果商。当向果商供应了五车水果后,改为向大型商超集中供应。在代码中,程序开始运行后,代码果商的主线程和代表大型商超的子线程交替运行。当条件满足后,执行t.join()方法,子线程会夺得CPU使用权,优先运行,子线程全部运行完毕后,代表果商的主线程恢复运行。

3、线程的礼让

当一个线程在运行中执行了Thread类的yield()静态方法后,如果此时还有相同或更高优先级的其他线程处于就绪状态,系统将会选择其他相同或更高优先级的线程运行,如果不存在这样的线程,则该线程继续运行。

注意:使用yield()方法实现线程礼让只是提供一种可能,不能保证一定会实现礼让,因为礼让的线程处于就绪状态时,还有可能被线程调度程序再次选中。

示例:

public class ChildThread implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            Thread.yield();//线程礼让
            System.out.println(Thread.currentThread().getName() + "品尝:第" + (i + 1) + "块");
        }
    }
}

class Test4 {
    public static void main(String[] args) {
        Thread t1=new Thread(new ChildThread(),"Child1");
        Thread t2=new Thread(new ChildThread(),"Child2");
        Thread t3=new Thread(new ChildThread(),"Child3");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

Child3品尝:第1Child2品尝:第1Child1品尝:第1Child2品尝:第2Child3品尝:第2Child3品尝:第3Child3品尝:第4Child1品尝:第2Child3品尝:第5Child2品尝:第3Child1品尝:第3Child2品尝:第4Child1品尝:第4Child2品尝:第5Child1品尝:第5

可以看出执行Thread.yield()方法之后,多线程交替运行较为频繁,提高了程序的并发性。

注意:
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程,两者的区别如下:
1.sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会。
2.yield()方法只会将运行机会让给相同优先级或更高优先级的线程。
3.调用sleep()方法需处理InterruptedException异常,而调用yeild()方法无此要求。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NoloveisGod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值