线程学习(20)-使用多线程间隔打印字符串

假设我现在有三个字符串,abcdefg,1234567,!@#$%^&,我期望使用多线程来进行打印,打印的结果为a1!b2@c3#d4$e5%f6^g7&。这也是道面试题。

有三种方案,synchronized,ReentrantLock结合Condition,LockSupport的park,unpark方法三种。

这个题的思路是要做什么,使用多线程,并且控制执行线程的执行顺序,来完成对结果的打印。

控制线程执行顺序,肯定要用到线程的通信机制。

Synchronized方式

synhronized中配合锁对象中的wait()以及notify,notifyAll方法,可以完成这种机制。但是notify只可能唤醒一个随机的线程,这个肯定不能用。而notifyAll会唤醒所有当前锁的wait线程,那么为了让唤醒的线程是我想执行的,那么就需要一个条件状态来进行控制。举个例子。

比如现在定义了个线程共享的成员变量,该变量有三个状态,0,1,2,有三个线程A,B,C来分别对应这三个状态,执行这三种状态的数据。我现在的想法是A执行完成后,执行B,接着执行C,再执行A的这种闭环操作,直至最终执行完成。那么这个变量,初始值为0,让它进入了我A线程,在执行完A线程的操作后,将该变量置为1,调用notifyAll方法,唤醒B,C线程,此时B,C线程判断该变量是否满足自身的执行条件,B发现满足,接着走下面的方法。C发现不满足,继续调用wait方法阻塞。

Lock+Condition方式

与Condition不同,ReentrantLock中可以包含多个Condition,类似Synchronized中的waitSet。那么就有这么一个思路了。现在有A,B,C三个线程,分别轮询执行三个字符串。那么我也可以定义三个Condition,默认情况下都是await阻塞状态。在主线程中,我先把A线程对应的confition使用signal方法来进行唤醒,执行A的业务逻辑,在A的业务逻辑执行完成,在A线程中唤醒B的condition,此时B线程开始执行。然后B的业务执行完成后,唤醒C的condition中signal方法。依次执行,直至程序执行完成。

LockSupport中的park,unpark方式

这种方式和Lock+Condition方式类似。只不过Lock+Condition唤醒的是一个Condition中的唯一一个线程,而park,unpark直接操作于线程。但在执行过程中存在个问题,使用unpark(线程名)唤醒线程时,会出现线程为空的现象,这个得后续去好好的了解下。

代码

package com.bo.threadstudy.four;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ThreadPrintDataTest {
    //多线程打印交替数据问题
    private static String a = "abcdefg";
    private static String b = "1234567";
    private static String c = "!@#$%^&";

    /**
     * 使用Synchronized来解决打印数据交错问题
     */
    @Test
    public void synchronizedTest() throws InterruptedException {
        Object lock = new Object();
        char[] aArr = a.toCharArray();
        char[] bArr = b.toCharArray();
        char[] cArr = c.toCharArray();

        //这种方案该怎么处理,设置一个状态,在a打印完成后,状态设置为b,b打印完成之后,状态设置为c,c打印完成后,状态设置为a
        //状态设置为0,1,2
        new Thread(() -> {
            for (int i = 0; i < aArr.length; i++) {
                printSynchronized(aArr, 0, i, lock);
            }
        }, "t1").start();
        new Thread(() -> {
            for (int i = 0; i < aArr.length; i++) {
                printSynchronized(bArr, 1, i, lock);
            }

        }, "t2").start();
        new Thread(() -> {
            for (int i = 0; i < aArr.length; i++) {
                printSynchronized(cArr, 2, i, lock);
            }
        }, "t3").start();

        while (true) {
            Thread.sleep(1000);
        }


    }

    private volatile Integer status = 0;

    /**
     * synchronized轮询打印的状态
     *
     * @param arr       数组
     * @param curStatus 当前状态
     * @param index     index下标
     * @param lock      使用的锁
     */
    private void printSynchronized(char[] arr, int curStatus, int index, Object lock) {
        //状态应该是公用的
        synchronized (lock) {
            //状态不相等,先行阻塞
            while (curStatus != status) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //状态相等的情况
            System.out.print(arr[index]);
            if (status == 2) {
                status = 0;
            } else {
                status++;
            }
            lock.notifyAll();
        }
    }


    /**
     * 使用Lock配合Condition来进行使用
     */
    @Test
    public void lockConditionTest() throws InterruptedException {
        //这个思路可以采用,一个Lock名下有多个Condition,不同的元素放到不同的condition中,完成操作
        ReentrantLock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();

        char[] aArr = a.toCharArray();
        char[] bArr = b.toCharArray();
        char[] cArr = c.toCharArray();


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < aArr.length; i++) {
                    printCondition(condition1, condition2, aArr, i, lock);
                }
            }
        }, "t1");
        t1.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < aArr.length; i++) {
                    //waitSet存放的是线程ID,这个里面存放的应该也是线程ID,它肯定只存放一个线程ID的
                    printCondition(condition2, condition3, bArr, i, lock);
                }

            }
        }, "t2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < aArr.length; i++) {
                    printCondition(condition3, condition1, cArr, i, lock);
                }
            }
        }, "t3").start();


        //condition和lock看来与synchronized调用类似,必须放到lock方法中访问,明白了
        lock.lock();
        try {
            condition1.signal();
        } finally {
            lock.unlock();
        }


        Thread.sleep(100000);

    }

    //老师写的面向对象,比我这个写的要好看,我没有面向对象的思想,不过思路对的
    private void printCondition(Condition curCondition, Condition nextCondition, char[] arr, int index, ReentrantLock lock) {
        lock.lock();
        try {
            //当前Condition正在等待
            curCondition.await();
            System.out.print(arr[index]);
            nextCondition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 基于LockSupport的park,unpark机制来完成
     */
    static Thread t1;
    static Thread t2;
    static Thread t3;

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        char[] aArr = a.toCharArray();
        char[] bArr = b.toCharArray();
        char[] cArr = c.toCharArray();
        //初始状况下都是暂停的,在外部控制来给他放行,老师封装的目的主要是为了线程可以初始化完成吗?

        //这么写会存在一个问题,来线程对象未定义好的时候,就唤醒,这中方式是不对的
        t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c1 : aArr) {
                    lockSupportPrint(c1,lock, Thread.currentThread(), t2);
                }
            }
        });


        t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c1 : bArr) {
                    lockSupportPrint(c1,lock, Thread.currentThread(), t3);
                }
            }
        });


        t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (char c1 : cArr) {
                    lockSupportPrint(c1,lock,Thread.currentThread(), t1);
                }
            }
        });
        //线程启动顺序的问题
        //TODO 这里存在个问题,线程变量必须定义成静态变量,start()启动必须得放到后面,要不传入的线程对像可能为空,
        //后期去看看什么问题吧
        t1.start();
        t2.start();
        t3.start();

        LockSupport.unpark(t1);


    }
    //不需要锁,写懵了
    private static void lockSupportPrint(char value, ReentrantLock lock, Thread curThread, Thread nextThread){
        //暂停当前线程
        LockSupport.park();
        System.out.print(value);
        //唤醒下一个线程
        LockSupport.unpark(nextThread);
    }


}

