Java线程详细教程

目录

一.线程创建

线程基础知识介绍

创建方法一:继承Thread类

创建方法二:实现Runnable接口

创建方法三:实现Callable接口;线程池

线程池

Callable接口实现多线程

二.线程状态

线程状态

线程状态转换

代码演示

三.线程优先级

四.用户线程,守护线程

五.线程同步

同步方法

同步代码块

可重入锁ReentrantLock


详细讲解,强烈建议收藏!!!

一.线程创建

线程基础知识介绍

什么是线程?为什么要使用线程?

        在没有使用到线程的程序中,我们总是只能串行的执行程序,也就是我们的程序一次性只能进行一个操作。而我们使用微信进行视频聊天的时候,我们可以一边视频,一边和其他人打字聊天。这就是多线程,可以一个线程负责视频聊天,一个线程负责打字聊天,使用的任然是一个程序。在日常使用场景中,多线程是必要的。

创建方法一:继承Thread类

        Thread单词意思就是线程的意思,是Java提供的一个线程类。可以自己创建一个类然后继承Thread,重写run方法。run方法写入你想要进行多线程运行的代码。

        通过你建的类,实例化一个对象;通过这个对象调用start()方法,该对象就可以进行多线程操作了。

创建步骤:

        1.继承Thread类,重写run方法

        2.实例化一个对象,调用start方法(就是执行run函数里面的内容)

代码演示:

//1 创建两个类,继承Thread,都重写run方法
class Thread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Thread1 running!");
        }
    }
}

class Thread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Thread2 running!");
        }
    }
}

//2 对这两个类分别实例化一个对象,调用start方法
public class Demo1 {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        thread1.start();
        thread2.start();
    }
}

        运行结果发现,Thread1线程和Thread2线程轮流上处理机执行,由此可知该段程序确实实现了多线程操作 

创建方法二:实现Runnable接口

        Runnable是一个接口,该接口是一个函数式接口(不知道函数式接口的伙伴,可以看看我上一篇博客哦),只有一个抽象方法run

创建方法一的Thread类就是实现了Runnable接口,查看源码如下图:

        那为什么已经有Java开发人员已经给我们封装好的Thread类,有更多的方法可以供我们调用,我们还要用Runnable接口来实现线程,不是很冗余嘛,容我卖个关子,会在代码中解释。

创建步骤:

        1 创建一个类实现Runnable接口,重写run方法

        2 通过该类实例化对象

        3 将实例化的对象作为参数传到Thread实现线程,再调用start方法

//1 创建Demo1实现Runnable接口,重写run方法
public class Demo1 implements Runnable{
    //run方法模拟买票场景
    int ticket = 10; //设置票数为10
    @Override
    public void run() {
        // 只要票数有剩余,就可以一直进行买票
        while (ticket > 0){
            // Thread.currentThread()该静态方法可以获得当前运行的线程 getName()得到线程名称
            System.out.println(Thread.currentThread().getName() + "   get   " +ticket--);
        }
    }
    public static void main(String[] args) {
        //2 实例化一个demo1对象
        Demo1 demo1 = new Demo1();
        //3 将实例化的对象作为参数传到Thread实现线程
        //Thread有传入一个对象的构造器,构造器还能加入线程的名称
        //实例化了多个Thread类,再调用start方法,就能实现多个线程对同一个对象进行操作
        //这就是为什么还要用Runnable接口来实现线程的原因
        new Thread(demo1,"pp").start();
        new Thread(demo1,"mc").start();
        new Thread(demo1,"pp1").start();
        new Thread(demo1,"mc1").start();
    }
}

上面代码用到的构造器: 

        细心的伙伴在运行代码的时候会发现,票数可能出现负数的情况,这是明显的异常,原因是多个线程买了同一张票,为了解决这个问题,需要用到线程同步的知识,将在后续章节介绍。

创建方法三:实现Callable接口;线程池

        Callable也是一个接口,可以使用到线程池。当多线程场景下,我们一直创建或者销毁线程,对性能影响都很大,如果我们提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中,就可以避免一直创建、销毁,达到重复利用。

线程池

ExecutorService     线程池接口

常用方法:

        void execute(Runnable command); 执行任务,没有返回值,一般用来执行Runnable

        <T> Future<T> submit(Callable<T> task);执行任务,有返回值,一般用来执行Callable

        void shutdown();关闭连接池

Executors  该类用于创建线程池

Callable接口实现多线程

        实现callable接口需要返回值类型 ,而上述两种方法是不需要返回值的。不同与上述两种方法,实现Callable需要重写call方法,而不是run方法,并且call方法需要抛出异常。

