Java 多线程 在哪里使用 基本创建方式 图片+文字+代码 详解!!!

多线程

概念

程序:

是为了完成特定任务,用某种语言编写的一组静态代码,静态对象

进程:

是一个程序的一次执行过程,或是正在运行的程序,是一个动态的过程,存在生命周期。一个进程可以有一个或多个线程,进程作为内存资源分配的单元,系统在运行时会为每个进程分配不同的内存空间。

线程:

线程是进程细化的概念,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,那么其称之为 — 多线程

线程作为执行和调度的单位,每一个线程,拥有独立的运行栈和程序计数器(PC),线程切换的开销小

请添加图片描述

一个进程内的多个线程共同分享进程中的内存空间,他们从同一个堆空间中分配对象,他们都可以访问,修改该进程中的变量和对象,这使得多个线程之间可以很方便的使用变量和对象,但是这也造成了线程的不安全


并行:多个CPU同时执行多个任务 例如:一起考试

并发:多个CPU同时执行相同任务 例如:商品抢购


多线程的优缺点以及使用场景

缺点:

执行多个任务,单线程快于多线程

优点:

提高应用程序响应,增强用户体验

提高计算机CPU利用率

改善程序结构,可以将一个长且复杂的线程分化为很多简单的线程,易于理解与修改

使用场景:

程序需要多个线程一起执行

执行一些需要等待的任务时,比如 加载,下载,网络请求,文件读写等

程序需要后台运行时


线程的创建方式

1.继承的方式

创建一个继承于Thread类的子类
重写其中run方法
创建其对象
执行该对象的.start( )方法
public class MultiThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        new Thread(){
            @Override
            public void run() {
                System.out.println("匿名子类重写的run方法");
            }
        }.start();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("重写的run方法");
    }
}

tips:如果该线程中的方法只使用一遍,比较多的采用匿名类的方式。想要执行多线程,必须使用Thread的start( )方法。

Thread.start( )方法的作用:1.启动该线程(主线程),2.调用该线程的run方法(子线程)。如果直接调用run方法,虽然程序还是可以正常执行,但是还是在主线程中(造对象,调用对象的方法,没有分出线程)


2.实现Runnable接口

创建一个实现类去实现Runnable接口
实现其中的run( )方法
创建该实现类的对象
将该对象填入Thread的构造器构造出Thread对象
最后通过Thread对象调用其中的start( )方法开启该线程,并执行该线程中的run( )方法
public class MultiThreadTest {

    public static void main(String[] args) {
        //创建实现类对象
        MyThread myThread = new MyThread();
        //将此对象填入Thread类的构造器中,创建Thread对象
        Thread thread = new Thread(myThread);
        //利用start()方法启动该线程,并调用该线程的run()方法
        thread.start();
    }
}

//创建Runnable的实现类
class MyThread implements Runnable{
    //实现Runnalbe其中run方法
    @Override
    public void run() {
        System.out.println("重写Runnable接口中的run方法");
    }
}

为什么start( )方法中说明的是,启动该线程并实现该线程(Thread)的run( )方法。但是上述代码中的MyThread类中并没有重写Thread中的run( )方法,而是实现了接口Runnable中的run( )方法,这是怎么调用到的呢?

Thread源码:

	//如果Thread类中的run没有被重写,执行的是target的run方法
	@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

	//构造器中填入Runnable的实现类,给target赋值
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

由Thread代码可知,Thread 类中的run( )方法再没有被重写时,执行的是target的run( ),又由构造器得知,这个target实际上就是一个Runnable接口的实现类。简而言之,如果Thread类中的run( )方法没有被重写,那么执行的就是Thread类构造器中Runnable接口实现类的run( )方法。

当然如果重写了Thread类中的run( )方法,那执行的一定是被重写过的(线程创建方式一)


3.实现Callable接口

