多线程

本文深入探讨了Java中的多线程概念,包括线程的栈空间、堆共享、线程休眠、阻塞、中断以及守护线程。详细阐述了线程安全问题,如同步代码块、同步方法和显式锁,并介绍了公平锁与非公平锁。此外,通过生产者-消费者问题展示了线程间的通信。最后,讨论了线程的六种状态和线程池技术,强调了使用线程池在高并发场景下的优势。示例代码展示了如何实现线程间的协作与通信。
摘要由CSDN通过智能技术生成

多线程

每个线程都有自己的栈空间,共用一份堆内容。

线程休眠:

Thread.sleep(tms)

线程阻塞(耗时操作):

所有需要消耗时间的操作(如读文件、等待用户输入)。

线程中断:

一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定

  1. t1.interrupt()给线程t1添加中断标记
  2. 线程任务内部语句(如休眠语句)抛出InterruptedException异常
  3. 进入catch块,执行资源释放语句,执行return语句
  4. 线程自杀
守护线程:

守护用户线程的,当最后一个用户线程结束时,所有线程自动死亡。

设置为守护线程要在启动前设置:t1.setDaemon(true);

线程安全问题:

由多个线程处理相同数据引起。

解决(看是否是同一把锁):

  1. 同步代码块

    // 格式	synchronized(锁对象){...}
    // 某线程抢到了该锁,打上了锁标记;执行代码块语句;解锁;各线程抢该锁;。。。
    // 谁先抢到,接下来继续抢到的概率比较高
    
  2. 同步方法

    // 给方法添加synchronized关键字,则该方法成为同步方法
    // 锁对象是this
    
  3. 显式锁Lock

    1、2都属于隐式锁。

    // 显式锁锁对象l
    private Lock l = new reentrantLock();
    ...
        // 上锁
        l.lock();
    	...
        // 解锁
        l.unlock();
    
公平锁与非公平锁:

以上1、2、3都是非公平锁,锁一解开,大家都在抢(抢时间片)。

->公平锁:大家排队谁先来谁就得到锁:

Private Lock l = new ReentrantLock(fair:true);
线程死锁:

避免方法:在加锁的方法里不要再调用其他加锁的方法。

多线程通信问题:

生产者-消费者问题:需要确保生产者生产和消费者消费不同时进行,确保数据安全

若不进行线程间通信,只进行同步,则会造成时间分配严重不均,不是原定的协作

生产者生产时,令消费者睡着object.wait(),生产完毕,把消费者唤醒object.notifyAll();反之亦然。

package thread;

public class Demo12 {
    public static void main(String[] args) {
        //多线程通信    生产者与消费者问题
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food f;

        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if(i%2==0){
                    f.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food f;

        public Waiter(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;
        //true表示可以生产
        boolean flag = true;
        public synchronized void setNameAndTaste(String name,String taste){
            if(flag){
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag){
                System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
线程的6种状态

Enum Thread State

在这里插入图片描述

相关类与接口

Thread类

构造方法,方法,静态方法

(如何关闭一个线程:Stop方法已过时,其不安全,不释放资源;可以通过观察变量的方式控制线程关闭)

优先级

休眠(静态方法)

守护线程(所有用户死亡则守护线程自动死亡)、用户线程

name

静态方法currentThread()

接口Runnable
接口Callable

需要带有返回值的线程时使用。

get方法

clear

cancel

类ExecutorService

线程池

execute(Runnable)向线程池内添加新的任务

类Executors

创建各种线程池

实现

main方法是主线程,在其中启动的其他线程是分支线程。

他们并行执行。(抢占式分配CPU)

1. 继承Thread
覆写public void run():线程要执行的任务方法

触发方式:通过Thread对象的start()来启动任务,而非调用run方法

最简单的使用情况:匿名内部类。

2. 实现Runnable(任务对象)
覆写public void run()
// 1. 实现一个任务对象
MyRunnable r = new MyRunnable();
// 2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r); 
// 3. 执行这个线程
t.start();
与继承Thread相比的好处:
  • 更适合多个线程同时执行相同任务的情况;
  • 可以避免单继承带来的局限性;
  • 任务与线程本身分离,提高程序的健壮性。
  • 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类型的线程。
3.实现Callable
  • 覆写 call() throws Exception方法。

  • 可以像1.2一样与主线程并发执行,也可以让主线程等待其结果(get方法)。

  • 使用较少。

// 1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
    @Override
    public <T> call() throws Exception {
        return T;
    }
}
// 2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
// 3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
  • 都是接口

  • 都可以编写多线程程序

  • 都采用Thread.start()启动线程

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

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

线程池

不用线程池:1.创建线程;2.创建任务;3.执行任务;4.关闭线程。

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源

(后端编程已经基于多线程了,可能主动使用线程池的情况会比较少)

线程列表 - 任务列表

1.缓存线程池
2.定长线程池
3.单线程线程池
4.周期定长线程池
lambda表达式:fromJDK1.8

函数式编程思想

适用于创建一个只创建1次的单方法接口实现实例,作为其他方法的参数。

    /*
    lambda表达式
    函数式编程思想
    **/
    public static void main(String[] args) {
        //冗余的Runnable编写方式
       /* Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        });
        t.start();*/

        Thread t = new Thread(() -> System.out.println("锄禾日当午"));
        t.start();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值