JAVA-多线程:

0 基础概念:

多线程 通过 同时运行多个任务 完成 提高效率、特殊需求(同时完成)。

一个程序即一个JVM进程,一个进程内有一个执行main()的主线程main线程,可有其他多个线程。

  • 并发:同一时刻,有多个指令在单个CPU上交替执行
  • 并行:同一时刻,有多个任务在多个CPU上同时执行

此处CPU实际指CPU“线程参数”

1 实现方式:

有三种:

  1. 创建一个新类继承Thread 类,重写run()方法。
  2. 创建一个新类实现Runnable接口,并实现其run方法。并将该类的实例对象传至Thread类的构造函数。
  3. 利用Callable接口和Futuer接口,同Runnable,但还可以返回值、抛出异常。需要借助FutureTask包装器(即Futuer接口的实现类)。

1.1 继承Thread类:

  1. 定义一个Thread的子类
  2. 重写 run()方法
  3. 创建对象,使用 对象.strat();

例:

PrintThread.java:

public class PrintThread extends Thread {
    public void run(){
        for (int i =1;i<=30;i++){
            System.out.println(this.getName()+":\t"+i);
        }
    }
}

Main:

public static void main(String[] args) {

        PrintThread t1=new PrintThread();
        PrintThread t2=new PrintThread();

        t1.setName("MyThread 1");
        t2.setName("MyThread 2");


        t2.run();//只会执行方法,不会创建新线程
        System.out.println(t2.isAlive());//F

        t1.start();
        t2.start();

        System.out.println(t2.isAlive());//T

    }

结果看到两条线程交替输出。

1.2 实现Runnable接口:

  1. 定义一个类实现Runnable接口,重写run()
  2. 创建自定义类对象
  3. 创建Thread对象,构造函数参数为自定义类的对象

例:

public class MyRun implements Runnable{
    @Override
    public void run() {
        //获取当前线程的对象
        Thread t= Thread.currentThread();
        for (int i =1;i<=30;i++){
            System.out.println(t.getName()+": "+i);
        }
    }
}

1.3 利用Callable接口和Futuer接口

  1. 创建自定义类实现Callable接口<泛型:表示结果类型>
  2. 重写call()。他有返回值,表示多线程运行的结果。
  3. 创建自定义类对象(表示多线程要执行的任务)
  4. 创建FutureTask类的对象,构造函数参数为自定义类对象(作用:管理多线程运行的结果)
  5. 创建Thread对象,构造函数参数为FutureTask类对象,启动。

例:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <=100 ; i++) {
            sum+=i;
        }
        return sum;
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc=new MyCallable();

        FutureTask<Integer> futureTask =new FutureTask<>(mc);

        Thread t1=new Thread(futureTask);

        t1.start();

        System.out.println(futureTask.get());
    }

1.4 三者对比:

线程实现方式优点缺点
继承Thread类编程比较简单,可以直接使用Thread类中的方法可以扩展性较差,不能再继承其他的类
实现Runnable接口扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口同上,可以获取线程结果。编程相对复杂,不能直接使用Thread类中的方法

3 常用成员方法:

方法名称说明
String getName()返回此线程的名称
void setName(String name)设置线程的名字(构造方法也可以设置名字)
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒,睡眠期间会被抢走执行权
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()出让线程/礼让线程
public static void join()插入线程/插队线程

3.1 线程的优先度

线程调度方法

  • 抢占式调度(随机)【JAVA中使用】
  • 非抢占式调度(轮流)
setPriority(int newPriority)设置线程的优先级(1~10,默认5)
final int getPriority()获取线程的优先级

优先级越高,抢占到的概率高

3.2 守护线程

当其他的非守护线程执行完毕以后,守护线程会 陆续 结束。

JVM退出时,不必关心守护线程是否已结束。

final void setDaemon(boolean on)设置为守护线程

4 线程的生命周期/状态

注:Java实际上并没有定义“运行”这个状态,是因为运行时JVM会把线程交给操作系统。

5 线程安全问题:

线程安全问题通常涉及到共享资源的访问和修改,如果没有适当的同步机制,就可能导致数据不一致、数据损坏、死锁等问题。

5.1 同步代码块

把操作共享数据的代码锁起来,使其面对多个线程对象,轮流执行:

关键字:s

synchronized(锁对象){ ……. }

  • 锁默认打开,有一个线程进去了,锁自动关闭
  • 里面的代码全部执行完毕,线程出来,锁自动打开
  • 锁对象随意创建但必须唯一,常用当前类的字节码对象(类名.class)

例:

TicketWindow.java:

public class TicketWindow extends Thread{
    static int ticket=1;
    static int total=0;
    static Object obj=new Object();//锁对象
    public static void setTotal(int n){
        total=n;
    }

    @Override
    public void run(){
            while (true){//注意此处条件不能为“ticket<=total”,判断条件并没有被锁住,会出现超票
                synchronized (obj) {//所不能包住while,否则一个线程全部卖完了
                    if (ticket>total)
                        break;
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(getName()+"卖出了第 "+ticket+" 张票");
                    ticket++;
                }
            }
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        TicketWindow t1=new TicketWindow();
        TicketWindow t2=new TicketWindow();
        TicketWindow t3=new TicketWindow();

        TicketWindow.setTotal(25);

        t1.setName("第一售票口");
        t2.setName("第二售票口");
        t3.setName("第三售票口");

        t1.start();
        t2.start();
        t3.start();
    }
}

