Javaでシングルトンの遅延初期化

もっとも簡単なシングルトンの例

@ThreadSafe
final class Singleton {
  public static final Singleton INSTANCE = new Singleton();
  private Singleton() {}; 
}

staticフィールドの初期化は、そのクラスに初めてアクセスされた(≠クラスがロードされた)時に行なわれる。

そこで次の例では、インスタンスが必要になる時まで生成を遅らせている。

@ThreadSafe
final class Singleton {
  private static Singleton instance;
  private Singleton() {}; 
  public static synchronized Singleton getInstance() {
    if(instance == null) { 
      instance = new Singleton();
    }
    return instance;
  } 
}

確かにインスタンスの生成は遅らせされたが、getInstanceを実行する度にスレッドの同期が行なわれるのはうれしくない。

そこで登場するのが悪名高きdouble-checked locking pattern。
アウトオブオーダー書き込みのせいで、不完全なインスタンスが別スレッドに公開されることがあるので使ってはいけない。

@NotThreadSafe
final class Singleton {
  private static Singleton instance;
  private Singleton() {}; 
  public static Singleton getInstance() {
    if(instance == null) { 
      synchronized(Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

Java 5.0からは変数にvolatile修飾子を付けることでこれを防げる。
以下はJava 5.0以降でのみ安全なdouble-checked locking pattern。

@ThreadSafe
final class Singleton {
  private static volatile Singleton instance;
  private Singleton() {}; 
  public static Singleton getInstance() {
    if(instance == null) { 
      synchronized(Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

そして、最後に示すのがInitialization-on-demand holder idiom。
LazyHolderの初期化は、getInstance()が実行されるまで行なわれないので、
スレッドセーフかつ高速なシングルトンの遅延初期化が実現できる。

@ThreadSafe
public class Singleton {
  private Singleton() {}
 
  private static class LazyHolder {
    public static final Singleton INSTANCE = new Singleton();
  }
 
  public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
  }
}

refs

Scala 2.9.1 + Ensime 0.7.6 + SBT 0.11.0 でensime-sbtが動かない

エラー内容

        ...

	==== Scala-Tools Maven2 Snapshots Repository: tried

	  http://scala-tools.org/repo-snapshots/org/scala-tools/sbt/sbt_2.9.1/0.7.4/sbt_2.9.1-0.7.4.pom

		::::::::::::::::::::::::::::::::::::::::::::::

		::          UNRESOLVED DEPENDENCIES         ::

		::::::::::::::::::::::::::::::::::::::::::::::

		:: org.scala-tools.sbt#sbt_2.9.1;0.7.4: not found

		::::::::::::::::::::::::::::::::::::::::::::::



:: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS
unresolved dependency: org.scala-tools.sbt#sbt_2.9.1;0.7.4: not found
Error during sbt execution: Error retrieving required libraries
  (see /Users/euler/.sbt/boot/update.log for complete log)
Error: Could not retrieve sbt 0.7.4

Process sbt exited abnormally with code 1

ensime-sbt-project-dir-pに"/project"をセットするか、空の/project/build.propertiesを作成すれば良いっぽい。

ref https://github.com/aemoncannon/ensime/issues/193

Ubuntu 11.10で開き方にemacsclientを追加する

mkdir -p ~/.local/share/applications
cat > !$/emacsclient.desktop <<EOF
[Desktop Entry]
Version=1.0
Name=Emacsclient
GenericName=Text Editor
Comment=View and edit files
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
Exec=/usr/bin/emacsclient -c %F
Icon=/usr/share/icons/hicolor/scalable/apps/emacs23.svg
Type=Application
Terminal=false
Categories=Utility;Development;TextEditor;
StartupWMClass=Emacsclient
EOF

refs

Lombokを使えばJavaでScalaのval(っぽいもの)が使えるんだぜ!

という煽りでしかないエントリー書こうと思ってたけど、
なんかもう僕の中で満足しちゃったので、
先日、社内勉強会の発表に使った資料を貼ってお茶濁し。

なれそめ

kiris:
招待してくんねぇかなー。「はてなブログβ使いたい」とか呟くと @nitro_idiot あたりが招待してくんねぇかなー。
nitro_idiot:
え、まだはてなブログ使ってないんですかっ!??
kiris:
えー、ほら俺はてな民とかそういうのと違うからー
kiris:
招待きた。twitterすごい。

 

はてなブログ始めました。

当分はメモ帳代りかなー。

Design by Contract(契約による設計)でScalaの守備力を上げる

このエントリはScala Advent Calendar jp 2010の16日目です。
昨日は@ussy00さんのScala でテンプレートエンジンを利用して HTML メールを送信するでした。


月日がたつのは早いもので今年も残すところ後9日です。
前回のブログ更新直後にtwitterで「あっ」とつぶやいてみれば、それが公開される頃には一年以上の月日が過ぎていました。
夏にはカブトムシが群がる程ワキの甘い一年でしたが、最期ぐらいはビシッと締める必要があります。


そんな僕にDesign by Contract(以下 DbC)です。


javaにはDbCをサポートするツールとしてContract4Jなどがありますが、
今回はscala wikiに掲載されていた、traitを使用したシンプルなDbCの実現方法の紹介とコードの解説をしたいと思います。
そもそもDbCってなによって方はまずこの辺りを参照してください。


まず初めに、契約を記述する為のContracted traitを以下のように定義します。

trait Contracted {
  class AssertionFailed extends Error

  type Conds = List[() => Boolean]

  protected case class Contract(reqs:Conds, enss:Conds) {
    def require(test: => Boolean) = Contract((() => test)::reqs, enss)
    def ensure(test: => Boolean) = Contract(reqs,(() => test):: enss)
    def in[T](body: => T):T = {
      for(r <- reqs.reverse if(!r())) throw new AssertionFailed()
      val ret = body
      for(e <- enss.reverse if(!e())) throw new AssertionFailed()
      ret
    }
  }

  def require(test: => Boolean) = Contract((() => test) :: Nil, Nil)
  def ensure(test: => Boolean) = Contract(Nil, (() => test) :: Nil)
}

Contractedに定義されたrequireとensureが事前条件と事後条件を定義する為のメソッドです。
この二つのメソッドは、「引数0でBooleanを返すの関数」を引数として指定していますが、
こうすることで内部に書かれた式を関数として受けとり、後から遅延評価させることができます。


requireのensureの返り値の型はContractedの内部クラスとして定義されている
Contractですが、このクラスの説明をする前に実際の使用方法をみてみましょう。
CbDを行なうには、対象のクラスに先程のContractedをmixinし、
更にrequireとensureで事前条件と事後条件を定義した後、inメソッドに実際のロジックを記述します。

class Account(b: Int) extends Contracted {
  def withdraw(amount: Int) {
    val old_balance = balance
    (
      require(amount > 0)
      require(balance - amount >= 0)
      ensure(old_balance - amount == balance)
    ) in {
      balance -= amount
    }
  }
}

val account = new Account(1000)
account.withdraw(300)
account.withdraw(800) // 事前条件 「balance - amount >= 0」をみたさないのでエラー

事前条件と事後条件の書き方は、DSL的な特殊な構文に見えますが、
これは先程のContractクラスとドットの省略を組み合わせるテクニックで実現しています。
それを理解する為に、試しにドットなどを省略せずに冗長な記述してみます。
するとこれは単純にメソッドチェインをしているだけということが判るかと思います。

val contract1 = this.require(amount > 0)
val contract2 = contract1.require(balance - amount >= 0)
val contract3 = contract2.ensure(old_balance - amount == balance)
contract3.in {
  balance -= amount
}

最期に先程説明を省略したContractの実装をみてみましょう。

  protected case class Contract(reqs:Conds, enss:Conds) {
    def require(test: => Boolean) = Contract((() => test)::reqs, enss)
    def ensure(test: => Boolean) = Contract(reqs,(() => test):: enss)
    def in[T](body: => T):T = {
      for(r <- reqs.reverse if(!r())) throw new AssertionFailed()
      val ret = body
      for(e <- enss.reverse if(!e())) throw new AssertionFailed()
      ret
    }
  }

requireやensureが呼ばれるとのリスト先頭に新しい契約を追加した、新しいContractを返します。
これまでに定義した契約が守られているかをbodyの前後のfor文で確認しているわけです。


さて次はScalaやってて知らない奴はモグリといっても過言ではない、
どうみてもチン○のマスコットでお馴染の@yuroyoroさんです。

元ネタ
- http://scala.sygneca.com/code/design-by-contract

flymakeでscalaの文法をリアルタイムにチェックする

前回のはおまけで、やりたかったのはむしろこっち。

flymakeがわからない人はこちらを参照。

(eval-when-compile (require 'cl))
(require 'flymake)

(defvar flymake-scala-tmpdir "/tmp")

(defvar flymake-scala-global-classpath ".")

(push '(".+\\.scala$" flymake-scala-init) flymake-allowed-file-name-masks)
(push '("^\\(.*\\):\\([0-9]+\\): error: \\(.*\\)$" 1 2 nil 3) flymake-err-line-patterns)

(defun flymake-scala-string-join (sequence separator)
  (mapconcat #'identity sequence separator))

(defun flymake-scala-string-not-empty (str)
  (and (stringp str) (not (or (string-equal "" str)
                              (string-match "^ +$" str)))))

(defun flymake-scala-parent-dir (path)
  "return parent directory path of argument."
  (substring-no-properties (file-name-directory path) 0 -1))

(defun flymake-scala-find-target-file-dir (path target)
  (let* ((src (split-string (flymake-scala-parent-dir path) "/"))
         (paths (maplist #'(lambda (l) (flymake-scala-string-join (reverse l) "/")) (nreverse src))))
    (loop for path in paths
          if (file-exists-p (concat path "/" target))
          return path)))

(defun flymake-scala-maven-build-cmd ()
  (list "mvn" (list "-fn" "-Dmaven.compiler.showWarnings=true" "dependency:copy-dependencies" "scala:compile")))

(defun flymake-scala-build-cmd (target distdir classpath)
  (list "fsc" (list "-classpath" classpath "-d" distdir target)))

(defun flymake-scala-init ()
  (let ((dir (flymake-scala-find-target-file-dir buffer-file-name "pom.xml")))
    (if (flymake-scala-string-not-empty dir)
        (progn
          (cd dir)
          (let ((distdir (loop for path in '("target" "build")
                               if (file-exists-p path)
                               return path)))
            (if (flymake-scala-string-not-empty distdir)
                (let* ((classes (concat distdir "/classes"))
                       (dependency (concat distdir "/dependency"))
                       (jars (directory-files dependency t "^[^\.]"))
                       (classpath (flymake-scala-string-join (append (cons classes jars) flymake-scala-global-classpath) ":")))
                  (flymake-scala-build-cmd buffer-file-name classes classpath))
              (flymake-scala-maven-build-cmd))))
      (flymake-scala-build-cmd buffer-file-name flymake-scala-tmpdir flymake-scala-global-classpath))))

(defun flymake-scala-start-fsc-server ()
  (with-temp-buffer
    (call-process-shell-command "fsc" nil nil)))

(defun flymake-scala-maven-update ()
  (interactive)
  (let ((dir (flymake-scala-find-target-file-dir buffer-file-name "pom.xml")))
    (if (flymake-scala-string-not-empty dir)
        (progn
          (cd dir)
          (let* ((cmd (flymake-scala-maven-build-cmd))
                 (progname (car cmd))
                 (args (cadr cmd))
                 (buffname (format "*%s*" progname))
                 (buffer (get-buffer-create buffname)))
            (switch-to-buffer-other-window buffer)
            (start-process-shell-command progname buffer progname (flymake-scala-string-join args " "))))
      (message "No pom.xml found"))))

(add-hook 'scala-mode-hook (lambda () (flymake-scala-start-fsc-server) (flymake-mode-on)))

maven2のプロジェクトにも地味に対応。
pom.xmlを再帰的に探して存在する場合はmvn scala:compileを、
無い場合はfscをflymakeで実行させとります。

■参考にしたサイト

2009/10/04 18:00

mvnコマンドの実行が遅いので、二回目以降はfscを使うように修正。
後からjarなどを追加した場合は、

M-x flymake-scala-maven-update

を実行してください。

2009/10/10 21:00

rails.elの関数を参照してしまっていたのを修正