目录
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. 实现一个容器,提供两个方法,add,count 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。(10个线添加元素,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
,然后启动了两个线程T1
和T2
,分别调用t
的m1
和m2
方法。在
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();
}
}