多线程
本文最后更新于6 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

多线程基础

什么是多线程

并发和并行

多线程的实现方式

继承Thread类的方式行进实现

继承Thread类实现多线程是通过创建Thread的子类并重写其run()方法以定义任务,再调用start()方法启动线程,但该方式受Java单继承局限且线程与任务紧密耦合,不利于资源共享。

创建Thread子类:

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

调用start方法:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.setName("线程一");
        myThread2.setName("线程二");
        myThread1.start();
        myThread2.start();
    }
}

实现Runnable接口的方式进行实现

实现Runnable接口是把任务代码定义在实现类的run()方法中,再将该实例作为目标对象传入Thread类并调用start()启动,此方式解耦了线程与任务、规避了单继承局限,且更适合多线程共享同一资源。

实现Runnable接口:

public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "Hello");
        }
    }
}

将该实例作为目标对象传入Thread类并调用start()启动

public class ThreadDemo {
    public static void main(String[] args) {
        MyRun myRun = new MyRun();

        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

        t1.setName("线程一");
        t2.setName("线程二");

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

利用Callable接口和Future接口的方式实现

实现Callable接口并重写其call()方法以定义带返回值和可抛出异常的任务,再通过FutureTask类包装后交由Thread启动或直接提交给线程池,最后利用Future接口的get()方法阻塞获取异步计算结果,从而支持返回值、异常捕获和任务取消等扩展功能。

实现Callable接口

public class MyCallable implements Callable<Integer> {
    /*
    * 求1-100的整数和*/
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

通过FutureTask类包装后交由Thread启动或直接提交给线程池,最后利用Future接口的get()方法阻塞获取异步计算结果,从而支持返回值

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        Integer sum = futureTask.get();
        System.out.println(sum);
    }
}

三种实现方式的对比

常见的成员方法

setName(String name)

细节:

  • 如果没有给线程设置名字,线程也是有默认的名字的
    格式:Thread-X(X序号,从0开始)
  • 如果要给线程设置名字,可以用set方法进行设置,也可以用构造方法设置
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        System.out.println(myThread1.getName());//Thread-0

        MyThread myThread2 = new MyThread();
        myThread2.setName("线程1");
        System.out.println(myThread2.getName());//线程1

        MyThread myThread3 = new MyThread("线程二");
        System.out.println(myThread3.getName());//线程二
    }
}

currentThread()

细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在之前,写的所有的代码,其实都是运行在main线程中的

public class ThreadDemo2 {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        System.out.println(t.getName());//main
    }
}

sleep(long time)

细节:

  • 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
  • 方法的参数:就表示睡眠的时间,单位毫秒
  • 当时间到了之后,线程就会自动的醒来,继续执行下面的代码
public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.currentThread();
        System.out.println("我要睡眠了");
        long start = System.currentTimeMillis();
        Thread.sleep(5000);
        long end = System.currentTimeMillis();
        System.out.println("我醒了");
        System.out.println(t.getName() + "睡眠了" + (start - end) + "毫秒");//main睡眠了-5003毫秒
    }
}

线程的优先级

线程的优先级默认为5,最小为1.最大为10

优先级的大小不能完全决定cpu的抢占,只是概率问题

守护进程

守护线程是在后台运行的辅助性线程(如JVM的垃圾回收),其核心特征是不会阻止JVM退出——当所有非守护(用户)线程执行完毕后,无论守护线程是否仍在运行,JVM都会强行终止它们并直接关闭程序。

出让线程

出让线程通过Thread.yield()方法使当前线程从运行状态主动退回到就绪状态,向线程调度器提示愿意让出当前CPU执行权以允许同优先级或更高优先级的其他线程获得运行机会,但该操作仅为建议且不释放任何锁资源。

插入线程

线程插入通过调用目标线程的join()方法,将目标线程“插入”到当前线程之前强制执行,迫使当前线程进入阻塞等待状态,直到目标线程运行结束或达到指定的超时时间,从而实现线程执行的顺序合并与同步协调。

