了解Java之多线程

目录

1.进程的概念

2.线程的概念

3.进程和线程区别

4. 深入线程的概念

5. 线程的运行模式

6. main的主线程

7. Thread类

8. 实现多线程方式—继承Thread

9. 线程执行的随机性

10.为什么要继承Thread

11. 获取线程名字

13. 线程命名

14. Thread类方法sleep

15. 线程让步(yield)

16. join()方法

17.线程停止

18.实现线程方式二—实现Runnable接口

19. 匿名内部类实现线程程序

20.多线程的代码实现异步计算

21.后台线程(守护进程)

22.线程生命周期图


1.进程的概念

进程:os中一个程序的执行周期。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能;程序要想运行,必须占有 CPU 资源

2.线程的概念

线程:线程是进程中的一个执行单元(执行路径),负责当前进程中程序的执行, 一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

3.进程和线程区别

进程(Process):操作系统资源分配的最小单位,每一个进程拥有自己的一整套变量

线程(Thread):操作系统调度的最小单位,每个线程依托于进程的存在,每个线程共享进程的资源

没有进程就没有线程,进程一旦终止,其内线程全部撤销

4. 深入线程的概念

  • 什么是多线程?
    •  就是一个程序中有多个线程在同时执行;CPU在多个线程之间进行着随即切换动作
  • 单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如去 网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网
  • 多线程程序:即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网

多线程是使用场景(优点)

  • 可能提升执行的速度(多一个调度单位(线程),更多的机会抢到 CPU )
  • 创建线程是有成本的,如果创建线程的事件 > 节省的时间,则不值当,不会提升速度
  • 如果操作系统中已经有太多线程(都是自己的线程),再加意义就不大了
  • 某些场景下,必须多线程才能处理,例如有阻塞IO(一个调度单位倒下了,剩余的调度单位还能继续抢占 CPU )

5. 线程的运行模式

  1. 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
  2. 抢占式调度:
  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
  • 对于 CPU 的一个核而言,某个时刻只能够执行一个线程,而 CPU 在多个线程间切换速度很快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高

6. main的主线程

/*
 *  程序中的主线程
 */
public class Demo {
    public static void main(String[] args) {
        System.out.println(0/0);
        function();
        System.out.println(Math.abs(-9));
    }
    public static void function(){
        for(int i = 0 ; i < 10000;i++){
            System.out.println(i);
        }
    }
}

7. Thread类

  • Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程
  • 创建新执行线程有两种方法:
    • 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法
    • 另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程
    • 启动线程一律调用Thread类提供的 start() 方法

如何创建线程?

  1. 创建一个线程对象
  2. 调用该对象的 start() 方法,把线程放到就绪队列中,等待 CPU 的调度

 

8. 实现多线程方式—继承Thread

/*
 * 创建和启动一个线程
 * 创建Thread子类对象
 * 子类对象调用方法start()
 * 让线程程序执行,JVM调用线程中的run
 */
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread st1 = new MyThread();
        MyThread st2 = new MyThread();
        st1.start();
        st2.start();
        for(int i = 0; i < 50;i++){
            System.out.println("main..."+i);
        }
    }
}
/*
       *  定义子类,继承Thread 
       *  重写方法run 
       */
public class MyThread  extends Thread{

    @Override
    public void run(){
        for(int i = 0; i < 50;i++){
            System.out.println("run..."+i);
        }
    }
}
  • start() 方法解析
    • 表示线程的启动
    • 首先检查线程状态是否为0(线程默认的状态为0表示未启动),如果已经启动 start() 方法抛出非受查异常illegalThreadStateException
    • 一个线程的 start() 方法只能调用一次
    • JVM调用 start() 方法进行资源分配与系统调度,准备好资源启动线程后回调 run() 方法类执行线程的具体任务

9. 线程执行的随机性

在多线程中,线程的执行是一个随机性的;

对于单核 CPU 来说,某一时间只能允许一个线程拥有 CPU 资源,那么哪一个线程拥有 CPU 资源来执行自己的代码完全是随机的,只不过对于不同的线程来说会有一个优先级的问题

