ConCurrent in Practice小记 (3)

高级同步技巧

Semaphore

Semaphore信号量,据说是Dijkstra大神发明的。内部维护一个许可集(Permits Set),用于发放许可和回收许可,存在内部计数器,主要用来计数能否得到资源(一般用来限制同时访问资源数)。当一个线程拿到许可,计数器减一;当线程释放资源则计数器加一;当计数器为0则阻塞线程。

特别地: Semaphore的同步锁机制仅仅用于对访问许可的同步,对于需要访问对象的池等的同步锁并不保证。如一个线程池需要访问一个资源池,此时对于每一个需要访问资源的线程,要先获得许可,这是在Semaphore中得到同步保护的,但是在得到许可后,对资源池本身的存取等依然是非同步保护的。需要自己实现。(或者是资源池本身的同步维护)——所以在方法中应该确保首先释放(线程所占)资源,再去释放许可。

JDK中的示例代码:

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
} public void putItem(Object x) {
if (markAsUnused(x))
available.release();
} // Not a particularly efficient data structure; just for demo protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
} protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
} }

Semaphore的构造方法为:
Semaphore(int permits); 给出允许发放的许可个数,默认不公平发放
Semaphore(int permits, boolead fair); 除给出许可个数外,boolean值代表是否允许公平发放许可
acquire(),release()分别是取得许可和释放许可。当Semaphore的许可数目为1时,即只有1和0两种状态,此时的Semaphore被称之为二进制信号量,是完全互斥的,仅允许线程一个一个执行。

另外同Lock类相同,Semaphore也有两种特殊acquire的方法:
acquireUninterruptibly(): 普通的acquire()方法在阻塞的时候是可以被中断的,且抛出异常。但是使用此方法会忽视中断信息且不会抛出异常。
tryAcquire(): 如果可以得到许可返回true,如果不能则立即返回false,并不会阻塞或者等待Semaphore的释放。

CountDownLatch

CountDownLatch提供这样一个类:目的在于同步线程之间的任务,它让一个或多个线程等待,直到一组操作全都执行完毕才返回。初始化时仅需要一个int参数,用来表示这一组操作共有多少任务,该正数传递到CountDownLatch对象的计数器中。当某个线程完成自己的任务时调用await()方法,该方法在内部计数器未自减到0之前让线程保持睡眠,当计数器减少到0时会唤醒所有调用await()而睡眠的线程并返回结果。CountDownLatch对象的countDown()方法会自减其内部计数器。

特别地:注意CountDownLatch对象只能使用一次,内部计数器初始化后均不可再更改值,当计数器减少到0时,再调用countDown()方法也不会有影响。如果需要下一次同步则必须生成新的对象。

CountDownLatch和临界区保护等没有关系,仅

示例代码:

package com.lyb.Section3;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; /**
* Created by lyb on 15-7-28.
*/
public class VideoConferenceTest { public static void main(String[] args){
VideoConference conference = new VideoConference(10);
Thread conferenceThread = new Thread(conference);
conferenceThread.start(); for (int i = 0; i < 10; i++){
Participant participant = new Participant(conference,"Participant " + i);
Thread thread = new Thread(participant);
thread.start();
} } } class VideoConference implements Runnable{ private final CountDownLatch countDownLatchControler; public VideoConference(int number){
countDownLatchControler = new CountDownLatch(number);
} public void arrive(String name){
System.out.printf("%s has arrived. \n",name);
countDownLatchControler.countDown();
System.out.printf("VideoConference : Waiting for %d conferences \n", countDownLatchControler.getCount());
} public void run(){
System.out.printf("VideoConference Initialization : %d participants. \n",
countDownLatchControler.getCount());
try {
countDownLatchControler.await();
System.out.printf("All the participants come \n");
System.out.printf("Let's begin the conference .... \n");
}catch (InterruptedException e){
e.printStackTrace();
}
} } class Participant implements Runnable{ private VideoConference conference;
private String name; public Participant(VideoConference conference, String name){
this.conference = conference;
this.name = name;
} public void run(){
try {
long duration = (long)(Math.random()*10);
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e ){
e.printStackTrace();
}
conference.arrive(name);
} }

CyclicBarrier

