当前位置: 首页 > news >正文

Java基础关键_029_线程(二)

目  录

一、定时任务

二、合并线程

1.说明

 2.实例

三、线程生命周期中的 JVM 调度

 1.优先级

(1)说明

(2)实例1 

(3)实例2

 2.让位

(1)说明

(2)实例

 四、线程安全

1.说明

2.实例 

3.同步代码块 

4.问题1

5.问题2

6.问题3

7.问题4


一、定时任务

        通过定时器(Timer)和定时任务(TimerTask),可以实现每间隔一定时间执行一次某部分程序。

public class TimerTaskTest extends TimerTask {@Overridepublic void run() {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");Date date = new Date();String format = dateFormat.format(date);System.out.println("当前时间:" + format);}
}
public class ThreadTest {public static void main(String[] args) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");Date start = null;try {start = simpleDateFormat.parse("2025-03-26 12:50:03 789");} catch (ParseException e) {throw new RuntimeException(e);}Timer timer = new Timer(true);  // 创建一个定时器,无参构造方法表示创建一个非守护线程,有参构造方法参数若为true,表示创建一个守护线程timer.schedule(new TimerTaskTest(), start, 1000);for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}


二、合并线程

1.说明

  1. 调用 join 方法完成线程合并,该方法是一个实例方法;
  2. 假设在主线程中调用分支线程对象的 join 方法,会将分支线程合并到主线程,主线程进入阻塞状态。直到分支线程执行结束,主线程阻塞状态才被解除;
  3. join 方法 与 sleep 方法的比较:
    1. sleep() 是静态方法,join() 是实例方法;
    2. sleep() 可以指定休眠时长,而 join() 不能保证阻塞时长;
    3. 两者都是让当前线程进入阻塞状态;
    4. sleep() 解除阻塞条件:时间到;
    5. join() 解除阻塞条件:调用该方法的线程结束。

 2.实例

public class MyClassThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {Thread.currentThread().setName("分支线程");System.out.println(Thread.currentThread().getName() + ":" + i);}}
}
public class ThreadTest {public static void main(String[] args) {MyClassThread thread = new MyClassThread();thread.start();System.out.println("主线程开始执行");try {thread.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for (int i = 0; i < 10; i++) {Thread.currentThread().setName("主线程");System.out.println(Thread.currentThread().getName() + ":" + i);}System.out.println("主线程执行结束");}
}


三、线程生命周期中的 JVM 调度

 1.优先级

(1)说明

  1. 线程是可以设置优先级的,优先级较高的,抢占 CPU 时间片的概率较高,仅仅是概率,并不是一定;
  2. JVM 采取的是抢占式调度模型;
  3. 在 Java 中,默认情况,一个线程的优先级是 5 。最低是 1 ,最高是 10 。

(2)实例1 

public class ThreadTest {public static void main(String[] args) {int maxPriority = Thread.MAX_PRIORITY;int minPriority = Thread.MIN_PRIORITY;int normalPriority = Thread.NORM_PRIORITY;System.out.println("最高优先级:" + maxPriority);System.out.println("最低优先级:" + minPriority);System.out.println("默认优先级:" + normalPriority);int mainPriority = Thread.currentThread().getPriority();System.out.println("主线程优先级:" + mainPriority);System.out.println("============================");Thread.currentThread().setPriority(7);mainPriority = Thread.currentThread().getPriority();System.out.println("主线程优先级:" + mainPriority);}
}


(3)实例2

public class MyClassThread extends Thread {int count = 0;  // 为了展现结果时方便比较,特别加此属性,无其他特殊意义@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.print(Thread.currentThread().getName() + ":" + i + "\t");count++;if (count == 6) {System.out.println();}}}
}
public class ThreadTest {public static void main(String[] args) {MyClassThread t1 = new MyClassThread();MyClassThread t2 = new MyClassThread();t1.setName("t1");t2.setName("t2");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();}
}


 2.让位

(1)说明

