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。线程释放。












目录
相关文章
|
3天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
20 0
|
1天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
1天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
2天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
6 0
|
2天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
2天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
2天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
15 0
|
2天前
|
监控 安全 Java
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
10 2
|
2天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
28 1
|
2天前
|
安全 Java
Java基础教程(15)-多线程基础
【4月更文挑战第15天】Java内置多线程支持,通过Thread类或Runnable接口实现。线程状态包括New、Runnable、Blocked、Waiting、Timed Waiting和Terminated。启动线程调用start(),中断线程用interrupt(),同步用synchronized关键字。线程安全包如java.util.concurrent提供并发集合和原子操作。线程池如ExecutorService简化任务管理,Callable接口允许返回值,Future配合获取异步结果。Java 8引入CompletableFuture支持回调。

热门文章

最新文章