【04 核心类库 4. 多线程】

4. 多线程

4.1 基本概念

4.1.1 程序和进程的概念

  • 程序 = 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
  • 进程 - 主要指运行在内存中的可执行文件。
  • 目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,
  • 也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。

4.1.2 线程的概念

  • 为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支

    持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。

  • 多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。

4.2 线程的创建(重中之重)

4.2.1 Thread类的概念

  • java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
  • Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

4.2.2 创建方式

  • 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。

  • 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用

    start方法。

4.2.3 相关的方法

方法声明功能介绍
Thread()使用无参的方式构造对象
Thread(String name)根据参数指定的名称来构造对象
Thread(Runnable target)根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target,String name)根据参数指定引用和名称来构造对象
void run()若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本,若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start()用于启动线程,Java虚拟机会自动调用该线程的run方法
//1.使用无参方式构造对象
        //由源码可知:Tread类中的成员变量target的数值为null
        Thread thread=new Thread();
//2.调用run方法进行测试
        //由源码可知:因为成员变量target的数值为null,因此if (target != null)中的条件不成立,{}中的方法不执行
        //run方法中,除上述代码,再无其他方法,因此证明run方法真的啥也没干
        thread.run();

        //3.打印一句话
        System.out.println("run方法啥也不干");
//实现接口		重写run方法
public class SubRunnableRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法中:i="+i);
        }
    }
}

public class SubRunnableRunTest {
    public static void main(String[] args) {
//1.创建自定义类型的对象,也就是实现Runnable接口
        SubRunnableRun srr=new SubRunnableRun();
//2.使用该对象作为实参构造Thread类型的对象
        Thread t1=new Thread(srr);
        //由源码可知:经过构造方法的调用,Thread类中的成员变量target的值为srr(Runnable接口的引用)
//3.使用Thread类型的对象调用start方法
        //此时调用的是Runnable接口中的run方法
        /* public void run() {
                if (target != null) {
                    target.run();
                }
        此时条件成立执行target.run();的代码,也就是srr.run()*/

        t1.start();

        //打印1~20之间的整数
        for (int i = 0; i <= 20; i++) {
            System.out.println("-----main方法中:i="+i);
        }
    }
}

4.2.4 执行流程

  • 执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。

  • main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,

    新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。

  • 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。

  • 两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。

4.2.5 方式的比较

  • 继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类。
  • Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。

4.2.6 匿名内部类的方式

使用匿名内部类的方式来创建和启动线程。

public class ThreadNoNameTest {
    public static void main(String[] args) {
        //匿名内部类的语法格式:父类/接口类型 引用变量名=new 父类/接口类型() {方法的重写};
//1.使用继承加匿名内部类的方法创建并启动线程
        /*Thread t1=new Thread() {
            @Override
            public void run() {
                System.out.println("张三说:在吗");
            }
        };t1.start();*/
        
        //优化以上代码
        new Thread() {
            @Override
            public void run() {
                System.out.println("张三说:在吗");
            }
        }.start();
//2.使用接口加匿名内部类的方法创建并启动线程
        /*Runnable ra=new Runnable() {
            @Override
            public void run() {
                System.out.println("李四说:不在");
            }
        };
        Thread t2=new Thread(ra);
        t2.start();*/
        
   //优化一:
       /* new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("李四说:不在");
            }
        }).start();*/
        
   //优化二:
        //Java8开始支持正则表达式:       () -> {方法的重写};
      /*  Runnable ra=()->{ System.out.println("李四说:不在");};
        new Thread(ra).start();*/

   //优化三:
        new Thread(()->System.out.println("李四说:不在")).start();
    }
}

4.3 线程的生命周期(熟悉)

  • 新建状态 - 使用new关键字创建之后进入的状态,此时线程并没有开始执行。
  • 就绪状态 - 调用start方法后进入的状态,此时线程还是没有开始执行。
  • 运行状态 - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
  • 消亡状态 - 当线程的任务执行完成后进入的状态,此时线程已经终止。
  • 阻塞状态 - 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。