5.2 同步方法

把synchronized关键字加到方法上

  • 同步方法锁住方法内所有代码
  • 锁对象不能自己指定
    • 非静态:this
    • 静态:当前类的字节码文件对象

5.3 Lock锁

上文的锁是自动打开/关闭的,想要手动开关锁,要使用Lock接口的实现类ReentrantLock来创建锁对象。

  • void lock() 上锁
  • void unlock() 释放锁

6 死锁

哲学家进餐问题

避免锁的嵌套

7 生产者和消费者(等待唤醒机制)

生产者消费者模式是一个经典的多线程协作模式。

生产者线程负责生产数据并将其存储到共享缓冲区,消费者线程则从缓冲区中取出数据进行消费。

等待唤醒机制:是解决生产者和消费者问题的关键机制。当缓冲区已满时,生产者线程需要等待(进入阻塞状态),直到有消费者线程从缓冲区中取出数据,释放出空间后,生产者线程才能被唤醒继续生产;同理,当缓冲区为空时,消费者线程需要等待(进入阻塞状态),直到有生产者线程生产数据放入缓冲区后,消费者线程才能被唤醒继续消费。

常见方法: 用锁对象去调用而非线程对象

  • void wait(); 当前线程等待,直到被其他线程唤醒
  • void notify(); 随机唤醒单个线程
  • void notifyAll() 唤醒所有线程(常用 )

例:

  • Space.java
public class Space {
    //缓冲区是否为空
    public  static  int flag=0;
    //消费者最大消耗次数
    public static int Count=20;
    //锁对象
    public static Object lock=new Object();
}
  • Producer.java:
public class Producer extends Thread{
    private void produce(){
        System.out.println("生产者生产了资源");
        Space.flag=1;
    }
    
    @Override
    public void run(){
        while (true){
            synchronized (Space.lock){
                if (Space.Count==0){//共享数据消耗次数达到上限
                    break;
                }else{
                    if(Space.flag!=0){//缓冲区不空,则wait
                        try {
                            Space.lock.wait();//隐含让当前线程与这个锁绑定
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{//缓冲区空,生产,并将缓冲区状态改为不空
                        produce();
                        Space.lock.notifyAll();
                    }
                }
            }
        }
    }
}
  • Consumer.java:
public class Consumer extends Thread{
    private void consume(){
        Space.Count--;
        System.out.println("消费者消耗了一次。还可消耗 "+Space.Count+" 次");
        Space.flag=0;
    }

    @Override
    public void run() {
        while (true){
            synchronized (Space.lock){
                if(Space.Count==0){//共享数据消耗次数达到上限
                    break;
                }else{
                    if(Space.flag==0){//缓冲区空,则wait
                        try {
                            Space.lock.wait();//隐含让当前线程与这个锁绑定
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{//缓冲区不空,消费,并将缓冲区状态改为空
                        consume();
                        Space.lock.notifyAll();
                    }
                }
            }
        }
    }
}

Test.java:

public static void main(String[] args) {
Producer p=new Producer();
Consumer c=new Consumer();

c.start();
p.start();
}

阻塞队列:

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • put(E e)
  • take()

8 线程池

频繁创建和销毁大量线程需要消耗大量时间,所以有了线程池:通过复用、管理一组线程来完成任务队列。

核心原理:

  • 创建一个线程池,线程池中初始为空
  • 提交任务时,线程池会复用已有的线程对象去完成任务;任务执行完毕,线程归还给池子。如果池子中没有空闲线程,可以创建新线程(不超过预订数量)去完成任务。
  • 如果提交任务时,线程池中没有空闲线程且无法创建新线程,任务就会排队等待。

代码实现:

  1. 创建线程池 ExecutorService
  2. 提交任务
  3. 所有任务全部执行完毕,关闭线程池。

例:

public static void main(String[] args) {
        //1.创建池子
        ExecutorService pool1=Executors.newFixedThreadPool(3);

        //2.提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

        //3.关闭池子(实际服务一般不会手动关闭)
        pool1.shutdown();
    }

线程池容量最大为三。提交了五个任务,三个线程完成。

8.1 自定义线程池:

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

该方法有七个参数:

public ThreadPoolExecutor( int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler);
参数描述
corePoolSize核心线程数
maximumPoolSize最大线程数
keepAliveTime空闲线程存活时间
unit时间单位
workQueue任务队列:LinkBlockingQuene(可变)、 ArrayBlockingQuene(不可变)
threadFactory线程工厂:指定线程如何生产
handler任务拒绝策略:线程池已达最大容量且线程都在工作、任务队列也达到最大容量(四种拒绝策略)

四种任务拒绝策略:

任务拒绝策略描述
AbortPolicy默认的拒绝策略,抛出 RejectedExecutionException 异常
DiscardPolicy直接丢弃任务、且不抛出异常(不推荐)
DiscardOldestPolicy丢弃处于任务队列头部的任务,添加被拒绝的任务
CallerRunsPolicy调用任务的 run(),绕过线程池直接运行

例:

public class MyPoolTest {
    public static void main(String[] args) {
        //自定义线程池对象
        ThreadPoolExecutor pool=new ThreadPoolExecutor(
                3,//核心线程数
                10,//最大线程数
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//单位
                new ArrayBlockingQueue<>(3),//任务队列(限定长度为三)
                Executors.defaultThreadFactory(),//默认线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略(注意是一个内部类)
        );

        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdownNow();
    }
}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