线程的生命周期

线程安全

同步代码块

synchronized同步代码块通过获取指定对象的监视器锁(Monitor Lock),强制线程串行执行括号内的临界区代码,既保证了操作的原子性,又通过锁释放时的“happens-before”规则确保变量修改对后续获取锁的线程立即可见,从而解决多线程并发安全问题。

public class MyThread extends Thread{
    private static int ticket = 0;
    //卖票
    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (ticket < 100) {
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(getName() + "正在卖第" + ++ticket + "张票!");
                } else {
                    break;
                }
            }
        }
    }
}

同步方法

public class MyRunnable implements Runnable{
    int ticket = 0;
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (tickets()){
                break;
            }
        }

    }

    private synchronized boolean tickets() {
        if (ticket == 100) {
            return true;
        } else {
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ++ticket + "张票");
        }
        return false;
    }
}

lock锁

Lock锁是基于AQS(抽象队列同步器)实现的显式同步接口,需在try-finally代码块中手动执行lock()unlock(),相比synchronized提供了tryLock非阻塞尝试、超时等待及可中断获取等更灵活的线程控制能力。

public class MyThread extends Thread{
    private static int ticket = 0;
    static Lock lock = new ReentrantLock();
    //卖票
    @Override
    public void run() {
        while (true) {
            try {
                sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            lock.lock();
            try {
                if (ticket < 100) {
                    System.out.println(getName() + "正在卖第" + ++ticket + "张票!");
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

死锁

死锁是并发程序中的一种僵局状态,指两个或多个线程因各自持有对方所需的系统资源且相互等待(满足互斥、持有并等待、不可抢占、循环等待四个必要条件),导致所有参与线程永久阻塞而无法继续推进,最终引发应用无响应或系统挂起的严重故障。

代码示例:

public class MyThread extends Thread{
static Object obj1 = new Object();
static Object obj2 = new Object();

@Override
public void run() {
while (true) {
if ("线程A".equals(getName())) {
synchronized (obj1) {
System.out.println("线程A拿到了1锁,准备拿2锁");

synchronized (obj2) {
System.out.println("线程A拿到了2锁,顺利执行");
}
}

} else if ("线程B".equals(getName())) {
synchronized (obj2) {
System.out.println("线程B拿到了2锁,准备拿1锁");
synchronized (obj1) {
System.out.println("线程B拿到了1锁,顺利执行");
}
}
}
}
}
}

等待唤醒机制

等待唤醒机制是线程间基于共享锁对象的协作通信模型,线程在条件不满足时主动释放锁并进入阻塞等待(wait/await),待其他线程改变条件后发出通知(notify/signal),被唤醒线程需重新竞争锁并循环校验条件后才能继续执行,以此避免无效自旋并解决生产者-消费者等协同场景问题。

案例:

public class Foodie extends Thread{
    @Override
    public void run() {
        //循环
        while (true) {
            synchronized (Desk.lock) {
                //判断自己还能不能吃
                if (Desk.count == 0) {
                    //吃不了了
                    break;
                }
                //还能吃,判断桌上是否有食物
                if (Desk.foodFlag == 0) {
                    //没食物,等待
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    //有,就吃,更新状态
                    Desk.count--;
                    System.out.println(getName() + "吃了一碗,还能吃" + Desk.count + "碗!!!");
                    Desk.foodFlag = 0;
                    // 唤醒厨师
                    Desk.lock.notify();
                }
            }
        }
    }
}
public class Cook extends Thread{
    @Override
    public void run() {
        synchronized (Desk.lock){
            while (true){
                if(Desk.count == 0){
                    break;
                }
                if(Desk.foodFlag == 1) {
                    //等待
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    //处理业务
                    Desk.foodFlag = 1;
                    System.out.println(getName() + "做好了食物,快来吃吧");
                    Desk.lock.notify();
                }
            }
        }
    }
}
public class Desk {
    public static int foodFlag = 0;
    public static Lock lock = new ReentrantLock();
    public static int count = 10;
}

阻塞队列

阻塞队列(BlockingQueue)是JUC包下支持阻塞操作的线程安全队列,当队列为空时,获取(take)操作会阻塞,当队列满时,插入(put)操作会阻塞,它内部自动封装了wait/notify逻辑,让你无需手写复杂的等待唤醒机制即可优雅实现生产者-消费者模式

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

// 生产者(厨师)
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
String food = "面条" + i;
queue.put(food); // 队列满时阻塞
System.out.println("厨师做好了: " + food);
}
} catch (InterruptedException e) {}
}).start();

// 消费者(食客)
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
String food = queue.take(); // 队列空时阻塞
System.out.println("食客吃完了: " + food);
}
} catch (InterruptedException e) {}
}).start();
}
}