  1. 通过 yield 方法实现让位,该方法是一个静态方法;
  2. 该方法会让当前线程让位;
  3. 让位不会让线程进入阻塞状态,只是放弃目前占有的 CPU 时间片,进入到就绪状态,继续抢占 CPU 时间片;
  4. 只能保证在概率上,大概率到了某个节点让位一次。

(2)实例

public class MyClassThread extends Thread {int count = 0;  // 为了展现结果时方便比较,特别加此属性,无其他特殊意义@Overridepublic void run() {for (int i = 0; i <= 10; i++) {System.out.print(Thread.currentThread().getName() + ":" + i + "\t");if ("t1".equals(Thread.currentThread().getName()) && (i % 2 == 0)) {Thread.yield();System.out.println("[" + Thread.currentThread().getName() + " yield,下标是:" + i + "]");count++;}if (count == 3) {System.out.println();}}}
}
public class ThreadTest {public static void main(String[] args) {MyClassThread t1 = new MyClassThread();MyClassThread t2 = new MyClassThread();t1.setName("t1");t2.setName("t2");t1.start();t2.start();}
}


 四、线程安全

1.说明

  1. 需要考虑线程安全的情况:
    1. 多线程并发;
    2. 有共享数据;
    3. 共享数据涉及修改操作。
  2. 一般情况下:
    1. 局部变量不存在该问题,存储在栈中,栈是私有的,不共享。特别是基本数据类型;
    2. 实例变量可能存在该问题,因为实例变量存储在堆中,堆内存是多线程共享的;
    3. 静态变量可能存在该问题,因为静态变量也是存储在堆内存中。
  3. 线程排队执行即线程同步机制,线程并发即线程异步机制。异步效率高,但是可能存在安全隐患

2.实例 

        前情提要:假设小明和小红夫妻双方拥有同一张银行卡,并且两人分别对这张卡绑定了微信支付和支付宝支付。有一天在同一时间,恰巧两人用这个账户同时支付 500 元。如果没有线程安全保障,小明扣款成功的信息没有传回银行服务器,而小红也申请支付,那么会导致两人总共扣款500元。示例如下:

public class BankAccount {private long accountNumber;private double balance;public BankAccount() {}public BankAccount(long accountNumber, long balance) {this.accountNumber = accountNumber;this.balance = balance;}public long getAccountNumber() {return accountNumber;}public void setAccountNumber(long accountNumber) {this.accountNumber = accountNumber;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}/** 取款* */public void withdraw(double money) {double oldBalance = this.getBalance();System.out.println(Thread.currentThread().getName() + "准备取款" + money + "元,当前账户余额为:" + oldBalance);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}this.setBalance(oldBalance - money);System.out.println(Thread.currentThread().getName() + "完成取款" + money + "元,当前账户余额为:" + this.getBalance());}
}
public class Withdraw implements Runnable{private BankAccount account;public Withdraw(BankAccount account) {this.account = account;}@Overridepublic void run() {account.withdraw(500);}
}
public class ThreadTest {public static void main(String[] args) {BankAccount bankAccount = new BankAccount(123456789, 10000);Thread t1 = new Thread(new Withdraw(bankAccount));Thread t2 = new Thread(new Withdraw(bankAccount));t1.setName("小明");t2.setName("小红");t1.start();t2.start();}
}


3.同步代码块 

        语法

        synchronized (需要排队的几个线程共享的对象){

                // 同步代码块

        }

        为什么会造成这样严重的损失问题呢?

        因为小明和小红两个线程不是同步的,只要修改两线程为同步操作,就避免了这种问题的发生。那么如何实现这种线程同步机制呢? 

        一个方法是可以使用 synchronized 关键字。原理:假设某个对象是 t1、t2 两个线程共享的,两个线程一定有一个先抢占到 CPU 时间片。假设 t1 先抢占到,则会去寻找共享对象的对象锁,找到后立即占有,占有对象锁就有权执行同步代码块内的代码。而 t2 则需要等待 t1 执行完同步代码块,释放该对象锁。

        需要修改 BankAccount.java 文件,示例如下:

public class BankAccount {private long accountNumber;private double balance;public BankAccount() {}public BankAccount(long accountNumber, long balance) {this.accountNumber = accountNumber;this.balance = balance;}public long getAccountNumber() {return accountNumber;}public void setAccountNumber(long accountNumber) {this.accountNumber = accountNumber;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}/** 取款* */public void withdraw(double money) {synchronized (this) {double oldBalance = this.getBalance();System.out.println(Thread.currentThread().getName() + "准备取款" + money + "元,当前账户余额为:" + oldBalance);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}this.setBalance(oldBalance - money);System.out.println(Thread.currentThread().getName() + "完成取款" + money + "元,当前账户余额为:" + this.getBalance());}}
}

        注意:控制同步代码块的范围,其范围越小,效率越高。 另外,也可以在实例方法上添加 synchronized 关键字,如此这个实例方法整体便是同步代码块,其共享对象的对象锁一定是 this 。