第四天的课程终于完了,下来准备第五天的课程,除了第八天,剩下的都不多了,加油,尽快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用线程来实现数字时钟,可以使用Python中的threading模块来创建线程。 在每个线程中,可以使用time模块来获取当前时间,并将其格式化为不同的时间格式,例如24小时制、12小时制、带有日期的格式等等。然后,可以将格式化后的时间信息输出到控制台或者GUI界面上。 在主线程中,可以创建多个子线程来分别输出不同格式的时间信息,从而实现多种格式的输出。同时,可以使用time.sleep()函数来控制每个线程的输出频率,以达到数字时钟的效果。 总之,使用线程可以方便地实现数字时钟,并且可以输出多种格式的时间信息,提高用户体验。 ### 回答2: 用多线程实现数字时钟可以通过创建多个线程来实现不同格式的时间输出。每个线程负责获取当前时间并以特定的格式输出。 首先,可以创建一个线程用于获取当前时间,以确保所有线程都使用同一时间。可以使用time模块的localtime函数获取当前时间,并将其保存在一个全局变量中。 然后,可以创建多个线程用于不同的时间格式输出。每个线程在一个无限循环中运行,不断获取当前时间并以不同的格式输出。可以使用time模块的strftime函数将时间格式化为字符串,并使用print函数输出。 例如,可以创建一个线程用于输出24小时制的时间,另一个线程用于输出12小时制的时间,还可以创建一个线程用于输出带有日期的时间等等。 在每个线程中,可以使用time模块的sleep函数设置线程的休眠时间,以控制时间输出的间隔。 最后,需要在主线程中启动所有的时间输出线程,并等待它们完成。可以使用threading模块的Thread类创建线程对象,并使用start方法启动线程。 下面是一个简单的示例: ```python import time import threading # 全局变量,保存当前时间 current_time = None # 获取当前时间的线程函数 def get_time(): global current_time while True: current_time = time.localtime() time.sleep(1) # 输出24小时制时间的线程函数 def format_24h(): while True: if current_time: formatted_time = time.strftime("%H:%M:%S", current_time) print("24小时制时间:", formatted_time) time.sleep(1) # 输出12小时制时间的线程函数 def format_12h(): while True: if current_time: formatted_time = time.strftime("%I:%M:%S %p", current_time) print("12小时制时间:", formatted_time) time.sleep(1) # 创建并启动线程 thread_get_time = threading.Thread(target=get_time) thread_format_24h = threading.Thread(target=format_24h) thread_format_12h = threading.Thread(target=format_12h) thread_get_time.start() thread_format_24h.start() thread_format_12h.start() # 等待线程结束 thread_get_time.join() thread_format_24h.join() thread_format_12h.join() ``` 上述示例定义了三个线程函数,分别用于获取当前时间、以24小时制输出时间和以12小时制输出时间。然后在主线程中创建并启动这三个线程,并使用join方法等待它们完成。这样就可以实现多线程输出不同格式的时间信息了。 ### 回答3: 使用线程实现数字时钟可以提高时钟的显示精度和同时进行多个格式的输出。在多线程中,可以使用一个线程负责获取系统时间,另一个线程负责将时间格式化成不同的输出格式,并进行显示。 首先,创建一个获取系统时间的线程,使用系统提供的时间函数获取当前的时、分、秒信息。然后,将这些信息传递给输出线程。 接下来,创建一个输出线程,该线程负责将传递过来的时间信息格式化为不同的输出格式。可以根据需求,格式化为24小时制、12小时制、带有毫秒的格式等。可以使用字符串拼接的方法来实现格式化。同时,在不同格式输出之间加上线程调度,可以实现多格式输出的效果。 最后,将格式化后的时间信息输出。可以通过显示在终端上、输出到文件、通过网络发送等方式来进行显示。可以使用多种输出方式,使得用户可以选择合适的方式来查看时间信息。 需要注意的是,多线程的实现需要考虑线程同步的问题。例如,在传递和处理时间信息时,需要使用锁来避免数据竞争。此外,也需要考虑线程的调度和优先级等问题,以保证时钟的准确性和稳定性。 通过以上的多线程实现,可以实现数字时钟的多种格式输出。不仅可以提供用户多种选择的时间显示方式,还可以增加程序的灵活性和可扩展性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值