4.4 线程的编号和名称(熟悉)

方法声明功能介绍
long getId()获取调用对象所表示线程的编号
String getName()获取调用对象所表示线程的名称
void setName(String name)设置/修改线程的名称为参数指定的数值
static Thread currentThread()获取当前正在执行线程的引用

案例题目

自定义类继承Thread类并重写run方法,在run方法中先打印当前线程的编号和名称,然后将线程的名称修改为"zhangfei"后再次打印编

号和名称。要求在main方法中也要打印主线程的编号和名称。

public class ThreadIdNameTest extends Thread{
    public ThreadIdNameTest(String name) {
        super(name);    //调用父类的构造方法
    }

    @Override
    public void run() {
        System.out.println("子线程的编号是:"+getId()+",子线程的名称是:"+getName());   //11 Thread-0 阿七
        setName("周扬");
        System.out.println("修改后子线程的编号是:"+getId()+",修改后子线程的名称是:"+getName()); //11  周扬
    }
    public static void main(String[] args) {
        ThreadIdNameTest tit=new ThreadIdNameTest("阿七");
        tit.start();

        //获取当前正在执行线程的引用,当正在执行的线程是主线程,也就是获取主线程的引用
        Thread t1=Thread.currentThread();
        System.out.println("主线程的编号是:"+t1.getId()+",名称是:"+t1.getName());     //1  main
    }
}
public class RunnableIdNameTest implements Runnable {
    @Override
    public void run() {
        //获取当前正在执行线程的引用,也就是子线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("子线程的编号是:" + t1.getId() + ",子线程的名称是:" + t1.getName());//11 阿七
        t1.setName("海棠");
        System.out.println("修改后子线程的编号是:" + t1.getId() + ",名称是:" + t1.getName());//11 海棠
    }

    public static void main(String[] args) {
        RunnableIdNameTest rint = new RunnableIdNameTest();
//        Thread t2=new Thread(rint);
        Thread t2=new Thread(rint,"阿七");    //给子线程名称传参
        t2.start();

        //获取当前正在执行线程的引用,当正在执行的线程是主线程,也就是获取主线程的引用
        Thread t1=Thread.currentThread();
        System.out.println("主线程的编号是:"+t1.getId()+",名称是:"+t1.getName());     //1  main

    }
}

4.5常用的方法(重点)

方法声明功能介绍
static void yield()当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待
static void sleep(times)使当前线程从 Running 放弃处理器进入Block状态, 休眠times毫秒, 再返回到Runnable如果其他线程打断当前线程的Block(sleep), 就会发生InterruptedException。
int getPriority()获取线程的优先级
void setPriority(int newPriority)修改线程的优先级。优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些
void join()等待该线程终止
void join(long millis)等待参数指定的毫秒数
boolean isDaemon()用于判断是否为守护线程
void setDaemon(boolean on)用于设置线程为守护线程

案例题目
编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数,其中线程二负责打印1 ~ 100之间的所有偶数。

在main方法启动上述两个线程同时执行,主线程等待两个线程终止。

//模拟时间
public class ThreadSleepTest extends Thread {

    //声明一个变量来决定是否执行循环的条件
    private boolean flag=true;

