java如何实现异步处理
1、 线程的同步
当多个线程需要访问同一个资源时,需要保证线程之间的同步,以免造成资源破坏。例如多个线程需要操作一个共享资源,先会把资源复制到自身线程空间,对副本操作完成后, 在将数据写回。如图 2-2 所示,当线程 1 将共享变量 v 取出进行数据操作,但是在还没有将数据写回时,线程 2 此时也访问 v,将导致线程 2 获取到的 v 不是最新结果,那么当线程 2 操作完,将 v 写回时,将会造成 v 的混乱和破坏,无法达到预期结果。

2、因此,如果没有对共享资源进行保护,共享资源将会破坏,每个线程读取到的数据将不能保证是最新数据。对于对多线程访问共享资源问题,Java 提供了同步机制,对访问的资源对象进行锁定。常用的同步方法有:同步块、同步方法、可重入锁 ReentrantLock。
同步块指将需要同步的代码包含在 synchronized 块中。则被锁定的对象在任何时刻只能被一个线程访问,其他线程需要等待使用被锁定对象的线程执行离开后才能访问被锁定对象。例如:
synchronized(System.out) {
for(int i=0; i<10;++i){ System.out.print(Thread.currentThread().getName());
}
System.out.println();
}

3、上述代码将会对 System.out 对象进行锁定,即 System.out 对象在 synchronized 代码块中只能被一个线程单独访问。这样保证了结果屏幕输出不会出现混乱。
同步方法指在声明方法时,在方法前面(public 关键词之后)添加关键词 synchronized,表示对当前对象(this 引用)同步整个方法,将将整个方法进行锁定。例如:
public synchronized void writeData(String msg) throws IOException{ bw.write(msg);
bw.write("\r\n");
}
4、在上面代码中,如果没有关键词 synchronized,多个线程写文件时,将无法内容保证写入数据的顺序。对 writeData( )进行同步之后,将可保证在每个线程添加数据到文件时,都会在最后面进行追加,不会对原文件数据造成破坏。
使用 synchronized 虽然可以解决多线程访问共享变量的问题,但也会造成 Java VM 性能的下降。同时还大大增加了死锁的可能。因此,在设计程序时,应该尽可能减少同步,尽可能使用局部变量而不是全局变量。
使用可重入锁 ReentrantLock 也可以对关键代码进行锁定。ReentrantLock 是一个可重入的互斥锁,具有与 synchronized 方法相同的行为,但是功能更加强大。当获取 ReentrantLock
5、锁的线程没有释放该锁时,其他调用 lock 的线程将进入等待状态。等到拥有锁的线程释放锁之后,才能往下执行。典型的使用 lock 的代码如下:
class LockDemo {
//可重入锁对象
private final ReentrantLock lock = new ReentrantLock();
// ...
public void method() {
lock.lock(); // 当前线程进入阻塞状态,直到获取锁
try {
// ... 方法主体
} finally {
lock.unlock(); //释放锁,放在 finally 中保证锁一定会被释放,避免产生死锁
}
}
}
6、2.2.1 线程之间的协调通信
线程之间的通信是线程编程中的一个重要内容。例如多线程中某个线程需要等待其他线程结束才继续执行、某个线程需要从其他线程获取数据等,都需要用到线程之间的通信。同步也可以看成是线程协作通信的其中一种。本节主要介绍常用线程通信技术:线程之间的参数传递、判断子线程是否运行结束、使用 CountDownLatch 协调子线程。
(1) 线程之间的参数传递。
子线程在运行时如果需要使用到主线程的数据,可以通过以下两种方法实现:(1)使用全局变量;(2)将数据作为子线程构造函数的参数进行传值。
使用全局变量可以通过把线程类作为内部类的方法实现,参考代码如下所示:
// 主类
public class MainClass {
// 共享变量
int v;
// 此处线程类为 MainClass 的内部类
class ThreadClass implements Runnable { public void run() {
doeSomething(v);// 操作全局变量 v
}
}
}
通过线程类构造函数传值则是一种较常用方法,参考代码如下:
// 主类
public class MainClass {
public static void main(String[] args) {
Object param; //需要传递给子线程的参数对象Thread t=new Thread(new ThreadClass(param)); t.start();
}
}
// 内部类
class ThreadClass implements Runnable {
Object param;
public ThreadClass(Object param){ this.param=param;
}
public void run() {
doeSomething(param);// 操作参数 param
}
}
7、应用背景:编写应用程序模拟银行取款。每次取款操作使用一个线程模拟,取出的金额为随机数,如果取出金额小于当前账户余额,则只能取剩余总数,此时余额为 0,程序结束。
程序代码如下所示:
public class WithDrawMoney implements Runnable { static int total; // 总存款
static int left; // 余额
Random rand = new Random();// 随机数生成
// 构造函数
public WithDrawMoney(int t) { this.total = t;
left = total;
}
// 线程主体
public void run() {
if (left > 0) {
// 随机取走金额,为 0-100 的随机整数
int take_out = rand.nextInt(100);
// 如果取钱大于当前余额,则只能取当前的余额数
if (take_out > left) { take_out = left;
Thread
}
System.out.printf("线程%s,目前余额为%d 元,取走了%d 元,剩余%d 元\n",
.currentThread().getName(), left, take_out, left
- take_out); left -= take_out;
}
}
public static void main(String[] args) {
WithDrawMoney bank = new WithDrawMoney(200);
//模拟 50 次取款操作
for (int i = 0; i < 10; ++i) {
Thread t = new Thread(bank); t.start();
}
}
}
程序执行结果如下所示:
如有问题 欢迎在下方提出
如有喜欢的朋友 可以给作者投个票哦 谢谢大家
