Java-多线程

线程


要想学习多线程,就得先知道什么是线程,要想知道线程,就得先知道什么是进程

  • 进程:
    是指正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和资源。
    通过任务管理器看
  • 线程:
    是进程的单个顺序控制流,或者就是说是一个单独执行的路径
    如果一个进程只有一条执行路径,称之为单线程
    如果一个进程有多条执行路径,称之为多线程
    线程是包含在进程中的。
    举例:扫雷,360杀毒软件,百度网盘

何为串行,并行,并发?

  • 1、串行,是指一个程序中所有的任务都是按照先后顺序执行的,在前一个任务还没有处理完的情况下,是不会进行处理下一个任务的。
    举例:理发店只有一个理发师,很多人去理发,就需要排队,就有先后顺序,先等前面的人理完发,再轮到后面的人。
  • 2、并行,是指将任务分给不同的处理器去畜栏里,每一个处理器中的任务再进行串行处理
    举例:火车站上有很多卖票窗口,多个窗口同时卖票,但是呢,针对于某一个窗口来说,是一个接着一个去处理的。
  • 3、并发,是指一个现象,并发需要处理器的支持,比如在处理一个任务的时候,操作系统可以调用资源去处理其他的任务,这个任务并行还是串行都可以
    无论是串行还是并行,都需要处理支持并发。
    举例:假设喝水是一个任务,每个火车站售票员,他再售票的同时也能喝水,这就表示支持并发

思考?JVM启动的时候是单线程还是多线程呢?多线程
  • JVM启动的时候,相当于启动了一个程序,就是启动了一个进程
    其中包含了主线程,垃圾回收线程。
    在启动JVM的时候,最低的要求是需要启动两个线程,所以JVM启动的时候是多线程程序。

创建线程的第一种方式:继承Thread类,重写run方法

  • 1、创建一个自定义类继承Thread类
  • 2、这个继承的类要重写run方法
    1)为什么要重写run方法?
    2)重写run方法后如何使用?
  • 3、根据这个类创建线程对象
  • 4、启动线程

面试题:线程调用start()和run()方法的区别?

  • 单纯地调用run方法仅仅表示的是第一个对象调用普通的方法,所以这里的执行还是按照自上而下顺序执行,所以这里依旧是单线程程序
    要想看到多线程程序的执行效果,就必须换一种方式启动线程。start()
    run方法中仅仅是封装了被线程执行的逻辑代码,但是直接对象调用run方法于普通的方法调用没有任何区别
    start()方法调用,首先做的是启动一个线程,然后再由JVM去调用该线程对象中的run()方法
模拟多线程环境
  • 要想模拟多线程环境,就得创建多个线程对象,然后同时启动
    模拟多线程环境,至少创建2个及以上的线程对象

注意事项

  • 1、启动线程调用的是start()方法
  • 2、线程的调用start()方法先后顺序与今后真正执行的顺序没有影响

线程的逻辑代码

package com.shujia.wyh.day25;
public class MyThread1 extends Thread{
    @Override
    public void run() {
        //写的是线程要执行的逻辑代码
//        System.out.println("数加真好!");
        //一般情况下,线程执行的逻辑代码都是比较耗时并且复杂的,为了模拟这里耗时复杂,我使用循环打印来代替
        for(int i=1;i<=300;i++){
            System.out.println(i);
        }
    }
}

Thread类的基本获取和设置方法

1.设置名字(两种方法)

  • 1、public final void setName(String name)
  • 2、通过构造方法给线程起名字Thread(String name)

获取名字

  • 如何获取一个线程的名字呢?
    public final String getName()
public class MyThreadDemo3 {
    public static void main(String[] args) {
//        MyThread2 t1 = new MyThread2(); //Thread-0
//        MyThread2 t2 = new MyThread2(); //Thread-1
        MyThread2 t1 = new MyThread2("李毅");
        MyThread2 t2 = new MyThread2("小虎");

//        t1.setName("李毅");
//        t2.setName("小虎");

        //启动线程
        t1.start();
        t2.start();
    }
}