    //子类不能抛出更大的异常
    @Override
    public void run() {
        //每隔一秒获取一次系统时间  模拟时间的效果
        while (flag) {
            //获取当前系统时间并转换格式
            Date d1=new Date();
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(sdf.format(d1));

            //睡眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {

        ThreadSleepTest tst=new ThreadSleepTest();
        tst.start();

        //等待五秒再关闭时钟
        System.out.println("主线程开始等待...");
        try {
            //过时的方法不建议使用
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//            tst.stop();
        tst.flag=false;
        System.out.println("主线程等待结束");
    }
}
//设置优先级
public class ThreadPriorityTest extends Thread {
    @Override
    public void run() {
//        System.out.println("子线程的优先级为:"+getPriority());  //5 10
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程中:i="+i);
        }
    }

    public static void main(String[] args) {

        ThreadPriorityTest tpt=new ThreadPriorityTest();
        //设置子线程的优先级
        tpt.setPriority(Thread.MAX_PRIORITY);   //10
        tpt.start();

        //获取当前正在执行线程的引用
        Thread t1=Thread.currentThread();
//        System.out.println("主线程的优先级为:"+t1.getPriority());   //5
        for (int i = 0; i < 10; i++) {
            System.out.println("-----主线程中:i="+i);
        }
    }
}
//等待线程的终止
//新年倒计时
package demo02;

public class ThreadJoinTest extends Thread {

    @Override
    public void run() {
        //模拟倒计时的效果
        System.out.println("倒计时开始");
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("新年快乐");
    }

    public static void main(String[] args) {

        ThreadJoinTest tjt = new ThreadJoinTest();
        tjt.start();

        System.out.println("主线程开始等待");
        try {
        //表示当前正在执行的线程对象等待调用对象,也就是主线程等待子线程
//            tjt.join();
            //表示最多等待5秒
            tjt.join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        System.out.println("终于等到你");
        System.out.println("可惜不是你");
    }
}
//守护线程
public class ThreadDaemonTest extends Thread{
    @Override
    public void run() {
//        System.out.println(isDaemon()?"子线程是守护线程":"子线程不是守护线程");
        //当子线程不是守护线程时,虽然主线程先结束,但是子线程依然会继续执行,直到打印完所有的内容
        //当子线程是守护线程时,主线程结束,子线程也会随之结束
        for (int i = 0; i < 50; i++) {
            System.out.println("子线程中,i="+i);
        }
    }

    public static void main(String[] args) {

        ThreadDaemonTest tdt=new ThreadDaemonTest();
        //必须在线程启动之前设置守护线程
        tdt.setDaemon(true);
        tdt.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("---主线程中,i="+i);
        }
    }
}

案例题目

编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数,其中线程二负责打印1 ~ 100之间的所有偶数。

在main方法启动上述两个线程同时执行,主线程等待两个线程终止。

方式一:

//线程一
public class SubThread1 extends Thread{
    @Override
    public void run() {
        //打印1~100的奇数
        for (int i = 1; i < 100; i+=2) {
            System.out.println("子线程一中,i="+i);
        }
    }
}
//线程二
public class SubThread2 extends Thread{
    @Override
    public void run() {
        //打印1~100的偶数
        for (int i = 2; i < 100; i+=2) {
            System.out.println("-----子线程二中,i="+i);
        }
    }
}
//启动线程
public class SubThreadTest {

    public static void main(String[] args) {

        SubThread1 st1=new SubThread1();
        SubThread2 st2=new SubThread2();

        st1.start();
        st2.start();

        System.out.println("主线程等待中...");
        try {
            st1.join();
            st2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程等待结束");
    }
}

方式二:

//线程一:
public class SubRunnable1 implements Runnable{
    @Override
    public void run() {
        //打印1~100的奇数
        for (int i = 1; i < 100; i+=2) {
            System.out.println("子线程一中,i="+i);
        }
    }
}
//线程二
public class SubRunnable2 implements Runnable{
    @Override
    public void run() {
        //打印1~100的偶数
        for (int i = 2; i < 100; i+=2) {
            System.out.println("-----子线程二中,i="+i);
        }
    }
}
public class SubRunnableTest {
    public static void main(String[] args) {

        SubRunnable1 sr1=new SubRunnable1();
        SubRunnable2 sr2=new SubRunnable2();

        Thread t1=new Thread(sr1);
        Thread t2=new Thread(sr2);

        t1.start();
        t2.start();

        System.out.println("主线程等待中...");
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("等待结束");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

江桥诗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值