August 18, 2017
在一个异步改同步的工程中,有两种方式,一个是轮询,一个是中断。这里介绍一下中断的操作。
在看这一部分之前你可能先要看下一篇文章,关于synchronized同步锁的问题。下面会用到。
如果你的Java程序中有两个线程——即生产者和消费者,那么生产者可以通知消费者,让消费者开始消耗数据,因为队列缓冲区中有内容待消费(不为空)。相应的,消费者可以通知生产者可以开始生成更多的数据,因为当它消耗掉某些数据后缓冲区不再为满。
wait # Object类中相关的方法有两个notify方法和三个wait方法: 因为wait和notify方法定义在Object类中,因此会被所有的类所继承。 这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。
wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。 线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
与sleep不同的是sleep不会释放锁。
notify # notify()方法会唤醒一个等待当前对象的锁的线程。如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。
被唤醒的县城也并不是直接就能执行,而是要进入到正常的线程竞争过程中。
同样的你notify方法也要放在synchronized方法或者synchronized块中执行。
正确的使用 # 除了要在同步的基础上使用以外,在书《Java并发实践》中,作者提出一定要在循环中使用wait。正确的代码应该是下面的样子:
// The standard idiom for calling the wait method in Java synchronized (sharedObject) { while (condition) { sharedObject.wait(); // (Releases lock, and reacquires on wakeup) } // do action based upon condition e.g. take or put into queue } 在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。
这两个函数都是对于某个对象来说的,这个对象一定是一个多线程共享的对象。是同步,是利用锁的机制进行同步的。
在生产者消费者的概念中,就是对产品的队列进行共享,所以wait()和notify()都是queue的动作,同时锁也是对于queue来说的。
August 10, 2017
异步调用最主要的特点就是调用方并不等待函数的结果,而是继续进行下面的动作,函数自己去完成相应的过程。在函数完成以后将结果以某种方式返回给调用者。
实现异步的方式有很多,在前端的技术中使用的最多,这里我记录一下Java项目的内部自己异步调用某个函数的方式。
角色 # 在异步调用中,有三个角色,分别是消费者、取货凭据、生产者。对应的就是调用方、数据返回方式、执行函数。
举小蛋糕的例子非常好理解,订蛋糕、蛋糕店给你个取蛋糕的收据,或者也可以给你送到某个地方、蛋糕店生产蛋糕。
消费者 # 首先来看一下调用方的代码应该怎么写。
public class Customer { public static void main(String[] args) { System.out.println("main BEGIN"); CakeShop host = new CakeShop(); Cake cake1 = host.request(10, 'A'); Cake cake2 = host.request(20, 'B'); Cake cake3 = host.request(30, 'C'); System.out.println("main otherJob BEGIN"); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println("main otherJob END"); System.out.println("cake1 = " + cake1.getCake()); System.out.println("cake2 = " + cake2.getCake()); System.out.println("cake3 = " + cake3.
...
July 25, 2017
线程与池这两个东西在很多语言中都存在,这是一种编程模式。 池的思维是根本。举个例子,如果某个数据库有一个远程交互的API允许你写代码来远程操作,那么你如果不用池,每条命令都重新发起一个连接,然后要数据库认证身份,然后执行命令。这样的效率不高,你会很当然的想到我要把连接保持住,让多个命令的执行只进行一次身份认证。这个时候就引入池的概念。每次从池中取出对应的连接,执行完命令再放回到池里。 同样的思维在ACM比赛中我们常用内存池的方法来避免频繁申请内存。
线程作为一种资源,也需要申请,申请也要开销,所以能将申请次数降低就可以提高效率,所以我们搞个池。 线程是一个进程的执行单元。一个进程中可以有若干线程,每个线程都是独立的执行单元。线程的调度比进程调度要快很多,因为线程调度的资源涉及更小。
那么为什么要多线程?
先来讲一下并行和并发。 并行:“真正的两个机器同时工作”; 并发:“看上去的同时执行的,实际上在同一个cpu上轮转执行的”; 并发的场景非常多,最基本的就是同时过来两个请求需要做,如果你是单线程的程序,就必须等待前一个完全做完再处理后面一个。如果你是多线程程序,就可以从线程池里取一个线程来处理新的请求,可以保证工作效率。这个地方其实有两个东西,一个是用户体验,一个是异步操作。前一个好理解,后一个讲一下我的理解。 现在很多时候我们都要将处理步骤拆成异步的来做,异步处理的实现方式其中就有多线程。同步就是我前一句代码没有做完,后面的代码后面的步骤就阻塞在那里了。如果我们用异步的方式,开一个新的线程来搞某个步骤。例如写日志和返回执行结果,我们就可以起一个线程去写日志,然后主线程直接返回处理结果,这个处理结果对日志是弱依赖的,日志写的如何并不影响我返回结果的操作,不能因为我日志写失败了我就不把结果返回去,有可能磁盘页被其他程序上锁了,一时半会儿都搞不完,这个时候多线程的优势就显现出来了。
感觉到这里讲了很多东西,都可以讲的很细的,但是讲的太细看的人都没兴趣了,我自己也不需要写太多。
如何实现多线程 # 在Java中封装的很好,用起来也很简单。实现多线程有两种方法,一种是继承Thread类,一种是实现Runable 接口。
Thread # class Thread1 extends Thread{ private String name; public Thread1(String name) { this.name=name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "运行 : " + i); try { sleep((int) Math.random() * 10); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Main { public static void main(String[] args) { Thread1 mTh1=new Thread1("A"); Thread1 mTh2=new Thread1("B"); mTh1.
...