示例

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("main 主线程执行结束");
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread 线程执行结束");
    }
}
//执行结果
//main 主线程执行结束
//MyThread 线程执行结束

10.为什么要继承Thread

  • 为什么要继承Thread类,并调用其的start方法才能开启线程呢?
    • 继承Thread类:因为Thread类用来描述线程,具备线程应该有功能
  • 为什么不直接创建Thread类的对象呢?
    • 这样做并没有什么错误,但是直接用 Thread 类的实例调用 strat() 方法,调用的是 Thread类中的 run() 方法;并且这个方法中没有做任何事,更重要的是这个 run() 方法中并没有定义我们需要线程执行的代码
  • 多线程执行时,到底在内存中是如何运行的呢?
    • 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈
    • 当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了

11. 获取线程名字

  • Thread类方法 getName():
    • public final String getName()

示例

/*
 *  获取线程名字,父类Thread方法
 *    String getName()
 */
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
/*
 *  每个线程,都有自己的名字
 *  运行方法main线程,名字就是"main"
 *  其他新键的线程也有名字,默认 "Thread-0","Thread-1"
 *  
 *  JVM开启主线程,运行方法main,主线程也是线程,是线程必然就是
 *  Thread类对象
 */

class MyThread extends Thread {

    public MyThread() {
        super("Kobe");
    }
    @Override
    public void run() {
        System.out.println(getName());
    }
}
  • Thread类方法 currentThread
    • public static native Thread currentThread()

示例

/*
 * 获取线程名字,父类Thread方法
 * String getName()
 */
public class Test {
    public static void main(String[] args) {
        MyThread nt = new MyThread();
        nt.start();
        /*Thread t =Thread.currentThread();
   		System.out.println(t.getName());*/
        System.out.println(Thread.currentThread().getName());
    }
}
/*
 *  每个线程,都有自己的名字
 *  运行方法main线程,名字就是"main"
 *  其他新键的线程也有名字,默认 "Thread-0","Thread-1"
 *
 *  JVM开启主线程,运行方法main,主线程也是线程,是线程必然就是
 *  Thread类对象
 *  Thread类中,静态方法
 *  static Thread currentThread()返回正在执行的线程对象
 */

class MyThread extends Thread{

    public MyThread() {
        super("Kobe");
    }
    @Override
    public void run() {
        System.out.println(getName());
    }
}
//执行结果
//main
//Kobe

13. 线程命名

  • 通过构造命名
    • public Thread (string name)
    • public Thread (Runnable target,String name)
  • public final synchronized void setName(String name)

示例

/*
 * 获取线程名字,父类Thread方法
 * String getName()
 */
public class Test {
    public static void main(String[] args) {
        MyThread nt = new MyThread();
        nt.setName("KobeGigi");
        nt.start();
    }
}
/*
 *  每个线程,都有自己的名字
 *  运行方法main线程,名字就是"main"
 *  其他新键的线程也有名字,默认 "Thread-0","Thread-1"
 *
 *  JVM开启主线程,运行方法main,主线程也是线程,是线程必然就是
 *  Thread类对象
 *  Thread类中,静态方法
 *  static Thread currentThread()返回正在执行的线程对象
 */

class MyThread extends Thread{

    public MyThread() {
        super("Kobe");
    }
    @Override
    public void run() {
        System.out.println(getName());
    }
}

14. Thread类方法sleep

  • 线程休眠———静态方法sleep(毫秒)
  • 概念:让线程暂缓执行,等到了预计时间再恢复执行
  • 功能:线程休眠会立即交出CPU,让CPU去执行其他任务。线程休眠不会释放对象锁

示例

public class Test {
    public static void main(String[] args) {
        MyThread nt = new MyThread();
        nt.start();
    }
}
class MyThread extends Thread {

    public MyThread() {
        super("Kobe");
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);//休眠1000ms,500ms已到并且cpu切换到该线程继续向下执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

15. 线程让步(yield)

