多线程循环打印

场景:两个线程交替打印字母和数字,效果如下:12ab34cd56ef......

synchronized

使用 synchronized 同步锁和 Object#wait() 和 Object#notifyAll(),在各个线程传入不同的 type 做区分,线程类型和当前打印类型不一致时则 wait。

java 复制代码
package com.example.demo;

public class Main {
    private static volatile int flag = 0; // 0 表示数字线程,1 表示字母线程
    private static int i = 1; // 数字计数器
    private static char c = 'a'; // 字母计数器

    public static void main(String[] args) {
        new Thread(new Print(0), "数字").start();
        new Thread(new Print(1), "字母").start();
    }

    static class Print implements Runnable {
        int type;

        public Print(int type) {
            this.type = type;
        }

        @Override
        public void run() {
            synchronized (Main.class) {
                while (true) {
                    if (flag != type) {
                        try {
                            Main.class.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    if (flag == 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + i++);
                        System.out.println(Thread.currentThread().getName() + ": " + i++);
                        flag = 1;
                    } else if (flag == 1) {
                        System.out.println(Thread.currentThread().getName() + ": " + c++);
                        System.out.println(Thread.currentThread().getName() + ": " + c++);
                        flag = 0;
                    }

                    Main.class.notifyAll(); // 每次操作后唤醒所有线程
                    if (i == 29 || c == 'z' + 1) {
                        break;
                    }
                }
            }
        }
    }
}

Lock

2.1 Lock 初级用法

注意:当线程调用 lock.lock() 时,如果锁已被其他线程持有,当前线程会进入阻塞状态(BLOCKED),不会消耗CPU资源

java 复制代码
package com.example.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private static Lock lock = new ReentrantLock();
    private static volatile int flag = 0;
    private static int i = 1; // 数字计数器
    private static char c = 'a'; // 字母计数器

    public static void main(String[] args) {
        new Thread(new Print(0), "数字").start();
        new Thread(new Print(1), "字母").start();
    }

    static class Print implements Runnable {
        int type;

        public Print(int type) {
            this.type = type;
        }

        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    if (flag == type) {
                        if (flag == 0) {
                            System.out.println(Thread.currentThread().getName() + ": " + i++);
                            System.out.println(Thread.currentThread().getName() + ": " + i++);
                            flag = 1;
                        } else if (flag == 1) {
                            System.out.println(Thread.currentThread().getName() + ": " + c++);
                            System.out.println(Thread.currentThread().getName() + ": " + c++);
                            flag = 0;
                        }
                        if (i == 29 || c == 'z' + 1) {
                            break;
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
        }

    }
}

问题:

  1. 不必要的锁竞争 :线程每次循环都会尝试获取锁,即使当前 flag 不符合自己的类型;
  2. 公平性问题ReentrantLock 默认是非公平锁,可能导致线程饥饿(线程长期抢不到锁);

2.2 Lock 进阶用法

通过 Condition 类实现了类似方法一中线程等待的效果,避免了锁竞争和公平性问题。

java 复制代码
package com.example.demo;

import java.util.concurrent.locks.*;

public class Main {
    private static final Lock lock = new ReentrantLock();
    private static final Condition condition = lock.newCondition();
    private static int flag = 0;
    private static int i = 1;
    private static char c = 'a';

    public static void main(String[] args) {
        new Thread(() -> print(0, "数字"), "数字").start();
        new Thread(() -> print(1, "字母"), "字母").start();
    }

    private static void print(int type, String name) {
        while (true) {
            lock.lock();
            try {
                // 条件不满足时主动等待
                while (flag != type) {
                    condition.await(); // 释放锁并等待,避免竞争
                }
                
                if (type == 0) {
                    System.out.println(name + ": " + i++);
                    System.out.println(name + ": " + i++);
                    flag = 1;
                } else {
                    System.out.println(name + ": " + c++);
                    System.out.println(name + ": " + c++);
                    flag = 0;
                }

                if (i >= 29 || c > 'z') break;

                // 唤醒其他线程
                condition.signalAll();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }
}

此外还可以借助 Condition 类实现精准唤醒

java 复制代码
import java.util.concurrent.locks.*;

public class Main {
    private static final Lock lock = new ReentrantLock();
    private static final Condition c1 = lock.newCondition();
    private static final Condition c2 = lock.newCondition();
    private static int flag = 0;
    private static int i = 1;
    private static char c = 'a';

    public static void main(String[] args) {
        new Thread(() -> print(0, "数字", c1, c2), "数字").start();
        new Thread(() -> print(1, "字母", c2, c1), "字母").start();
    }

    private static void print(int type, String name, Condition cur, Condition next) {
        while (true) {
            lock.lock();
            try {
                // 条件不满足时主动等待
                while (flag != type) {
                    cur.await(); // 释放锁并等待,避免竞争
                }

                if (type == 0) {
                    System.out.println(name + ": " + i++);
                    System.out.println(name + ": " + i++);
                    flag = 1;
                } else {
                    System.out.println(name + ": " + c++);
                    System.out.println(name + ": " + c++);
                    flag = 0;
                }

                if (i >= 29 || c > 'z') break;

                // 精准唤醒下一个线程
                next.signal();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }
}

Semaphore

通过信号量控制

java 复制代码
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.*;

public class Main {
    private static final Lock lock = new ReentrantLock();
    private static Semaphore s1 = new Semaphore(1);
    private static Semaphore s2 = new Semaphore(0);
    private static int flag = 0;
    private static int i = 1;
    private static char c = 'a';

    public static void main(String[] args) {
        new Thread(() -> print(0, "数字", s1, s2), "数字").start();
        new Thread(() -> print(1, "字母", s2, s1), "字母").start();
    }

    private static void print(int type, String name, Semaphore cur, Semaphore next) {
        try {
            while (true) {
                // 如果当前线程对应的信号量是0,则阻塞
                cur.acquire();

                if (type == 0) {
                    System.out.println(name + ": " + i++);
                    System.out.println(name + ": " + i++);
                    flag = 1;
                } else {
                    System.out.println(name + ": " + c++);
                    System.out.println(name + ": " + c++);
                    flag = 0;
                }
                // 释放下一个线程的信号量
                next.release();
                if (i >= 29 || c > 'z') {
                    break;
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
相关推荐
Derek_Smart44 分钟前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP2 小时前
MyBatis-mybatis入门与增删改查
java
孟陬5 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌5 小时前
一站式了解四种限流算法
java·后端·go
华仔啊5 小时前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
也些宝6 小时前
Java单例模式:饿汉、懒汉、DCL三种实现及最佳实践
java
大道至简Edward7 小时前
深入 JVM 核心:一文读懂 Class 文件结构(附 Hex 实战解析)
jvm
Nyarlathotep01137 小时前
SpringBoot Starter的用法以及原理
java·spring boot
wuwen57 小时前
WebFlux + Lettuce Reactive 中 SkyWalking 链路上下文丢失的修复实践
java