4.问题1

        题目:下面给出的代码中,m2() 是否会等待 m1() 执行完成后再执行?

public class Method {public synchronized void m1(){System.out.println("m1 开始执行");try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("m1 执行结束");}public void m2(){System.out.println("m2 开始执行");System.out.println("m2 执行结束");}
}
public class MyClassThread implements Runnable {private Method method;public MyClassThread(Method method) {this.method = method;}@Overridepublic void run() {if ("t1".equals(Thread.currentThread().getName())) {method.m1();}if ("t2".equals(Thread.currentThread().getName())) {method.m2();}}
}
public class ThreadTest {public static void main(String[] args) {Method method = new Method();Thread t1 = new Thread(new MyClassThread(method));Thread t2 = new Thread(new MyClassThread(method));t1.setName("t1");t2.setName("t2");t1.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}t2.start();}
}

        答案:答案是否定的,因为 m2() 并没有被 synchronized 关键字修饰,所以无需寻找对象锁。


5.问题2

        题目:若将问题1中的 m2() 也加上 synchronized 关键字,那么 m2() 是否会等待 m1() 执行完成后再执行?

public class Method {public synchronized void m1(){System.out.println("m1 开始执行");try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("m1 执行结束");}public synchronized void m2(){System.out.println("m2 开始执行");System.out.println("m2 执行结束");}
}

         答案:答案是需要,因为只创建了一个 Method 对象,两个线程共享同一个对象,所以只有一个对象锁,所以需要等待。


6.问题3

        题目:已知问题2是因为只创建了一个 Method 对象,导致只有一把对象锁,所以需要等待。那么创建两个 Method 对象,结果会怎样呢?

public class ThreadTest {public static void main(String[] args) {Method m1 = new Method();Method m2 = new Method();Thread t1 = new Thread(new MyClassThread(m1));Thread t2 = new Thread(new MyClassThread(m2));t1.setName("t1");t2.setName("t2");t1.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}t2.start();}
}

        答案: 答案当然是不需要等待了,因为是两个 Method 对象,就会有两个对象锁,各自执行自己的同步代码块。


7.问题4

        题目:若在问题3的基础上,为 m1() 和 m2() 加上 static 关键字修饰,结果又如何呢?

public class Method {public static synchronized void m1() {System.out.println("m1 开始执行");try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("m1 执行结束");}public static synchronized void m2() {System.out.println("m2 开始执行");System.out.println("m2 执行结束");}
}

        答案:答案是 m2() 需要等待 m1() 执行完成再执行。因为当 synchronized 出现在 静态方法上时,线程就会去找类锁,无论创建多少个对象,一个类只有一把类锁。

        由此可得,静态方法上添加 synchronized 关键字,实际是为了保证静态变量的安全 。

在实例方法上添加是为了保证实例变量的安全。


http://www.mrgr.cn/news/96113.html

相关文章:

  • Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出
  • linux - centos7 部署 redis6.0.5
  • Echarts使用
  • 字节跳动前端开发实习生面试总结
  • 蓝桥杯高频考点——二分(含C++源码)
  • QT自运行程序
  • 海康HTTP监听报警事件数据
  • Docker+Ollama+Xinference+RAGFlow+Dify+Open webui部署及踩坑问题
  • 2.4 Gannt图【甘特图】
  • EMQX Dashboard
  • 运行前端项目报错解决方法
  • 计算机组成原理的学习day01
  • 【悲观锁和乐观锁有什么区别】以及在Spring Boot、MybatisPlus、PostgreSql中使用
  • 蓝桥杯 第十二天 819 递增序列
  • el-table单元格编辑,动态增删行,回车/上下左右箭头切换单元格
  • Dubbo 全面解析:从 RPC 核心到服务治理实践
  • SpringBoot整合Log4j2进行日志记录异步写入日志文件
  • 【windows搭建lvgl模拟环境(一)之VSCode】
  • 协作机械臂需要加安全墙吗? 安全墙 光栅 干涉区
  • docker中间件部署