  • 功能:暂停当前正在执行的线程对象,并执行其他线程
  • yield() 会让当前线程交出CPU,但不一定立即交出yield() 交出CPU后只能让拥有相同优先级的线程有获取CPU的机会,yield() 不会释放对象锁

16. join()方法

  • 功能:等待该线程终止
  • 实例:如果在主线程中调用该方法会让主线程休眠,让调用该方法的线程先执行完毕后再恢复执行主线程

17.线程停止

  • 工设置标志位,让线程在满足条件后退出
  • 使用Thread类提供的 interrupt() 方法中断线程(只是系统设置的一个标志位,直接使用即可
    • interrupt() 法只是将线程状态改为中断状态而已,他不会中断一个正在运行的线程
    • 如果线程调用 wait()、sleep()、join()、 进入阻塞状态,调用该线程的interrupt()会抛出 InterruptedException 并且将线程 interrupt 重置为false

18.实现线程方式二—实现Runnable接口

  • Java 中多线程的处理就是一个典型的代理模式。其中Thread类完成资源调度、系统分配辅助线程业务类自定义的线程类负责真实业务实现
/*
 * 实现接口方式的线程
 * 创建Thread类对象,构造方法中,传递Runnable接口实现类
 * 调用Thread类方法start()
 */
public class Test {
    public static void main(String[] args) {
        MyRunnable sr = new MyRunnable();
        Thread t = new Thread(sr);
        t.start();
        for(int i = 0 ; i < 50; i++){
            System.out.println("main..."+i);
        }
    }
}
/*
 *  实现线程成功的另一个方式,接口实现
 *  实现接口Runnable,重写run方法
 */
class MyRunnable implements Runnable{
    @Override
    public void run(){
        for(int i = 0 ; i < 50; i++){
            System.out.println("run..."+i);
        }
    }
}
  • 实现接口方式的好处
    • ​​​​​​​第二种方式实现Runnable接口避免了单继承的局限性
    • 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务;继承Thread类,线程对象和线程任务耦合在一起
    • 一旦创建Thread类的子类对象,既是线程对象,又有线程任务
    • 实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦
    • (降低紧密性或者依赖性,创建线程和执行任务不绑定)

​​​​​​​19. 匿名内部类实现线程程序

 *  使用匿名内部类,实现多线程程序
 *  前提: 继承或者接口实现
 *  new Thread或者Runnable(){
 *     重写run()方法
 *  }
 */
public class Test {
    public static void main(String[] args) {
        //继承方式  XXX extends Thread{ public void run(){}}
        new Thread(){
            public void run(){
                System.out.println("!!!");
            }
        }.start();

        //实现接口方式  XXX implements Runnable{ public void run(){}}

        Runnable r = new Runnable(){
            public void run(){
                System.out.println("### ");
            }
        };
        new Thread(r).start();
        new Thread(new Runnable(){
            public void run(){
                System.out.println("@@@");
            }
        }).start();

    }
}

20.多线程的代码实现异步计算

class SumThread extends Thread{
    int start = 0;
    int end = 0;
    Object o = new Object();
    SumThread(int start,int end) {
        this.start = start;
        this.end = end;
    }

    int sum = 0;
    @Override
    public void run() {
        for (int i = start; i <= end; i++) {
            synchronized(o){
                sum += i;
            }
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        //这个线程计算 1+++100
        SumThread st1 = new SumThread(1,100);
        //这个线程计算101+++200
        SumThread st2 = new SumThread(101,200);
        
        st1.start();
        st2.start();

        st1.join();
        st2.join();

        System.out.println(st1.sum+st2.sum);
    }
}

21.后台线程(守护进程)

  • 概念:当前JVM进程中存在任何一个用户线程没有结束,后台线程就一直工作;只有当最后一个用户线程停止后,后台线程会随着JVM进程一同停止,后台线程属于陪伴线程
  • 线程的分类
    • 线程分为用户线程守护线程——判断函数 isDaemon()
    • 垃圾回收线程属于守护线程
  • ​​​​​​​设置方式:setDaemon() 将当前线程设置为守护线程
  • Java中启动的线程默认为用户线程,包括main线程

22.线程生命周期图

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值