CyclicBarrier跟CountDownLatch类相似,也是在多个线程到达某一同步点之后睡眠,等待一系列操作完成之后,再进行下一个任务。这里仍然是同步的线程数目,当有一个线程到达时,先睡眠,到达的数目为制定数目后可以进行初始化参数的Runnable任务。
(这里有一个疑问,如果是线程池中,仅有3条线程,但是在这里CyclicBarrier设置为5,拿是不是永远达不到Barrier了?)

另一个版本的await()方法:
await(long time, TimeUnit unit): 唤醒条件为休眠被中断,内部计数器到达,等待时间到。
getNumberWaiting() 返回当前在屏障前等待的参与者的数目
getParties() 返回同步的任务数,即总共需要达到屏障前的数目

CyclicBarrier同CountDownLatch最大的不同是CyclicBarrier可以被重用,使用reset()方法可以重置barrier,但是全部正在await()的线程将抛出broken异常。Broken状态是Barrier的特殊状态,在此状态下不能操作,状态的引起是由于正在await()的某个线程被中断。isBroken()可以检测该状态。

Phaser

Phaser移相器是JDK1.7引入的新类,可以运行阶段性的并发任务。当任务是分为多个步骤来做,则Phaser可以在每个阶段的的结尾同步线程,所以除非完成第一个阶段,否则不可能开始第二个步骤。

package com.lyb.Section3;

import com.sun.xml.internal.stream.util.ThreadLocalBufferAllocator;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit; /**
* Created by lyb on 15-7-30.
*/
public class PhaserTest {
public static void main(String[] args){
Phaser phaser = new Phaser(3); FileSearch ahome = new FileSearch("/home/lyb/WorkSpace","md",phaser);
FileSearch bhome = new FileSearch("/home/lyb/Book","md",phaser);
FileSearch chome = new FileSearch("/home/lyb/Software","md",phaser); Thread ahomeThread = new Thread(ahome,"WorkSpace");
ahomeThread.start(); Thread bhomeThread = new Thread(bhome,"Book");
bhomeThread.start(); Thread chomeThread = new Thread(chome,"Software");
chomeThread.start(); try {
ahomeThread.join();
bhomeThread.join();
chomeThread.join();
}catch (InterruptedException e){
e.printStackTrace();
} System.out.printf("Terminated:" + phaser.isTerminated());
} } class FileSearch implements Runnable{
private String initPath;
private String end;
private Phaser phaser;
private List<String> results; public FileSearch(String initPath, String end, Phaser phaser){
this.initPath = initPath;
this.end = end;
this.phaser = phaser;
results = new ArrayList<>();
} private void directoryProcessed(File file){
File filelist[] = file.listFiles();
if (filelist != null){
for (File file1 : filelist){
if (file1.isDirectory()){
directoryProcessed(file1);
}else {
fileProcessed(file1);
}
}
}
} private void fileProcessed(File file){
if (file.getName().endsWith(end)){
results.add(file.getAbsolutePath());
}
} private void filterResults(){
List<String> newResults = new ArrayList<>();
long actualTime = new Date().getTime();
for (String filePath : results){
File file = new File(filePath);
long modifyTime = file.lastModified();
if (actualTime - modifyTime < TimeUnit.MILLISECONDS.convert(100,TimeUnit.DAYS)){
newResults.add(filePath);
}
}
results = newResults;
} private boolean checkResults(){
if (results.isEmpty()){
System.out.printf("%s : Phase %d: 0 results \n",
Thread.currentThread().getName(),
phaser.getPhase());
System.out.printf("%s : Phase %d: end \n",
Thread.currentThread().getName(),
phaser.getPhase());
phaser.arriveAndDeregister();
return false;
}else {
System.out.printf("%s : Phase %d : %d results. \n",
Thread.currentThread().getName(),
phaser.getPhase(),results.size());
phaser.arriveAndAwaitAdvance();
return true;
}
} private void showInfo(){
for (String filePath : results){
File file = new File(filePath);
System.out.printf("%s : %s \n",Thread.currentThread().getName(),file.getAbsoluteFile());
}
phaser.arriveAndAwaitAdvance();
} public void run(){
phaser.arriveAndAwaitAdvance();
System.out.printf("%s Starting. \n", Thread.currentThread().getName());
File file = new File(initPath);
if (file.isDirectory()){
directoryProcessed(file);
} if (!checkResults()){
return;
} filterResults();
if (!checkResults()){
return;
} showInfo();
phaser.arriveAndDeregister();
System.out.printf("%s Work completed. \n", Thread.currentThread().getName());
} }

