Java开发之路之多线程

简介:


类别 方法 简介

线程的创建
Thread()  
Thread(String name)  
Thread(Runable target)  
Thread(Runable target,String name)  



线程的方法
void start() 启动线程
static void sleep(long millis)
线程休眠
static void sleep(long millis,int nanos)
void join()
使其他线程等待当前线程终止
void join(long millis)
void join(long millis,int nanos)
static void yield() 当前运行线程释放处理器资源
获取线程的引用 static Thread currentThread() 返回当前运行的线程引用

1.中断线程

当线程的run方法执行方法体中最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止。

1.1 错误方法stop

在Java的早起版本中,有一个stop方法,其他线程可以调用它终止线程。但是这个方法已经被弃用了。

1.2 interrupt方法

interrupt方法可以被用来请求终止线程。当对一个线程调用 interrupt方法时,线程的中断状态将被置位。这是每一个线程都具有的boolean标志。每个线程都应该不时的检查这个标志,以判断线程是否被中断。

要想弄清楚中断状态是否被置位

(1)对于用Runable接口实现的线程来说首先调用静态的Thread.currentThread方法获得当前线程,然后调用isInterrupted方法:
 
  
package com.qunar.thread;
 
public class RunableDemo implements Runnable{
 
@Override
public void run() {
while(Thread.currentThread().isInterrupted()){
//
}//while
}
}
(2)对于用Thread类实现线程来说直接 调用isInterrupted方法:
 
  
package com.qunar.thread;
 
public class ThreadDemo extends Thread{
 
@Override
public void run() {
while(isInterrupted()){
}//while
}
}

如果 线程被阻塞,就无法检测中断状态。这是产生InterruptedException异常的地方。 当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将会被 InterruptedException异常中断


案例一:
 
  
package com.qunar.thread;
 
public class TicketThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("thread is running...");
long time = System.currentTimeMillis();
// 相当于sleep(1000)
while(System.currentTimeMillis() - time < 1000){}
}//while
}
}

 
  
package com.qunar.test;
 
import com.qunar.thread.TicketThread;
 
public class ThreadTest {
public static void main(String[] args){
TicketThread ticketThread = new TicketThread();
System.out.println("start thread...");
ticketThread.start();
// 主线程休眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 3秒后中断子线程
System.out.println("interrupted thread...");
ticketThread.interrupt();
// 主线程休眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程结束
System.out.println("stop thread...");
}
}
运行结果:
 
  
start thread...
thread is running...
thread is running...
thread is running...
thread is running...
interrupted thread...
thread is running...
thread is running...
thread is running...
stop thread...
thread is running...
thread is running...
thread is running...
thread is running...
thread is running...

从上面的结果来看interrupted方法并没有使线程中断。


案例二:
 
  
package com.qunar.thread;
 
public class TicketThread extends Thread{
@Override
public void run() {
while(!isInterrupted()){
System.out.println("thread is running...");
long time = System.currentTimeMillis();
// 相当于sleep(1000)
while(System.currentTimeMillis() - time < 1000){}
}//while
}
}
运行结果:
 
  
start thread...
thread is running...
thread is running...
thread is running...
thread is running...
interrupted thread...
stop thread...

从上面的运行结果来看 interrupted方法使线程中断,其实质是 当对一个线程调用 interrupt方法时,线程的中断状态将被置位。这是每一个线程都具有的boolean标志。每个线程都应该不时的检查这个标志,以判断线程是否被中断。但是确保线程不被阻塞,否则中断状态检测不出。这就是上面为什么使用
while ( System . currentTimeMillis () - time < 1000 ){} 而不是 sleep(1000)的原因

案例三:

 
  
package com.qunar.thread;
 
public class TicketThread extends Thread{
@Override
public void run() {
while(!isInterrupted()){
System.out.println("thread is running...");
// 线程被阻塞1秒钟
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}//while
}
}
运行结果:
 
  
start thread...
thread is running...
thread is running...
thread is running...
interrupted thread...
thread is running...
java.lang.InterruptedException: sleep interrupted
thread is running...
at java.lang.Thread.sleep(Native Method)
at com.qunar.thread.TicketThread.run(TicketThread.java:11)
thread is running...
thread is running...
stop thread...
thread is running...
thread is running...
thread is running...
thread is running...
如果中断状态置位时,调用sleep方法,它不会休眠。相反,它将 清除这一状态(从结果中看出线程没有终止,一直运行)并且 抛出InterruptedException异常。因此你的while循环调用sleep
函数,不会检测中断状态。相反,要如下所示捕获 InterruptedException异常。
 
  
public void run(){
try{
...
while(more work to do){
do more work
sleep(delay);
}
}
catch(InterruptedException e){
// thread was interrupted during sleep
}
finally{
// cleanup if required
}
}

