Java 多线程练习

本文提供了7个Java编程示例,涵盖定时任务(如电脑定时关机)、按月执行代码、模拟售票、多线程求和问题、线程同步容器、线程死锁模拟以及特定模式的多线程输出。这些示例展示了Java在处理多线程、并发控制和时间调度方面的能力。
摘要由CSDN通过智能技术生成

目录

1.定时器操作(实现电脑定时关机)。

2. 每个月的月末(02:00:00) 执行一次代码

3. 模拟售票

4. 用15个线程实现,求123456789 之间放+-和为100的表达式(11个结果),如果一个线程求出结果, 立即告诉其它停止。

 5. 实现一个容器,提供两个方法,add,count 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。(10个线添加元素,1个线程监控统计)

 6. 编写程序模拟线程死锁

7. 编写程序,实现三个线程,运行输出 A1 B2 C3 A4 B5 C6 ….. 


1.定时器操作(实现电脑定时关机)。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

//指定到2023-08-01 02:00:00关机
public class T1 {
    public static void main(String[] args) {
        // 创建一个新的线程
        Thread t2 = new Thread(() -> {
            // 创建一个日期格式化对象,用于解析和格式化日期和时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            // 初始化一个日期对象
            Date d = null;
            try {
                d = sdf.parse("2023-08-01 02:00:00");  // 解析指定时间的字符串为日期对象
            } catch (ParseException e) {
                e.printStackTrace();
            }
            long end = d.getTime();  // 获取指定时间的毫秒数

            try {
                while (true) {
                    long now = System.currentTimeMillis();  // 获取当前时间的毫秒数

                    if (now >= end) {
                        System.out.println("时间到");  // 时间到,输出一段文字
                        //Runtime.getRuntime() 是 Java 提供的一个静态方法,它返回当前 Java 虚拟机的运行时对象。
                        // 通过调用这个方法,你可以获取一个 Runtime 实例,用于执行系统级操作,如执行外部程序、管理操作系统的进程等。
                        Runtime run = Runtime.getRuntime();
                        run.exec("cmd /k shutdown /s /t 0");  // 执行关机指令
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t2.start();  // 启动线程
    }
}
//指定到2023-08-1 02:00:00关机
public class T2 {
    public static void main(String[] args) throws Exception {
                // 创建一个线程池
                ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

                // 设置关机时间
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date shutdownTime = sdf.parse("2022-02-08 11:09:05");

                // 计算距离关机时间的毫秒数
                long delayInMillis = shutdownTime.getTime() - System.currentTimeMillis();

                // 在指定时间执行关机任务
                executor.schedule(() -> {
                    // 执行关机命令
                    try {
                        // Windows系统关机命令
                        String shutdownCommand = "shutdown -s -t 0";
                        // Linux系统关机命令
                        // String shutdownCommand = "sudo shutdown now";

                        // 执行操作系统命令
                        Runtime.getRuntime().exec(shutdownCommand);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }, delayInMillis, TimeUnit.MILLISECONDS);

                // 关闭线程池
                executor.shutdown();
            }
        }
2. 每个月的月末(02:00:00) 执行一次代码
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;

//每个月的月末(02:00:00)->是8月1日2点
public class T3 {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            while (true) {
                try {
                    // 获取当前日期
                    var c = Calendar.getInstance();

                    // 设置日期为下个月的第一天(月末)
                    c.set(Calendar.MONTH, c.get(Calendar.MONTH) + 1);
                    c.set(Calendar.DAY_OF_MONTH, 0);

                    // 设置特定时间(小时、分钟、秒)
                    c.set(Calendar.HOUR_OF_DAY, 2);
                    c.set(Calendar.MINUTE, 0);
                    c.set(Calendar.SECOND, 0);

                    // 创建日期格式化对象
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

                    // 将设定的时间与当前时间进行比较
                    if (c.getTimeInMillis() == System.currentTimeMillis()) {
                        // -------------- 每月的月末,要执行的代码

                        // 输出设定的日期和时间
                        System.out.println(sdf.format(c.getTime()));
                        System.out.printf("%1$tF %1$tT%n", System.currentTimeMillis());
                        
                        // 使线程睡眠2秒,机器运行速度过快,防止1S内执行多次
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
3. 模拟售票

模拟了多个线程同时售票的场景,保证了并发环境下的票的售卖过程的正确性和一致性。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class T4 {
    List<String> set = new ArrayList<>(); // 票的集合
    int num = 50; // 票的数量,默认为50张

    public T4() {
        this.num = 50;
        for (int i = 0; i < this.num; i++) {
            set.add(String.format("%03d", i + 1)); // 将票号以三位数格式添加到集合中
        }
    }

    public T4(int num) {
        this.num = num;
        for (int i = 0; i < this.num; i++) {
            set.add(String.format("%03d", i + 1)); // 将票号以三位数格式添加到集合中
        }
    }

    public void out() {
        Random rand = new Random();
        String t = Thread.currentThread().getName(); // 获取当前线程的名称
        while (this.set.size() > 0) { // 当票池中还有票时
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟处理过程,让线程睡眠1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (this) { // 使用synchronized关键字实现对票池的同步访问
                try {
                    TimeUnit.SECONDS.sleep(1); // 模拟处理过程,让线程睡眠1秒
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                try {
                    int index = rand.nextInt(this.set.size()); // 随机选择票池中的一个索引
                    String p = this.set.get(index); // 获取对应索引的票号
                    this.set.remove(p); // 从票池中移除该票号
                    if (this.set.size() == 0) {
                        System.out.printf("%s:售出%s票,票已经售完。%n%n", t, p); // 输出剩余最后一张票信息
                    } else {
                        System.out.printf("%s:售出%s票,还有%d张票%n", t, p, this.set.size()); // 输出售票信息
                    }
                } catch (Exception e) {
                    System.out.printf("%s:票已经售完。%n", t); // 输出票已经售完的信息
                }
            }
        }
    }

    public static void main(String[] args) {
        T4 t = new T4();
        new Thread(t::out, " 东风路").start(); // 创建并启动线程
        new Thread(t::out, " 南阳路").start(); // 创建并启动线程
        new Thread(t::out, " 瑞达路").start(); // 创建并启动线程
        new Thread(t::out, "科学大道").start(); // 创建并启动线程
    }
}

4. 15个线程实现,求123456789 之间放+-和为100的表达式(11个结果),如果一个线程求出结果, 立即告诉其它停止。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class T5 {
    //volatile是Java中的关键字,它用于声明一个变量,在多线程环境中确保可见性和有序性。
    //当一个变量被声明为volatile时,线程在访问该变量时会直接从主内存中读取变量的最新值,而不是从线程的工作内存中读取。
    // 并且,对声明为volatile的变量的写操作会立即刷新到主内存中,而不是留存在线程的工作内存中。
    volatile List<String> list = new ArrayList<>(); // 用于存储满足条件的计算字符串

    void op() {
        System.out.printf("%s 启动计算中 %n", Thread.currentThread().getName());

        String[] o = new String[]{"", "+", "-"}; // 符号数组
        Random rand = new Random(); // 随机数生成器
        StringBuffer str = new StringBuffer("1"); // 初始化字符串为"1"

        // 使用正则表达式匹配数字
        Pattern p = Pattern.compile("(\\d+|-\\d+)");

        // 当满足条件的计算字符串数量不为11时,不断生成新的计算字符串
        while (list.size() != 11) {
            for (int i = 2; i < 10; i++) {
                str.append(String.format("%s%d", o[rand.nextInt(o.length)], i)); // 随机生成运算符和数字拼接到计算字符串中
            }

            String s = str.toString(); // 获取生成的计算字符串
            Matcher m = p.matcher(s); // 进行匹配

            List<Integer> ln = new ArrayList<>(); // 用于存储匹配到的数字
            while (m.find()) {
                ln.add(Integer.parseInt(m.group())); // 将匹配到的数字转换为整数并添加到列表中
            }

            int sum = ln.stream().reduce(0, Integer::sum); // 对列表中的数字求和
            if (sum == 100 && !list.contains(s)) { // 如果求和结果为100且列表中不包含该计算字符串,则将其添加到列表中
                list.add(s);
                System.out.printf("[%s]:%s = 100%n", Thread.currentThread().getName(), s);
            } else {
                str.delete(1, str.length()); // 清空计算字符串
            }
        }

        System.out.printf("%s 结束 %n", Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        var t = new T5();
        for (int i = 0; i < 15; i++) {
            new Thread(t::op, "T" + i).start(); // 创建并启动15个线程
        }
    }
}

 5. 实现一个容器,提供两个方法,addcount 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。(1个线程添加元素,1个线程监控统计)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class T6 {
    List<String> list = new ArrayList<>(); // 存储字符串的列表
    CountDownLatch latch = new CountDownLatch(1); // 用于线程同步,初始计数为1

    void add() {
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.SECONDS.sleep(1); // 休眠1秒钟
            } catch (Exception e) {
                e.printStackTrace();
            }

            String msg = String.format("%s-%d", "hello", i); // 格式化字符串
            list.add(msg); // 添加字符串到列表
            System.out.printf("%s : %s%n", Thread.currentThread().getName(), msg); // 输出线程名称和添加的字符串

            if (list.size() == 5) { // 当列表中元素数量达到5时
                latch.countDown(); // 减少计数器的值到0
            }
        }
    }

    void count() {
        try {
            latch.await(); // 等待计数器达到0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("%s 已经有5个元素%n", Thread.currentThread().getName()); // 输出统计信息
    }

    public static void main(String[] args) {
        var t = new T6();

        // 创建并启动添加元素的线程
        new Thread(t::add, "T1").start();

        // 创建并启动统计元素个数的线程
        new Thread(t::count, "T2").start();
    }
}

 6. 编写程序模拟线程死锁

这段代码涉及多线程的同步问题,使用了两个对象a和b作为锁。

在主线程的main方法中,创建了一个T14对象t,然后启动了两个线程T1T2,分别调用tm1m2方法。

m1方法中,线程先获取对象a的锁,然后睡眠1秒,再尝试获取对象b的锁。在synchronized块中,该线程可以安全地访问对象b

m2方法中,线程先获取对象b的锁,然后睡眠1秒,再尝试获取对象a的锁。在synchronized块中,该线程可以安全地访问对象a

这段代码存在死锁的风险,如果线程T1在获取对象a的锁后,睡眠了1秒,然后线程T2在获取对象b的锁后,尝试获取对象a的锁时,由于线程T1还持有对象a的锁,因此线程T2会被阻塞。同样地,线程T1在获取对象b的锁时也会被阻塞,两个线程都无法继续执行下去,导致程序无法正常结束。

import java.util.concurrent.TimeUnit;

public class T7 {
    final Object a = new Object(); // 创建对象a作为锁
    final Object b = new Object(); // 创建对象b作为锁

    // 线程T1的方法
    void m1() {
        System.out.println(Thread.currentThread().getName() + "启动等待...");

        synchronized (a){ // 获取对象a的锁
            try{
                TimeUnit.SECONDS.sleep(1); // 线程睡眠1秒
            }catch(Exception e){
                e.printStackTrace();
            }
            synchronized (b){ // 获取对象b的锁
                // 在此块中安全地访问对象b
            }
        }
    }

    // 线程T2的方法
    void m2() {
        System.out.println(Thread.currentThread().getName() + "启动等待...");

        synchronized (b){ // 获取对象b的锁
            try{
                TimeUnit.SECONDS.sleep(1); // 线程睡眠1秒
            }catch(Exception e){
                e.printStackTrace();
            }
            synchronized (a){ // 获取对象a的锁
                // 在此块中安全地访问对象a
            }
        }
    }

    public static void main(String[] args) {
        var t = new T7(); // 创建T7对象

        // 创建并启动线程T1
        new Thread(t::m1,"T1").start();

        // 创建并启动线程T2
        new Thread(t::m2,"T2").start();
    }
}

7. 编写程序,实现三个线程,运行输出 A1 B2 C3 A4 B5 C6 ….. 
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class T8 {
    AtomicInteger num = new AtomicInteger(0); // 创建一个原子计数器
    ReentrantLock lock = new ReentrantLock(true); // 创建一个公平锁

    // 工作方法
    void work() {
        String tn = Thread.currentThread().getName(); // 获取当前线程名

        while (true) {
            lock.lock(); // 获取锁

            try {
                TimeUnit.SECONDS.sleep(1); // 线程睡眠1秒
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 对计数器进行增加操作,并输出相应的信息
            System.out.printf("%s%d ", tn, num.incrementAndGet());
            //如果当前线程名为"C",则输出换行符。
            if ("C".equals(tn)) {
                System.out.println();
            }
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        var t = new T8(); // 创建T8对象

        // 创建并启动线程A
        var a = new Thread(t::work, "A");
        a.start();

        // 创建并启动线程B
        var b = new Thread(t::work, "B");
        b.start();

        // 创建并启动线程C
        var c = new Thread(t::work, "C");
        c.start();
    }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bridge Fish

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值