线程的状态

线程池

原理:

实现:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建一个固定大小为 3 的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 2. 提交 5 个任务给线程池执行
        for (int i = 1; i <= 5; i++) {
            int taskId = i;
            pool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskId);
                try {
                    Thread.sleep(500); // 模拟业务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 3. 关闭线程池(不再接收新任务,等待已有任务执行完毕)
        pool.shutdown();
    }
}

自定义线程池的七个参数:

底层原理:

demo:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        // 1. corePoolSize:核心线程数(2个)
        // 2. maximumPoolSize:最大线程数(5个)
        // 3. keepAliveTime:非核心线程空闲存活时间(10秒)
        // 4. unit:时间单位(秒)
        // 5. workQueue:阻塞队列(容量为3的ArrayBlockingQueue)
        // 6. threadFactory:自定义线程工厂(设置线程名和优先级)
        // 7. handler:拒绝策略(自定义打印日志)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                              // 核心线程数
                5,                              // 最大线程数
                10L,                            // 空闲存活时间
                TimeUnit.SECONDS,               // 时间单位
                new ArrayBlockingQueue<>(3),    // 任务队列(容量3)
                new CustomThreadFactory(),      // 自定义线程工厂
                new CustomRejectedHandler()     // 自定义拒绝策略
        );

        // 提交 10 个任务(触发拒绝策略)
        // 执行逻辑:
        // - 第1、2个:由核心线程(core=2)执行
        // - 第3、4、5个:放入队列(容量3)
        // - 第6、7、8个:创建非核心线程(最大到5)
        // - 第9、10个:队列已满 + 线程已达最大数 → 触发拒绝策略
        for (int i = 1; i <= 10; i++) {
            int taskId = i;
            pool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
                try {
                    Thread.sleep(2000); // 模拟耗时,保证队列堆积
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        pool.shutdown();
    }

    /**
     * 自定义线程工厂(参数6):给线程起有意义的名称
     */
    static class CustomThreadFactory implements ThreadFactory {
        private final AtomicInteger threadNumber = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "MyPool-Thread-" + threadNumber.getAndIncrement());
            t.setDaemon(false);      // 非守护线程
            t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    /**
     * 自定义拒绝策略(参数7):当任务过多时打印警告,防止静默丢失
     */
    static class CustomRejectedHandler implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("【拒绝警告】任务 " + r.toString() + " 被拒绝,队列已满且线程数已达上限!");
        }
    }
}

最大并行数

最大并行数是指系统在同一时刻能真正同时运行的任务数量上限,物理上受 CPU 核心数(Runtime.getRuntime().availableProcessors())制约;而线程池的 maximumPoolSize 仅是逻辑上的并发上限,超过物理核心数的线程只能通过时间片轮转并发执行(而非真正并行),因此对于 CPU 密集型任务,最大并行数建议设置为 CPU 核心数,避免频繁上下文切换导致性能下降。

线程池多大合适:

总结

根据java基础课程,多线程和JUC的知识就到这里了,但是我觉得很多东西都没有学到,所以我打算再去找一份专门针对java多线程JUC的学习课程进行进一步的学习。

文末附加内容
暂无评论

发送评论 编辑评论


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