注释:
  • void interrupt()  
                向线程发送中断请求。线程的中断状态将被设置为true。
                如果目前该线程被一个sleep(long),sleep(long,int),join(),join(long),join(long,int),wait(),wait(long),
                wait(long,int)调 用阻塞,那么,中断状态将被清空,同时InterruptedException异常被抛出。
  • static boolean interrupted() 
                测试当前线程(即正在执行这一命令的线程)是否被中断。
                注意:这是一个静态方法。这一调用会产生副作用-它将当前线程的中断
状态重置为false。
  • boolean isInterrupted() 
                测试线程是否被终止。不像静态的中断方法,这一调用不用改变线程的中断状态。



2.线程同步

如果两个线程存取相同的对象,并且每一个线程都掉用了一个修改该对象状态的方法,将会发生什么?可以想象,线程彼此踩了对方的脚。
这样的一种情况通常称为竞争条件。

2.1 竞争条件的一个例子

为了避免多线程引起的共享数据的混乱,必须学会如何同步存取。

下面的测试程序中,模拟一个有若干账户的银行。随机的生成这些账户之间转移钱的交易。每一个账户有一个线程,每一笔交易中,会从线程服务的账户中随机转移一笔钱款到另一个账户中。

2.2 竞争条件详解

当两个线程试图同时更新同一个帐号的时候,这个问题就出现了。假定两个线程同时执行指令:
 
   
accounts[i] += amount;
问题在于这不是一个原子操作。该指令可能被处理如下:
(1)将accounts[i]加载到寄存器
(2)增加amount
(3)将结果写回accounts[i]

现在,假定第一个线程执行步骤1和步骤2,然后,它被剥夺了运行权。假定第二个线程被唤醒并修改了accounts数组中的同一项。然后,第一个线程被唤醒并完成第三步。
这样这一动作就擦去了第二个线程所做的更新。




2.3 锁对象

有两种机制防止代码块受并发访问的干扰, ReentrantLock和 synchronized关键字。

ReentrantLock保护bank类的实例:
 
   
private Lock bankLock = new ReentrantLock();
 
...
 
// 转移
public void transfer(int from,int to,double amount){
if(accounts[from] < amount){
return;
}//if
// 加锁
bankLock.lock();
try{
System.out.print(Thread.currentThread().getName());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d",amount,from,to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",GetTotalBalance());
}
finally{
// 解锁
bankLock.unlock();
}
}
假定第一个线程调用transfer,在执行结束前被剥夺了运行权。假定第二个线程也调用transfer,由于第二个线程不能获得锁,将在调用lock方法时被阻塞。它必须等待第一个线程完成transfer方法的执行之后才能再度被激活。当第一个线程释放锁时,第二个线程才能开始运行。

注意:

              每一个Bank对象有自己的ReentrantLock对象。
              如果两个线程试图访问同一个Bank对象,那么锁以串行方式提供服务。
              如果两个线程访问不同的Bank对象,每一个线程得到不同的锁对象,两个线程都不会发生阻塞。


              要留心临界区中代码,不要因为异常的抛出而跳出了临界区。如果在临界区代码结束之前抛出了异常,finally子句释放锁,但会使对象处于一种受损的状态。



锁是可重入的,因为线程是可以重复的获得已经持有的锁。锁保持一个持有计数来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。

例如,transfer方法调用GetTotalBalance方法,这也会封锁banklock对象,此时banklock对象的持有计数为2。当 GetTotalBalance方法退出的时候,持有计数变回1。当transfer方法退出时,持有计数变为0。线程释放。












目录
相关文章
|
5天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
6天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
7天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
7天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
7天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
24 3
|
7天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
77 2
|
15天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6
|
15天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
52 2
|
7月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
4月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
71 1