Java多线程的四种实现方式(学习笔记)


一、多线程概念

1.进程与线程

进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。

线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少 有一个线程。线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程。

2.线程调度

线程调度分为分时调度和抢占式调度。

分时调度所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。 CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时 刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使 用率更高

3.同步与异步

同步:排队执行 , 效率低但是安全.

异步:同时执行 , 效率高但是数据不安全.

4.并发与并行

并发指两个或多个事件在同一个时间段内发生。 一秒并发,一分钟并发,一小时并发

并行指两个或多个事件在同一时刻发生(同时发生)

二、多线程实现方式

1.继承Thread类

代码如下:

public class Demo1 {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        //启动线程
         m.start();
    }
    public  static  class MyThread extends Thread{
        @Override
        public void run() {
         //这个执行路径的方式,不是调用run方法,
         //而是通过thread对象的start()来启动任务
            for(int i=0;i<10;i++){
        System.out.println("哈哈哈"+i);
            }
        }
    }
}

使用一次的类可用匿名内部类简化代码

public class Demo1 {


    public static void main(String[] args) {
        new MyThread() {
            //重写run方法,
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println( "哈哈哈" + i);
                }
            }
        }.start();
    }
}

步骤

  • 创建一个继承于Thread类的子类
  • 重写Thread类中的run():将此线程要执行的操作声明在run()
  • 创建Thread的子类的对象
  • 调用此对象的start():①启动线程 ②调用当前线程的run()方法

2.实现 Runnable接口

代码如下:

public class Demo2 {
    public static void main(String[] args) {
        //1.    创建一个任务对象
        MyRunnable r = new MyRunnable();
        //2.    创建一个线程,并为其分配一个任务
        Thread th = new Thread(r);
        //3.    执行这个线程
        th.start();
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<10;i++){
                System.out.println("哈哈哈"+i);
            }
        }
    }
}

步骤

  • 创建一个实现Runnable接口的类
  • 实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
  • 创建Runnable接口实现类的对象
  • 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 调用Thread类中的start():① 启动线程 ② 调用线程的run() —>调用Runnable接口实现类的run()

与继承Thread相比,实现Runnable的优势

  • 通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
  • 可以避免单继承所带来的局限性
  • 任务与线程是分离的,提高了程序的健壮性
  • 后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程

以下两种方式是jdk1.5新增的

3.实现Callable接口

Runnable 与 Callable 对比

Runnable 与 Callable的相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程

Runnable 与 Callable的不同点

  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。

代码如下:

public class Demo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.  编写类实现Callable接口 , 实现call方法
        Callable<Integer> c = new MyCallable();
        //2.  创建FutureTask对象 , 并传入第一步编写的Callable类对象
        FutureTask<Integer> task = new FutureTask<>(c);
        //3.  通过Thread,启动线程 
        new Thread(task).start();
        //4.   可返回执行结果
        Integer j = task.get();
        System.out.println("返回值:"+j);
    }
    static class MyCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int i=0;i<10;i++){
                System.out.println("哈哈哈"+i);
                sum=sum+i;
            }
            return sum;
        }
    }
}

4.线程池创建

4.1 缓存线程池

说明

  • 缓存线程池无限制长度
  • 任务加入后的执行流程:1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用
    代码如下:
public class Demo4 {
    public static void main(String[] args) {
        //向线程池中加入新的任务
        ExecutorService service = Executors.newCachedThreadPool();
        //指挥线程池执行新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"哈哈哈");
            }
        });
    }
}

4.2 定长线程池

说明

  • 长度是指定的线程池
  • 加入任务后的执行流程
    1 判断线程池是否存在空闲线程
    2 存在则使用
    3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
    4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
    代码如下:
public class Demo5 {
    //定长线程池
    public static void main(String[] args) {
        //参数指定长度
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"哈哈");
            }
        });
    }
}

4.3 单线程线程池

执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
代码如下:

public class Demo6 {

    public static void main(String[] args) {
        //单线程线程池
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "哈哈");
            }
        });
    }
}

4.4 周期定长线程池

执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程

代码如下:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo7 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        /*
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次在执行上面时间以后)
            参数3:周期时长数字(没隔多久执行一次)
            参数4:时长数字的单位
        */
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"哈哈");
            }
        },5,1,TimeUnit.SECONDS);  //5秒后每1秒执行一次

    }
}

参考文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值