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












  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@SmartSi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值