线程调度问题

问题引入

  • 假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,
    才可以执行指令。那么Java是如何对线程进行调用的呢?

线程的两种调度模型(Java属于抢占式调度模型)

  • 1、分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 2、抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,

Java使用的是抢占式调度模型。***

获取线程中的优先级方法:

  •     public final int getPriority() 返回此线程的优先级。
    

设置线程优先级的方法:

  • public final void setPriority(int newPriority) 更改此线程的优先级。

最大和最小优先级

public final static int MIN_PRIORITY = 1; 线程可以拥有的最小的优先级
public final static int MAX_PRIORITY = 10; 线程可以拥有的最大的优先级

注意事项:

  • 1、线程的默认优先级是5
  • 2、设置优先级的时候,范围是1-10
  • 3、线程的优先级越高仅仅表示的是获取CPU时间片的机率会高一些,并不能保证一定会先执行。

代码如下:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        MyPriorityThread p1 = new MyPriorityThread();
        MyPriorityThread p2 = new MyPriorityThread();
        MyPriorityThread p3 = new MyPriorityThread();

        //获取线程优先级
//        int py1 = p1.getPriority();
//        System.out.println(py1);        5
//        int py2 = p2.getPriority();
//        System.out.println(py2);        5
//        int py3 = p3.getPriority();
//        System.out.println(py3);        5

        //可以设置线程的优先级
        p1.setPriority(10);
        p2.setPriority(5);
        p3.setPriority(1);

        //设置各个线程的名字
        p1.setName("小花");
        p2.setName("小黑");
        p3.setName("小白");

        p1.start();
        p2.start();
        p3.start();
    }
}

线程加入 public final void join()

  • public final void join()
    线程对象调用该方法的时候,目的是让调用该方法的当前线程先执行完,执行完毕后,再让其他线程执行
    其他没有调用join方法的线程,他们之间还是会抢CPU执行权的。

注意事项:

  • join方法的调用,必须是紧跟着当前线程start()方法后调用,否则不起作用。
public class JoinDemo {
    public static void main(String[] args) {
        MyJoinThread jd1 = new MyJoinThread();
        MyJoinThread jd2 = new MyJoinThread();
        MyJoinThread jd3 = new MyJoinThread();

        //给各个线程取名字
        jd1.setName("无敌小可爱");
        jd2.setName("无敌菠萝怪");
        jd3.setName("无敌香蕉π");

        //注意:join 方法的调用,必须是紧跟着当前线程start()方法后调用的,否则不起作用;
        //启动各个线程
//        try {
//            jd1.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        jd1.start();
                try {
            jd1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        jd2.start();
        jd3.start();
    }
}

线程礼让 public static void yield()

  • 礼让线程的目的是暂停当前正在执行的线程,并让其他线程执行,
    它的作用实际上是为了让线程之间看起来更加和谐,它并不能保证多个线程之间一人一次。
public class MyYieldDemo {
    public static void main(String[] args) {
        //创建多线程环境
        MyYieldThread t1 = new MyYieldThread();
        MyYieldThread t2 = new MyYieldThread();
        MyYieldThread t3 = new MyYieldThread();

        //给线程设置名字
        t1.setName("小白");
        t2.setName("小黑");
        t3.setName("小红");

        t1.start();
        t2.start();
        t3.start();
    }
}

后台线程(守护线程) public final void setDaemon(boolean on)

线程分类

  • 用户线程:我们在学习线程之前,运行起来的一个一个程序中的线程都是用户线程
  • 守护线程:所谓的守护线程,指的是程序运行的时候,在后台提供了一个通用的服务线程,比如说垃圾回收线程,他就是一个守护线程。
    这种线程不一定是要存在的,但是可能程序会出问题。只要程序存在用户线程,程序就不会停止
守护线程的设置:
  • public final void setDaemon(boolean on)

注意事项:

  • 1、守护线程必须在启动之前进行设置
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        //模拟多线程环境
        MyDaemonThread dd1 = new MyDaemonThread();
        MyDaemonThread dd2 = new MyDaemonThread();
        MyDaemonThread dd3 = new MyDaemonThread();

        //给线程付名字
//        dd1.setName("草莓");
//       dd1.setDaemon(true);
        dd2.setName("西瓜");
        dd2.setDaemon(true);
        dd3.setName("菠萝");
        dd3.setDaemon(true);

        //当所有线程都为守护进程时。守护进程会马上死亡,因为它没有需要守护的东西

        //当草莓为线程,其他俩是守护线程时,如果草莓进程结束,那么,西瓜和菠萝也会很快调亡

        //启动线程
//        dd1.start();
        dd2.start();
        dd3.start();
    }
}

休眠线程

  • 模拟真实环境,让线程进入休眠状态
package com.bigdat.java.day26;

public class MySleepThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <= 300; i++) {
            //需求:每打印一次,间隔 1000 毫秒在打印下一个
            //public static void sleep(long millis) throws InterruptedException
            //史当前正在执行的线程已指定的毫秒数暂停
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+":"+i);
        }
    }
}
package com.bigdat.java.day26;
/*
    public static void sleep(long millis)
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        //创建三个进程
        MySleepThread st1 = new MySleepThread();
        MySleepThread st2 = new MySleepThread();
        MySleepThread st3 = new MySleepThread();

        //给进程名字
        st1.setName("窗口一");
        st2.setName("窗口二");
        st3.setName("窗口三");

        //启动线程
        st1.start();
        st2.start();
        st3.start();

    }
}

中断线程

  • 在自定义类中定义每个线程睡眠5秒
public class MyStopTread extends Thread{
    @Override
    public void run() {
        System.out.println("我开始睡觉。。。。");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("睡醒了。。。");
    }
}
  • 然后再测试类中,将正在睡眠的线程唤醒,即打断线程的休眠时间

两种方

  • public final void stop()
    强制打断睡眠,程序停止,这个方法不太好,这个方法已经被弃用
  • public void interrupt()/打断睡眠,提示打断睡眠的错误,run方法中后面的代码继续执行,直到执行完毕
public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopTread myStopTread = new MyStopTread();
        myStopTread.start();

        try {
            Thread.sleep(2000);
//            myStopTread.stop(); //强制打断睡眠,程序停止,这个方法不太好,这个方法已经被弃用
            myStopTread.interrupt(); //打断睡眠,提示打断睡眠的错误,run方法中后面的代码继续执行,直到执行完毕。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

多线程的第二种打开方式-runnable

多线程的实现方式2:实现Runnable接口,实现run方

  • 1、自定义一个类实现Runnable接口
  • 2、实现run方法
  • 3、创建实现Runnable接口类对应的对象
  • 4、借助Thread类创建线程对象,将自定义类作为构造方法的参数传入

主方法代码:

public class MyRunnableDemo1 {
    public static void main(String[] args) {
        //创建实现Runnable接口类对应的对象
        MyRunnable1 myRunnable1 = new MyRunnable1();

        //创建多个线程对象
//        Thread t1 = new Thread(myRunnable1);
//        Thread t2 = new Thread(myRunnable1);

        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        Thread t1 = new Thread(myRunnable1,"小白");
        Thread t2 = new Thread(myRunnable1,"小黑");

        //给线程起名字
//        t1.setName("李毅");
//        t2.setName("小虎");

        //启动线程
        t1.start();
        t2.start();
    }
}

自定义类代码

public class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 300; i++) {
            //由于Runnable接口没有getName()方法,所以在这里无法直接调用获取线程的名字
            //间接调用,
            //Thread类中有一个静态的方法
            // public static Thread currentThread()返回对当前正在执行的线程对象的引用。
//            System.out.println(getName() + ":" + i);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
posted @ 2022-04-10 11:21  a-tao必须奥利给  阅读(38)  评论(0编辑  收藏  举报