创建步骤:

        1 创建一个类实现Callable接口,重写call方法

        2 创建目标对象,即通过该类实例化对象

        3 创建执行的服务,可通过实现线程池来创建

        4 提交执行 callable为submit方法

        5  获取结果

        6 关闭服务

/*实现callable接口需要返回值类型*/
public class TestCallable implements Callable<Boolean> {
    //重写call方法需要抛出异常
    @Override
    public Boolean call() throws Exception {
        return null;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1 创建目标对象
        TestCallable testCallable1 = new TestCallable();
        TestCallable testCallable2 = new TestCallable();
        TestCallable testCallable3 = new TestCallable();
        //2 创建执行的服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //3 提交执行
        Future<Boolean> f1 = ser.submit(testCallable1);
        Future<Boolean> f2 = ser.submit(testCallable2);
        Future<Boolean> f3 = ser.submit(testCallable3);
        //4 获取结果
        boolean r1 = f1.get();
        boolean r2 = f2.get();
        boolean r3 = f3.get();
        //5 关闭服务
        ser.shutdown();
    }
}

二.线程状态

线程状态

创建:当线程被创建时为创建状态

就绪:线程被分配了除处理机以外的所有资源

运行:线程正在处理机上运行

阻塞:线程还缺乏除处理机以外的其他资源导致无法继续执行

终止:线程执行结束

 

线程状态转换

创建到就绪:线程被分配足够资源就到了就绪状态,可以随时上处理机,理论上创建好的线程调用start方法即可进入就绪状态

就绪到运行:该转换可能又操作系统自行安排,也可通过程序定义的抢占来完成,join方法就是抢占方法,只有join的线程执行完,其他的才能执行

运行到就绪大有可能就是有线程抢占了处理机资源,也可以使用yield方法线程自己礼让

运行到阻塞:除了线程缺乏资源只能阻塞,也可以调用sleep方法,线程自行休眠

代码演示

下面对上述及其他线程状态有关方法演示:

// 线程状态演示
public class Station implements Runnable{

    @SneakyThrows
    @Override
    public void run() {
        /*
         * 线程休眠
         * 从运行状态变为阻塞状态
         * ...毫秒后,继续变为就绪状态
         * 每个线程都有一把锁,休眠锁保持
         */
        Thread.sleep(1000);
        /*
         * 线程礼让
         * 从运行状态变为就绪状态,不阻塞
         */
        Thread.yield();

    }

    public static void main(String[] args) throws InterruptedException {
        Station station = new Station();
        // 线程创建
        Thread thread = new Thread(station);
        // start之后线程就绪
        thread.start();
        /*
         * 线程抢占
         * 只有join的线程执行完,其他的才能执行
         */
        thread.join();

        //观察线程状态
        System.out.println(thread.getState());
    }
}

三.线程优先级

上文介绍线程状态切换中有个抢占算法join,什么时候需要抢占呢?其中有个使用场景就是,线程优先级是不同的,高优先级可以抢占低优先级的处理机资源。

Java线程按照数值划分等级,从1~10数值越大等级越高。

可以通过getPriority()这个方法获取线程的优先级

通过setPriority()设置线程的优先级,数值可以从0~10

还提供了三种常量可供选择。

未设置优先级,默认为5.

下面进行代码演示

public class Priority {
    public static void main(String[] args) {
        // 未设置优先级,优先级都为5,包括主线程
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
        Thread thread1 = new Thread(new ThreadPriority(),"thread1");
        thread1.setPriority(1); // 设置线程优先级 1~10

        Thread thread2 = new Thread(new ThreadPriority(),"thread2");
        thread2.setPriority(Thread.MAX_PRIORITY);
        thread1.start();
        thread2.start();
    }
}

class ThreadPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

 

四.用户线程,守护线程

/*
用户线程 : 用户线程全部执行完毕,程序终止
守护线程 : 守护线程就算无限循环,也会随着用户线程全部终止而终止 日志,垃圾回收,监控内存
默认创建的都是用户线程
*/
thread.setDaemon(true); //将该线程设置为守护线程

五.线程同步

        多个线程操作同一个对象,就可能造成同步问题,如上文买票,可能多个线程对同一张票进行操作。解决方案可以让多个线程排队 ,,锁唯对操作对象的线程上锁一,就能避免同步问题

同步方法

使用synchronized修饰一个方法,就实现了同步方法,整个方法体都会进行同步;同时只会有一个线程执行这个方法。实际就是对这个方法加上了锁,锁唯一,有一个线程执行了这个方法,就上锁了,其他线程不能进入,就解决了同步问题。

下面优化代码,解决上文遗留的买票问题

