初次学习Java多线程,这一篇就够了

多线程:一项用于改进性能的技术。

概念及其作用:

概念: 多线程是指从软硬件上实现多条执行流程的技术。
作用和好处: 改进性能,意味着用更少的资源做更多的事情。

创建多线程的方式:

方式一:通过继承Thread类

优点: 编码简单
缺点: 线程类已经继承了Thread类无法继承其他类,不利于扩展

代码实现以及代码中的注意事项,会以注释的方式在代码中体现。

1.定义一个线程类去继承Thread类,重写run方法来说明让这个类去做什么事

//1.定义一个线程类继承Thread
public class MyThread extends Thread{
    //2.重写run方法,里面定义线程做什么
    //这里我们让这个线程去输出5次
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行输出数字:" + i);
        }
    }
}

2.在主方法中创建新的线程并且调用start方法

public class threadDemo1{
    public static void main(String[] args) {
        //3.new一个新的线程对象(这里用的是多态的写法)
        Thread thread = new MyThread();
        //4.调用start方法启动线程(执行的还是run方法)
        //为什么不用run方法?
        //如果调用run方法,主方法就会当run为一个普通方法,无法启动多线程,
        //执行完成run方法后才会继续向下执行
        //start方法是告诉我们操作系统我这又开了一条新的线程
        thread.start();

        //不要把主线程的任务放到子线程的前面去
        //如果放到前面了,操作系统就会认为只有一个线程,主线程这时永远都会先跑完
        //这里是主线程的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出数字:" + i);
        }
    }
}

执行结果:主线程与子线程同时进行
在这里插入图片描述

方式二:通过实现Runnable接口的方法

优点: 线程任务类只是实现了接口,可以继续继承类和实现接口,扩展性强
缺点: 编程多一层对象包装,如果线程有执行是不可以直接返回的

1.实现Runnable接口,并重写其中的run方法

//1.实现Runnable接口,并重写接口中的run方法
public class MyRunnable implements Runnable{
    //重写run方法,这里的run方法中也是定义子线程执行的任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出数字:" + i);
        }
    }
}

2.创建任务对象,并且将任务对象交给线程对象启动start方法

public class ThreadDemo2 {
    public static void main(String[] args) {
        //2.创建一个任务对象
        Runnable runnable = new MyRunnable();
        //3.把runnable任务对象交给Thread处理
        //这里需要通过Thread的构造器新创建一个线程对象并且调用start方法
        Thread thread = new Thread(runnable);
        //4,通过线程对象调用start方法
        thread.start();
        
        //这里是主线程执行的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出数字:" + i);
        }
    }
}
方式二的另一种写法(匿名内部类的方式)

这种方法就是方式二的变式,用了匿名内部类与lambda表达式

public class ThreadDemo2_other {
    public static void main(String[] args) {
        //1.创建匿名内部类的对象来重写run方法
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程输出数字:" + i);
            }
        });
        //2.将任务对象交给线程对象,并且调用start方法
        thread.start();
        //主线程执行的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出数字:" + i);
        }
    }
}
方式三:JDK5.0新增:实现Callable接口

优点: 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕后获取线程执行的结果
缺点: 编码复杂
1.定义任务类实现Callable接口

import java.util.concurrent.Callable;

/**
 * 1.定义一个任务类实现Callable接口,应该声明线程任务执行完毕后的结果的数据类型
 */
//1.创建一个Callable的实现类,并且重写call方法
public class MyCallable implements Callable<String> {
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        //重写call方法(线程的任务方法)
        //这个任务为计算1-n的和
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return "子线程执行的结果是" + sum;
    }
}

2.通过FutureTask对象将任务对象交给线程对象并启动任务

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建Callable任务对象
        Callable<String> call = new MyCallable(100);
        //4.将callable任务对象交给(封装为)FutureTask对象
        //FutureTask继承Runnable,属于Runnable的对象
        //因为Runnable对象能够交给线程对象,所以这里用FutureTask
        //作用:是Runnalbe的对象兑现更可以交给Thread
        //      可以在线程执行完毕之后通过调用其get方法得到线程的结果
        FutureTask<String> futureTask = new FutureTask<>(call);
        //5.交给线程对象,并且调用start方法
        Thread thread = new Thread(futureTask);
        //6.调用start方法让线程启动
        thread.start();
        //通过FutureTask对象的get方法来返回结果
        System.out.println(futureTask.get());

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出的数字为" + i);
        }
    }
}

多线程中常用的方法

这里setName() getName run() start() 方法不做演示

1.currentThread()方法

方法说明:可返回代码段正在被哪个线程调用的信息

public class Run{
    public static void main(String[] args){
    //调用currentThread()方法输出当前线程名称
        System.out.println(Thread.currentThread().getName());
    }
}
2.sleep(long time)方法

方法说明:让当前线程休眠指定的时间后再继续执行,单位为毫秒

import java.util.Date;
//这个例子用主线程任务来实现方法
public class ThreadSleepDemo {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            System.out.println("输出:" + i + new Date());
            if (i == 3){
                Thread.sleep(5000);
            }
        }
    }
}

观察执行结果的时间可以发现,线程休眠了5秒
在这里插入图片描述

线程安全(了解就可以)

线程安全问题: 当多个线程同时操作同一个线程的时候可能会出现业务安全问题,称为线程安全问题。
例如取钱问题,如果发生线程安全问题,银行就会亏损等

