【Java多线程】线程的基本认识与创建


1、进程和线程的认识

进程:

  • 进程就是计算机正在进行的一个独立的应用程序,进程是一个动态的概念,必须是进行状态,如果一个应用程序没有启动,那就不是一个进程。
  • 进程是程序执行过程中资源分配和管理的基本单位。
  • 进程拥有自己的独立的地址空间,每启动一个进程,系统就会分配地址空间。

线程:

  • 一个程序运行中可以执行多个任务,任务称之为线程。
  • 线程是cpu执行的最小单位。
  • 进程可以拥有多个线程,各个线程之间共享程序的内存空间

为什么出现线程?

每个进程有自己独立的地址空间,多并发请求,为每一个请求创建一个进程导致系统开销、用户请求效率低。

2、多线程和多进程的区别和联系

区别:

  1. 进程是资源分配的最小单位,线程是cpu调度的最小单位
  2. 每个进程拥有自己独有的数据,线程共享数据
  3. 线程之间的通信相比于进程之间的通信更有效,更容易
  4. 线程相比于进程 创建/销毁 开销更小
  5. 多进程程序更加健壮,多线程程序只要有一个线程挂掉,对其共享资源的其他线程也会产生影响

联系:
6. 进程是相互独立,一个进程下可以有一个或者多个线程


进程和线程的使用场景

  • 在程序中,如果需要频繁创建和销毁的使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。
  • 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。

3、并发和并行的区别

  • 并发:指多个线程操作同一个资源,不是同时执行,需要交替执行,单核CPU,因为CPU执行每一个时间片很短,速度太快,看起来是同时执行(张三、李四厨师,使用同一口锅炒菜,交替执行)
  • 并行:多核CPU,每个线程来使用一个单独的CPU的资源来运行(张三、李四厨师,一人一口锅,一起炒菜)

并发编程:指允许多个任务在一个时间段内重复执行的设计结构

相关概念:

  • 高并发:设计的程序,能够执行海量的任务同时执行。
  • QPS:每秒能够响应的请求数。
  • 平均响应时间:并发数 / 平均响应时间 = QPS。
  • 并发用户数:系统可以承载的最大用户量。
  • 吞吐量:单位时间内能够处理的请求数。

互联网系统架构中,如何提高系统的并发能力?

垂直扩展:

提升单机的处理能力。

  1. 增强单机的硬件性能:增加CPU的核数、内存升级、磁盘扩容。
  2. 提升系统的架构能力:使用Cache来提高效率。

水平扩展:

集群、分布式都是水平的扩展方案。

  • 集群:多个人做同一事(例如一个餐厅中同时多顾几个厨师同时炒菜)。
  • 分布式:一个复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成(1.洗菜 2.切菜 3.炒菜)。

具体操作为:

  1. 站点层扩容:通过Nginx反向代理,实现高并发的系统,将服务部署在多个服务器上。
  2. 服务层扩容:通过RPC框架实现远程调用:Dubbo,Spring Clodud,将业务逻辑分拆成不同的RPC Client,每个Clident完成各自的不同的业务,如果并发量比较大,则可以新增加RPC Client。
  3. 数据层扩容:一台数据库拆分成多态,分库分表,主从复制,读写分离。

4、线程的创建

(1)继承Thread类

class WatchTV extends Thread {
    @Override
    public void run() {
        System.out.println("Watch TV");
    }
}

class Eat extends Thread {
    @Override
    public void run() {
        System.out.println("eating");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Thread watchTV = new WatchTV();
        Thread eat = new Eat();
        watchTV.start(); //watchTV();
        eat.start(); //eat();
    }
}

使用继承Thread类创建步骤:
1、自定义类继承Thread(extends Thread),重写run方法
2、实例化自定义的类
3、启动子线程,调用start方法

(2)实现Runnable接口

class WatchTV implements Runnable {
    @Override
    public void run() {
        System.out.println("Watch TV");
    }
}