创建Callable的实现类
完成Callable接口中的call( )方法
创建该实现类的对象
把对象作为参数传入FutureTask类的构造器 创建FutureTask类的对象
把FutureTask类的对象作为参数填入Thread类的构造器,创建Thread对象并使用start()开启该线程
*Callable接口中的call( )方法允许返回值,如果需要返回值,可以用FutureTask类的对象的get( )方法接收其返回值
public class CallableTest {
    
//创建Callable的实现类
class MyThread implements Callable{
//重写接口中的call方法
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 20 ; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
}
    
    public static void main(String[] args) {
        //创建Callable接口的实现类对象
        MyThread myThread = new MyThread();
        //用callable实现类的对象创建FutureTask接口的对象
        FutureTask futureTask = new FutureTask(myThread);
        //新建线程并start
        new Thread(futureTask).start();

        //使用FutureTask类的对象的get()方法 接收call()方法的返回值
        try {
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
      }
   }
}

4.线程池 一般使用

使用工具类Executors的newXXXThreadPool()的方法创建ExecutorService接口的实现类对象
Executors.newCachedThreadPool(); 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor(); 创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(); 创建一个线程池,可以设置给定时间后运行或定期执行
用ExecutorService接口的实现类对象的execute()或submit()方法执行线程 其中execute( )方法适用于实现Runnable接口的线程,而submit( )适用于实现Callable接口的线程,submit( )方法拥有返回值,返回值为FutureTask<>
使用结束调用shutdown( )方法关闭线程池连接
public class ThreadPoolTest {
    public static void main(String[] args) {
        
        //Executors.newXXXThreadPool()的方式创建ExecutorService接口的实现类对象
        //这里newFixedThreadPool固定创建了10个线程大小的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //用ExecutorService接口的实现类对象的execute()或submit()方法执行线程
        //.execute()方法适用于Runnable接口的实现类对象
        service.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        });

        //.submit()方法适用于Callable接口的实现类对象 因为Callable接口有返回值
        // 使得submit()方法也有返回值,可以选择不接收
        FutureTask<Integer> futureTask = (FutureTask<Integer>) service.submit(new Callable() {
            @Override
            public Integer call() throws Exception {
                return 15;
            }
        });

        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //关闭线程池连接
        service.shutdown();
    }
}

tips:线程池的管理函数(用ExecutorService接口的实现类对象调用 ,常见如ThreadPoolExecutor)

ThreadPoolExecutor s = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); //创建ExecutorService接口的实现类对象
s.setCorePoolSize(); 设置核心池的大小
s.setKeepAliveTime(); 线程没有任务时最多保持多长时间后会被终止
s.setMaximumPoolSize(); 最大线程数
......

方式一和方式二的比较

在开发中是优先选择方式二(实现Runnable接口)创建线程的

原因:

1.实现Runnable接口天然实现数据的共享(创建Thread类对象时传入的都是同一个Runnable实现类对象时)
2.实现Runnable接口不是继承,继承有唯一性,而接口的实现没有数量要求,扩展性高

实现Callable接口创建线程的方式较之前的方法更为强大

原因:

1.Callable接口拥有返回值
2.Callable接口可以抛出异常
3.Callable接口支持泛型

线程池的优势 为什么项目中都常用线程池

真正的使用时,一个个创建线程,销毁线程太麻烦了。特别是经常创建销毁特别大的资源时,对性能的影响非常大

不如先创建好线程,再将其放入线程池中,使用的时候调用,不需要的时候归还线程池(不销毁),这样创建和销毁的次数就大大减少,实现重复利用

优势:

提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中的线程,不需要每次都创建和销毁)
便于线程集中管理
2.Callable接口可以抛出异常
3.Callable接口支持泛型

线程池的优势 为什么项目中都常用线程池

真正的使用时,一个个创建线程,销毁线程太麻烦了。特别是经常创建销毁特别大的资源时,对性能的影响非常大

不如先创建好线程,再将其放入线程池中,使用的时候调用,不需要的时候归还线程池(不销毁),这样创建和销毁的次数就大大减少,实现重复利用

优势:

提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中的线程,不需要每次都创建和销毁)
便于线程集中管理
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值