线程同步:解决线程安全的法宝

方式一:同步代码块synchronized

作用: 把出现线程安全问题的核心代码给上锁
原理: 每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
锁对象要求: 理论上,锁对象只要对于当前同时执行的线程来说是同一个对象即可,
规范上:建议使用共享资源作为锁对象。

同步代码块是如何实现线程安全的?
对于出现问题的核心代码使用synchronized进行加锁
每次之恩那个一个线程占锁进入访问

同步代码块的同步锁对象有什么要求?
对于实例方法建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)作为锁对象

public class SynchronizedDemo {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    //取钱业务
    public void drawMoney(double money) {
        //同步代码块
        //this == 共享账户
        synchronized (this) {
            //1.判断账户是否够钱
            if (this.getMoney() >= money){
                //2.取钱
                System.out.println("取钱成功,取出" + money);
                this.money -= money;
            }else {
                System.out.println("余额不足");
            }
        }
    }
}
方式二:同步方法

作用: 把出现线程安全问题的核心方法给上锁
原理: 每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
底层原理: 同步方法其实底层也是有隐式锁对象的,只是所得范围是整个方法代码

	//取钱业务
    //使用同步方法,给方法加上synchronized关键字
    public synchronized void drawMoney(double money) {
            //1.判断账户是否够钱
            if (this.getMoney() >= money){
                //2.取钱
                System.out.println("取钱成功,取出" + money);
                this.money -= money;
            }else {
                System.out.println("余额不足");
            }
    }
方式三:Lock锁

JDK1.5后新增新一代的线程同步方式与采用synchronized相比,lock可提供多种锁方案,更灵活

//1.首先要拿来一把锁,创建锁对象
//final修饰后:锁对象是唯一和不可替换的
private final Lock lock = new ReentrantLock();

public void drawMoney(double money) {
		//这里我们用try..finally...的方式,无论程序是否出错最后都把锁解开
        try {
            lock.lock();//2.上锁
            //1.判断账户是否够钱
            if (this.getMoney() >= money){
                //2.取钱
                System.out.println(name + "来取钱成功,吐出" + money);
                this.money -= money;
            }else {
                System.out.println("余额不足");
        }
        } finally {
            lock.unlock();//3.解锁
        }
    }

线程池(重点)

概念及其优势

概念: 线程池就是一个可以复用线程的技术
优势:

  • 降低资源消耗:线程和任务分离,提高线程重用性
  • 控制线程并发数量,降低服务器压力,统一管理所有线程
  • 提高系统响应速度。假如创建线程用的时间为T1,执行任务的时间为T2,销毁线程的时间为T3,那么使用线程池就免去了T1和T3的时间。
如何获得线程池对象

通过线程池的实现类ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                               int maximumPoolSize,
                               long keepAliveTime,
                               TimeUnit unit,
                               BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                               RejectedExecutionHandler handler)

类中参数的说明:
corePoolSize: 核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize: 线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
keepAliveTime: 线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit: 指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue: 任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
threadFactory: 线程工厂。用于指定为线程池创建新线程的方式。
handler: 拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池的使用

线程池执行Runnable任务

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //1.创建线程池对象(多态)
        ExecutorService threadPool = new ThreadPoolExecutor(3,10,
                5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        //2.给任务线程池处理
        Runnable target = new MyRunnable();
        //3.将任务通过线程池对象中的execute方法交给线程池执行任务
        threadPool.execute(target);
    }
    // 关闭线程池
	threadPool.shutdown(); // 立即关闭,即使任务没有完成,会丢失任务
	threadPool.shutdownNow(); // 会等待全部任务执行完毕之后再关闭任务
}

线程池执行Callable任务

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //1.创建线程池对象(多态)
        ExecutorService threadPool = new ThreadPoolExecutor(3,10,
                5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        //2.将callable任务通过线程池对象的submit方法交给任务线程池处理
        threadPool.submit(new MyCallable(100));
    }
}

Executors工具类创建线程池对象

注意: Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的
Executors使用可能存在陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//通过Executors工具类来创建线程池对象
public class ExecutorsPoolDemo {
    public static void main(String[] args) {
        //1.创建固定线程数据的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        //2.再通过线程池对象的execute方法可以提交执行Runnable任务
        //  通过线程池对象的submit方法可以提交执行Callable任务
    }
}

定时器 Timer ScheduledExecutorService

概念: 定时器是一种控制任务延时调用,或者周期调用的技术。
作用: 闹钟,定时邮件发送。

实现方式一:Timer

Timer定时器的特点和存在的问题:

  1. Timer是单线程,处理多个任务按照顺序执行,存在延时设置定时器的时间有出入
  2. 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行

Timer定时器的使用

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo1 {
    public static void main(String[] args) {
        //1.创建Timer定时器
        Timer timer = new Timer();
        //2.调用方法,处理定时任务(这里用了一个匿名内部类的方式创建一个延时任务)
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行一次");
            }
        },3000,2000);
    }
}
实现方式二:ScheduledExecutorService

ScheduledExecutorService的优点:

  1. 基于线程池,某个任务的执行情况不会影响其他定时执行任务的执行

ScheduledExecutorService执行定时任务

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

public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) {
        //1.创建ScheduledExecutorService线程池,做定时器
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
        //2.开启定时任务
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },3,2, TimeUnit.SECONDS);
    }
}

线程的生命周期:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_细嗅蔷薇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值