class Eat extends implements Runnable {
    @Override
    public void run() {
        System.out.println("eating");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Thread watchTV = new Thread(new WatchTV());
        Thread eat = new Thread(new Eat());
        watchTV.start(); //watchTV();
        eat.start(); //eat();
    }
}

实现Runable的创建线程步骤
1、自定义实现一个Runable接口的实现类,并实现run方法
2、实例化自定义的Runable实现类
3、创建Thread类实例,将实例化的Runable实例作为参数传递
4、启动子线程,调用Thread实例的start方法

(3)实现Callable接口

class MyCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i=0; i<10000; i++){
            sum += i;
        }
        return sum;
    }
}

public class TestDemo {
    public static void main(String[] args) {
       Callable<Integer> callableTask = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(callableTask);
        Thread thread = new Thread(task);
        thread.start();

        //接受线程执行之后的结果
        try {
            Integer integer = task.get();
            System.out.println("result: "+integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
}

实现Callable接口的创建多线程的步骤:
1、自定义实现Callable接口的实现类,并实现call方法
2、创建自定义类的实例
3、创建一个FutureTask实例,将自定义实例作为参数传入
4、创建Thread类的实例,将FutureTask的实例作为参数传入
5、启动子线程,代用Thead实例的start方法

 


实现Runable接口和继承Thread类的区别

1、使用继承Thread类(单继承)是不能继承其他类,而Runable方式可以
2、继承Thread类是无法实现资源共享,而实现Runable接口可以实现资源共享
3、实现Runable接口代码更健壮,任务和线程是解耦合的,代码更加清晰,代码和数据是相互独立的


Callable和Runable接口区别

1、Callable类型的任务有返回值,而Runable类型的任务不能有返回值
2、Callable规定的方法是call(),而Runable规定的方法是run()方法
3、call()可以抛出异常,run()方法是不能排除异常的
4、Callable任务可以拿到一个Future对象,Future表示异步计算的结果,他提供了可以检查子线程是否执行完成的方法,可以获取到子线程执行的结果。通过Future对象可以了解和控制子线程的执行。


5、start()方法和run()方法的区别

//start方法剖析
  public synchronized void start() {
      if(threadStatus!=0)//判断当前线程状态是否为0
            throw new IllegalThreadStateException();
      group.add(this); //当前线程加入一个线程
      boolean started=false;
      try{
          start0(); //start0是一个native本地方法 调用run执行该线程
          started=true;
      }finally{
          try{
            if(!started){
                group.threadStartFailed(this);
            }
          }catch(Throwable ignore){
      }
  }
  • start()方法本身是用来启动一个线程,并将其添加到一个线程组里面,这时线程获取CPU资源之后就会执行所定义的run方法的逻辑。
  • 而run()方法本身只是一个普通的方法,并不会启动新的线程。
    在这里插入图片描述

6、守护线程

API描述为:The java virtual machine exits when the only threads running are all daemon threads。 即:当JVM总没有一个非守护线程时,JVM进程会退出。

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程。

与守护线程相对的是用户线程(非守护线程),用户线程可以认为是系统的工作线程,它会完成这个程序要完成的业务员操作。

如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。即:守护线程能够自动结束生命周期,而非守护则不具备这一特点。

例如

我们可以创建一个子线程,并让他持续进行睡眠,如下:

import java.util.concurrent.TimeUnit;

public class TestDemo {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    while (true) {
                        TimeUnit.MILLISECONDS.sleep(1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread Finished");
    }
}

此时该线程为用户线程,即非守护线程,那么当主线程结束时,该程序并不会自动结束。
在这里插入图片描述

此时,如果想当主线程结束时,子线程自动结束,那么就可以将子线程设为守护线程,完整代码如下:

我们可以通过Thread.setDaemon设置守护线程。不过需要注意的是守护线程必须在start之前设置,否则会报错。

import java.util.concurrent.TimeUnit;

public class TestDemo {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    while (true) {
                        TimeUnit.MILLISECONDS.sleep(1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //将子线程变为守护线程
        thread.setDaemon(true);//在线程启动之前去调用
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread Finished");
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值