Phaser的构造函数接受的参数是指在phase末端控制的同步的线程数目,也称之为参与者数目。在run方法中首先调用arriveAndAwaitAdvance(),这样每个线程都在自己创建完毕后等待其他线程,同时每次调用该方法,会更新phase内部的线程计数,减去完结的线程数,得到现在实际应该同步的线程数,并使当前线程睡眠,等待最后一个同步线程到达,使所有的线程唤醒,开始执行第二个phase的操作。

arriveAndDeregister()是在线程到达phase时,已经判定为应该终止的线程,Deregister之后,phase内部计数减一,不会再计算该线程。当然每个phase执行到最后,都应每个线程调用该方法,退出phase计数。

最终,由main方法中调用isTerminated()退出phaser。

Phaser 对象可能是在这2中状态:

  • Active: 当 Phaser 接受新的参与者注册,它进入这个状态,并且在每个phase的末端同步。 (在此状态,Phaser像在这个指南里解释的那样工作。此状态不在Java 并发 API中。)
  • Termination: 默认状态,当Phaser里全部的参与者都取消注册,它进入这个状态,所以这时 Phaser 有0个参与者。更具体的说,当onAdvance() 方法返回真值时,Phaser 是在这个状态里。如果你覆盖那个方法,你可以改变它的默认行为。当 Phaser 在这个状态,同步方法 arriveAndAwaitAdvance()会立刻返回,不会做任何同步。

Phaser 类的一个显著特点是你不需要控制任何与phaser相关的方法的异常。不像其他同步应用,线程们在phaser休眠不会响应任何中断也不会抛出 InterruptedException 异常。只有一个异常InterruptedException在特定中才会出现。

The Phaser类还提供了其他相关方法来改变phase。他们是:

  • arrive(): 此方法示意phaser某个参与者已经结束actual phase了,但是他应该等待其他的参与者才能继续执行。小心使用此法,因为它并不能与其他线程同步。(不懂。。。)

  • awaitAdvance(int phase): 如果我们传递的参数值等于phaser的actual phase,此方法让当前线程进入睡眠直到phaser的全部参与者结束当前的phase。如果参数值与phaser 的 actual phase不等,那么立刻返回。

  • awaitAdvanceInterruptibly(int phaser): 此方法等同与之前的方法,只是在线程正在此方法中休眠而被中断时候,它会抛出InterruptedException 异常。

当你创建一个 Phaser 对象,你表明了参与者的数量。但是Phaser类还有2种方法来增加参与者的数量。他们是:

  • register(): 此方法为Phaser添加一个新的参与者。这个新加入者会被认为是还未到达 actual phase.
  • bulkRegister(int Parties): 此方法为Phaser添加一个特定数量的参与者。这些新加入的参与都会被认为是还未到达 actual phase.
  • Phaser类提供的唯一一个减少参与者数量的方法是arriveAndDeregister() 方法,它通知phaser线程已经结束了actual phase,而且他不想继续phased的操作了。

当phaser有0个参与者,它进入一个称为Termination的状态。Phaser 类提供 forceTermination() 来改变phaser的状态,让它直接进入Termination 状态,不在乎已经在phaser中注册的参与者的数量。此机制可能会很有用在一个参与者出现异常的情况下来强制结束phaser.

当phaser在 Termination 状态, awaitAdvance() 和 arriveAndAwaitAdvance() 方法立刻返回一个负值,而不是一般情况下的正值如果你知道你的phaser可能终止了,那么你可以用这些方法来确认他是否真的终止了。

个人总结:

  1. 任意时刻都可以加入phase,通过register和bulkregister,第一次到达phase才开始同步
  2. onArrive和onAdvance会手动调用线程的到达,onarrive并不等待其他的线程,而如果onAdvance返回true(其实是判断内部的计数是否是0),则即将进入termination。
  3. 可以级联,成为phase树,而且child phase的参与者的register是自动在parent的计数上注册的。当子phase参与者为0,则自动从树上裂解。
  4. 有效的镜像,可以通过随时调用getxxx方法得到参与者数量,为到达数量等等。