public class Lock1 {
    public static void main(String[] args) {
        //创建一个买票的对象
        BuyTicket buyTicket = new BuyTicket();
        // 三个线程小明,小红,小蓝都要买票
        Thread thread1 = new Thread(buyTicket,"小明");
        Thread thread2 = new Thread(buyTicket,"小红");
        Thread thread3 = new Thread(buyTicket,"小蓝");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class BuyTicket implements Runnable{
    // 一个100张票
    private int ticket = 100;
    //票卖完了就设置false,初始为true
    boolean flag = true;
    
    //线程执行逻辑,有票就一直买
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    //实现了一个同步方法,整个方法体都进行了同步
    //票大于0,就买;票没了就设置flag为false,没票了
    private synchronized void buy() {
        if (ticket > 0){
            System.out.println(Thread.currentThread().getName() + "  get  " + ticket--);
        }else {
            flag = false;
        }
    }
}

同步代码块

使用synchronized定义一段代码块,对一段代码块或者代码块里的共享变量进行上锁。

上文buy方法也可以用同步代码块实现。

// 实现了一个同步代码块,只对代码块里面的内容进行上锁
    private void buy2() {
        synchronized(this){
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + "  get  " + ticket--);
            }else {
                flag = false;
            }
        }
    }

在取钱场景中,一个账户,同时在手机端和ATM机上取钱 ,也存在同步问题。账户就作为了共享变量,多个线程都可以进行操作。此时将取钱方法使用synchronized修饰,变为同步方法,由于对共享变量更新不及时,依然可能存在同步问题。

此时使用同步代码块对共享变量进行上锁可以完全解决同步问题。

import lombok.Data;

public class Lock2 {
    public static void main(String[] args) {
        Account account = new Account(100);
        // 定义两个线程,取两次钱
        Drawing drawing1 = new Drawing(50,account);
        Drawing drawing2 = new Drawing(100,account);
        drawing1.start();
        drawing2.start();
    }
}

// 账户类
@Data
class Account{
    private int money;
    public Account(int money) {
        this.money = money;
    }
}
// 存取款类,可以多个线程同时进行取钱
class Drawing extends Thread{
    final Account account;
    int drawingMoney; // 存取金额
    public Drawing(int drawingMoney,Account account) {
        this.drawingMoney = drawingMoney;
        this.account = account;
    }

    @Override
    public synchronized void run() {
        if(account.getMoney() - drawingMoney < 0){
            System.out.println("没钱了!!!");
        }else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.setMoney(account.getMoney() - drawingMoney);
            System.out.println("取了:" + drawingMoney + ";剩余:" + account.getMoney());
        }

    }
}

执行结果

 

依然存在问题,使用同步代码块:

import lombok.Data;

public class Lock2 {
    public static void main(String[] args) {
        Account account = new Account(100);
        // 定义两个线程,取两次钱
        Drawing drawing1 = new Drawing(50,account);
        Drawing drawing2 = new Drawing(100,account);
        drawing1.start();
        drawing2.start();
    }
}

// 账户类
@Data
class Account{
    private int money;
    public Account(int money) {
        this.money = money;
    }
}
// 存取款类,可以多个线程同时进行取钱
class Drawing extends Thread{
    final Account account;
    int drawingMoney; // 存取金额
    public Drawing(int drawingMoney,Account account) {
        this.drawingMoney = drawingMoney;
        this.account = account;
    }

    @Override
    public void run() {
        // 同步代码块,对共享变量上锁
        synchronized(account){
            if(account.getMoney() - drawingMoney < 0){
                System.out.println("没钱了!!!");
            }else {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setMoney(account.getMoney() - drawingMoney);
                System.out.println("取了:" + drawingMoney + ";剩余:" + account.getMoney());
            }
        }

    }
}

 得到结果

同步问题解决。 

        在实际开发中,可以尽量使用同步代码块,一个方法体并非所有部分都需要上锁,使用同步代码块更加灵活。并且同步代码块我们可以自由选择锁,对于共享变量的处理,使用同步代码块能更好解决问题。

可重入锁ReentrantLock

同步代码块、同步方法都是系统帮助我们上锁,解锁。可重入锁可以显示的上锁,解锁。

import java.util.concurrent.locks.ReentrantLock;

public class Lock3 {
    public static void main(String[] args) {
        Buy buy = new Buy();
        new Thread(buy).start();
        new Thread(buy).start();
        new Thread(buy).start();
    }
}

class Buy implements Runnable {
    int goods = 10;
    // 定义一个可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            // 显式上锁
            lock.lock();
            while (true) {
                if (goods > 0) {
                    System.out.println(goods--);
                } else {
                    break;
                }
            }
        } finally {
            // 显式解锁
            lock.unlock();
        }
    }
}

如有帮助,烦请点赞关注! 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mc长风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值