generated by haroopad

:first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:3px}iframe{border:0}figure{-webkit-margin-before:0;-webkit-margin-after:0;-webkit-margin-start:0;-webkit-margin-end:0}kbd{border:1px solid #aaa;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:1px 2px 2px #ddd;-webkit-box-shadow:1px 2px 2px #ddd;box-shadow:1px 2px 2px #ddd;background-color:#f9f9f9;background-image:-moz-linear-gradient(top,#eee,#f9f9f9,#eee);background-image:-o-linear-gradient(top,#eee,#f9f9f9,#eee);background-image:-webkit-linear-gradient(top,#eee,#f9f9f9,#eee);background-image:linear-gradient(top,#eee,#f9f9f9,#eee);padding:1px 3px;font-family:inherit;font-size:.85em}.oembeded .oembed_photo{display:inline-block}img[data-echo]{margin:25px 0;width:100px;height:100px;background:url(../img/ajax.gif) center center no-repeat #fff}.spinner{display:inline-block;width:10px;height:10px;margin-bottom:-.1em;border:2px solid rgba(0,0,0,.5);border-top-color:transparent;border-radius:100%;-webkit-animation:spin 1s infinite linear;animation:spin 1s infinite linear}.spinner:after{content:'';display:block;width:0;height:0;position:absolute;top:-6px;left:0;border:4px solid transparent;border-bottom-color:rgba(0,0,0,.5);-webkit-transform:rotate(45deg);transform:rotate(45deg)}@-webkit-keyframes spin{to{-webkit-transform:rotate(360deg)}}@keyframes spin{to{transform:rotate(360deg)}}p.toc{margin:0!important}p.toc ul{padding-left:10px}p.toc>ul{padding:10px;margin:0 10px;display:inline-block;border:1px solid #ededed;border-radius:5px}p.toc li,p.toc ul{list-style-type:none}p.toc li{width:100%;padding:0;overflow:hidden}p.toc li a::after{content:"."}p.toc li a:before{content:"• "}p.toc h5{text-transform:uppercase}p.toc .title{float:left;padding-right:3px}p.toc .number{margin:0;float:right;padding-left:3px;background:#fff;display:none}input.task-list-item{margin-left:-1.62em}.markdown{font-family:"Hiragino Sans GB","Microsoft YaHei",STHeiti,SimSun,"Lucida Grande","Lucida Sans Unicode","Lucida Sans",'Segoe UI',AppleSDGothicNeo-Medium,'Malgun Gothic',Verdana,Tahoma,sans-serif;padding:20px}.markdown a{text-decoration:none;vertical-align:baseline}.markdown a:hover{text-decoration:underline}.markdown h1{font-size:2.2em;font-weight:700;margin:1.5em 0 1em}.markdown h2{font-size:1.8em;font-weight:700;margin:1.275em 0 .85em}.markdown h3{font-size:1.6em;font-weight:700;margin:1.125em 0 .75em}.markdown h4{font-size:1.4em;font-weight:700;margin:.99em 0 .66em}.markdown h5{font-size:1.2em;font-weight:700;margin:.855em 0 .57em}.markdown h6{font-size:1em;font-weight:700;margin:.75em 0 .5em}.markdown h1+p,.markdown h1:first-child,.markdown h2+p,.markdown h2:first-child,.markdown h3+p,.markdown h3:first-child,.markdown h4+p,.markdown h4:first-child,.markdown h5+p,.markdown h5:first-child,.markdown h6+p,.markdown h6:first-child{margin-top:0}.markdown hr{border:1px solid #ccc}.markdown p{margin:1em 0;word-wrap:break-word}.markdown ol{list-style-type:decimal}.markdown li{display:list-item;line-height:1.4em}.markdown blockquote{margin:1em 20px}.markdown blockquote>:first-child{margin-top:0}.markdown blockquote>:last-child{margin-bottom:0}.markdown blockquote cite:before{content:'\2014 \00A0'}.markdown .code{border-radius:3px;word-wrap:break-word}.markdown pre{border-radius:3px;word-wrap:break-word;border:1px solid #ccc;overflow:auto;padding:.5em}.markdown pre code{border:0;display:block}.markdown pre>code{font-family:Consolas,Inconsolata,Courier,monospace;font-weight:700;white-space:pre;margin:0}.markdown code{border-radius:3px;word-wrap:break-word;border:1px solid #ccc;padding:0 5px;margin:0 2px}.markdown img{max-width:100%}.markdown mark{color:#000;background-color:#fcf8e3}.markdown table{padding:0;border-collapse:collapse;border-spacing:0;margin-bottom:16px}.markdown table tr td,.markdown table tr th{border:1px solid #ccc;margin:0;padding:6px 13px}.markdown table tr th{font-weight:700}.markdown table tr th>:first-child{margin-top:0}.markdown table tr th>:last-child{margin-bottom:0}.markdown table tr td>:first-child{margin-top:0}.markdown table tr td>:last-child{margin-bottom:0}.github{padding:20px;font-family:"Helvetica Neue",Helvetica,"Hiragino Sans GB","Microsoft YaHei",STHeiti,SimSun,"Segoe UI",AppleSDGothicNeo-Medium,'Malgun Gothic',Arial,freesans,sans-serif;font-size:15px;background:#fff;line-height:1.6;-webkit-font-smoothing:antialiased}.github a{color:#3269a0}.github a:hover{color:#4183c4}.github h2{border-bottom:1px solid #e6e6e6;line-height:1.6}.github h6{color:#777}.github hr{border:1px solid #e6e6e6}.github pre>code{font-size:.9em;font-family:Consolas,Inconsolata,Courier,monospace}.github blockquote>code,.github h1>code,.github h2>code,.github h3>code,.github h4>code,.github h5>code,.github h6>code,.github li>code,.github p>code,.github td>code{background-color:rgba(0,0,0,.07);font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:85%;padding:.2em .5em;border:0}.github blockquote{border-left:4px solid #e6e6e6;padding:0 15px;font-style:italic}.github table{background-color:#fafafa}.github table tr td,.github table tr th{border:1px solid #e6e6e6}.github table tr:nth-child(2n){background-color:#f2f2f2}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8;-webkit-text-size-adjust:none}.diff .hljs-header,.hljs-comment,.hljs-javadoc{color:#998;font-style:italic}.css .rule .hljs-keyword,.hljs-keyword,.hljs-request,.hljs-status,.hljs-subst,.hljs-winutils,.nginx .hljs-title{color:#333;font-weight:700}.hljs-hexcolor,.hljs-number,.ruby .hljs-constant{color:teal}.hljs-dartdoc,.hljs-phpdoc,.hljs-string,.hljs-tag .hljs-value,.tex .hljs-formula{color:#d14}.hljs-id,.hljs-title,.scss .hljs-preprocessor{color:#900;font-weight:700}.hljs-list .hljs-keyword,.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type,.tex .hljs-command,.vhdl .hljs-literal{color:#458;font-weight:700}.django .hljs-tag .hljs-keyword,.hljs-rules .hljs-property,.hljs-tag,.hljs-tag .hljs-title{color:navy;font-weight:400}.hljs-attribute,.hljs-variable,.lisp .hljs-body{color:teal}.hljs-regexp{color:#009926}.clojure .hljs-keyword,.hljs-prompt,.hljs-symbol,.lisp .hljs-keyword,.ruby .hljs-symbol .hljs-string,.scheme .hljs-keyword,.tex .hljs-special{color:#990073}.hljs-built_in{color:#0086b3}.hljs-cdata,.hljs-doctype,.hljs-pi,.hljs-pragma,.hljs-preprocessor,.hljs-shebang{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.diff .hljs-change{background:#0086b3}.hljs-chunk{color:#aaa}.MathJax_Hover_Frame{border-radius:.25em;-webkit-border-radius:.25em;-moz-border-radius:.25em;-khtml-border-radius:.25em;box-shadow:0 0 15px #83A;-webkit-box-shadow:0 0 15px #83A;-moz-box-shadow:0 0 15px #83A;-khtml-box-shadow:0 0 15px #83A;border:1px solid #A6D!important;display:inline-block;position:absolute}.MathJax_Hover_Arrow{position:absolute;width:15px;height:11px;cursor:pointer}#MathJax_About{position:fixed;left:50%;width:auto;text-align:center;border:3px outset;padding:1em 2em;background-color:#DDD;color:#000;cursor:default;font-family:message-box;font-size:120%;font-style:normal;text-indent:0;text-transform:none;line-height:normal;letter-spacing:normal;word-spacing:normal;word-wrap:normal;white-space:nowrap;float:none;z-index:201;border-radius:15px;-webkit-border-radius:15px;-moz-border-radius:15px;-khtml-border-radius:15px;box-shadow:0 10px 20px gray;-webkit-box-shadow:0 10px 20px gray;-moz-box-shadow:0 10px 20px gray;-khtml-box-shadow:0 10px 20px gray;filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true')}.MathJax_Menu{position:absolute;background-color:#fff;color:#000;width:auto;padding:5px 0;border:1px solid #CCC;margin:0;cursor:default;font:menu;text-align:left;text-indent:0;text-transform:none;line-height:normal;letter-spacing:normal;word-spacing:normal;word-wrap:normal;white-space:nowrap;float:none;z-index:201;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;-khtml-border-radius:5px;box-shadow:0 10px 20px gray;-webkit-box-shadow:0 10px 20px gray;-moz-box-shadow:0 10px 20px gray;-khtml-box-shadow:0 10px 20px gray;filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true')}.MathJax_MenuItem{padding:1px 2em;background:0 0}.MathJax_MenuArrow{position:absolute;right:.5em;color:#666}.MathJax_MenuActive .MathJax_MenuArrow{color:#fff}.MathJax_MenuArrow.RTL{left:.5em;right:auto}.MathJax_MenuCheck{position:absolute;left:.7em}.MathJax_MenuCheck.RTL{right:.7em;left:auto}.MathJax_MenuRadioCheck{position:absolute;left:.7em}.MathJax_MenuRadioCheck.RTL{right:.7em;left:auto}.MathJax_MenuLabel{padding:1px 2em 3px 1.33em;font-style:italic}.MathJax_MenuRule{border-top:1px solid #DDD;margin:4px 3px}.MathJax_MenuDisabled{color:GrayText}.MathJax_MenuActive{background-color:#606872;color:#fff}.MathJax_Menu_Close{position:absolute;width:31px;height:31px;top:-15px;left:-15px}#MathJax_Zoom{position:absolute;background-color:#F0F0F0;overflow:auto;display:block;z-index:301;padding:.5em;border:1px solid #000;margin:0;font-weight:400;font-style:normal;text-align:left;text-indent:0;text-transform:none;line-height:normal;letter-spacing:normal;word-spacing:normal;word-wrap:normal;white-space:nowrap;float:none;box-shadow:5px 5px 15px #AAA;-webkit-box-shadow:5px 5px 15px #AAA;-moz-box-shadow:5px 5px 15px #AAA;-khtml-box-shadow:5px 5px 15px #AAA;filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true')}#MathJax_ZoomOverlay{position:absolute;left:0;top:0;z-index:300;display:inline-block;width:100%;height:100%;border:0;padding:0;margin:0;background-color:#fff;opacity:0;filter:alpha(opacity=0)}#MathJax_ZoomFrame{position:relative;display:inline-block;height:0;width:0}#MathJax_ZoomEventTrap{position:absolute;left:0;top:0;z-index:302;display:inline-block;border:0;padding:0;margin:0;background-color:#fff;opacity:0;filter:alpha(opacity=0)}.MathJax_Preview{color:#888}#MathJax_Message{position:fixed;left:1px;bottom:2px;background-color:#E6E6E6;border:1px solid #959595;margin:0;padding:2px 8px;z-index:102;color:#000;font-size:80%;width:auto;white-space:nowrap}#MathJax_MSIE_Frame{position:absolute;top:0;left:0;width:0;z-index:101;border:0;margin:0;padding:0}.MathJax_Error{color:#C00;font-style:italic}footer{position:fixed;font-size:.8em;text-align:right;bottom:0;margin-left:-25px;height:20px;width:100%}
-->

04-14 21:10