设计模式之单例模式(singleton)

单例模式的定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

一个简单Java实现

public class Singleton {
 private static Singleton instance;

 private Singleton() {
 }

 public static Singleton getInstance() {

   if(instance == null) {
     instance = new Singleton();
   }
   return instance;
 }
}

在上述Java实现中,单例对象保存在一个类静态对象,类的构造函数声明为私有,要想使用这个类,必须调用getInstance方法获取全局唯一的静态单例对象。

这个简单的方法主要的缺点是没有考虑到多线程的情况,在多线程运行过程中,很有可能多个线程一开始同时进入getInstance方法,从而使得单例对象被初始化多次,线程获取到的单例并不一致。

一个解决方法是将getInstance方法声明为线程同步,请看下面的样例。

多线程同步实现创建单例

public class Singleton {
 private static Singleton instance;

 private Singleton() {
 }

 public static synchronized Singleton getInstance() {

   if(instance == null) {
     instance = new Singleton();
   }
   return instance;
 }
}

上述Java实现中,getInstance方法声明为线程同步,其避免了多线程一开始同时调用该方法而导致的多次实例化问题。但是这个解决方案并不完美,问题就出在线程同步上,在简单应用上这个缺点表现不明显,但是如果是多线程应用,getInstance方法被频繁调用的话,所有线程会在进入getInstance方法排队等待,线程调用会被频繁切换,造成不必要的系统资源消耗。

其实线程同步主要是希望在最开始的几个线程访问getInstance方法时,一旦单例对象创建完毕后,就不再需要让每个线程等待依次进入该方法,换句话说,单例创建完毕后,各个进程就可以异步访问该方法,获取已创建的单例对象。

优化后的解决方案见下面的样例。

多线程的异步获取和同步创建单例

public class Singleton {
 private volatile static Singleton instance;

 private Singleton() {
 }

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

上述Java实现中,静态单例对象被声明为volatile对象,其意义是这个对象为各个线程共享对象,JVM不再为每个线程执行时在内存复制该对象。getInstance方法的访问是异步的,各个线程的访问独立运行,如果已有单例对象,则直接获取并退出getInstance方法;如果单例对象为空,则创建过程对线程同步,保证只让第一进入该代码块的线程来初始化该单例。

通过JVM的静态初始化来创建单例

如果系统的启动资源足够用,可以让单例的创建放在JVM启动中,即通过静态初始化器(static initializer)来创建单例,

public class Singleton {
 private static Singleton instance = new Singleton();

 private Singleton() {
 }

 public static Singleton getInstance() {
   return instance;
 }
}

上述方法简单实用。

代码样例

代码仓库地址:http://git.oschina.net/pphh/designPatterns,可以通过如下git clone命令获取仓库代码,

git clone git@git.oschina.net:pphh/designPatterns.git

上述代码样例在文件路径designPatterns\java\singleton中。

参考资料

《设计模式-可复用面向对象软件的基础》

《Head First 设计模式》

百度百科:http://baike.baidu.com/view/1859857.htm

Java图片编辑工具thumbnailator

对图片的编辑是一个常见的编程需求,比如缩小图片、加水印等等,而thumbnailator就是一个非常好用的开源图片编辑Java类库,

https://github.com/coobird/thumbnailator

本文将对此工具进行介绍并给出代码样例。

1. 项目引入thumbnailator类库

以Maven项目为例,可以按如下配置添加thumbnailator依赖类库,

 
 net.coobird
 thumbnailator
 0.4.8
 

2. 获取缩略图

获取缩略图有两种方式,一个是指定缩放后的图片尺寸,还有一个指定图片缩放比例。

指定缩放尺寸到宽150+高100,

Thumbnails.of(new File("bird.jpg")).size(150, 100).toFile(new File("thumbnail.jpg"));

指定缩放比例为四分之一,

BufferedImage img = ImageIO.read(new File("bird.jpg"));
BufferedImage thumbnail = Thumbnails.of(img).scale(0.25).asBufferedImage();
ImageIO.write(thumbnail, "jpg", new File("bird_scaled_25.jpg"));

图片缩放的效果图,

thumbnail

3. 旋转图片指定角度

对图片分别旋转0、90、180、270、45度,

for (int i : new int[] {0, 90, 180, 270, 45}) {
 Thumbnails.of("bird.jpg")
  .size(100, 100)
  .rotate(i)
  .toFile(new File("bird_rotated_" + i + ".jpg"));
 }

rotate

4. 添加水印

添加一个水印,并且设置水印透明度为30%,

Thumbnails.of("bird.jpg")
 .scale(1)
 .watermark(Positions.BOTTOM_RIGHT, ImageIO.read(watermarkFile), 0.3f)
 .outputQuality(0.8)
 .toFile(new File("bird_with_watermark.jpg"));

图片添加水印的效果图,

watermark

5. 更改图片格式

读取JPEG格式图片,然后输出PNG格式图片,

OutputStream outPng = new FileOutputStream(resDir + "output.png");
Thumbnails.of("bird.jpg")
 .scale(1)
 .outputFormat("png")
 .toOutputStream(outPng);
 outPng.close();

6. 批量处理图片

Thumbnailnator可以同时指定多个图片文件,批量进行处理,

Thumbnails.of("bird.jpg", "bird2.jpg", "bird3.jpg")
 .scale(1)
 .toFiles(new File(outDirectory), Rename.PREFIX_DOT_THUMBNAIL);

7. 代码样例

上述的代码样例可以在如下代码仓库中获取(代码路径tools\java\thumbnailator),

git clone git@git.oschina.net:pphh/tools.git

代码仓库地址: http://git.oschina.net/pphh/tools

参考文章:

工具使用说明 https://github.com/coobird/thumbnailator/wiki/Examples

 

 

Linux之shell脚本

 

1. 一个简单的脚本

一个简单的shell脚本如下,

#!/bin/bash
1. this is a simple shell script

echo "$1"

str="hello,world"
echo "$str"

exit 0

其中第一行#!/bin/bash声明的这个脚本运行的shell工具,有些脚本会在第一行声明为#!/bin/sh,这两种声明会有细微差别,主要在于:

bash是普通的shell工具,各个linux系统都会提供这个工具。

sh使用的是POSIX标准模式shell工具,一般linux系统会软链到/bin/bash工具,或者在Ubuntu会软链到/bin/dash,软链的时候都会打开相应的POSIX标准模式。

2. 数学计算

一个简单的数学计算脚本如下,

#!/bin/bash

a=1
b=2
i=$(($a+$b))
j=$(($a*$b))
echo "$a+$b=$i"
echo "$a*$b=$j"

read -p "please input a number:" x
read -p "please input another number:" y
echo "$x+$y="$(($x+$y))
echo "$x*$y="$(($x*$y))

上述会将变量a和b相加相乘,并输入结果。如果希望让用户输入,可以使用read这个命令工具。

3. 流程控制

Linux Shell脚本语言提供条件、循环、case switch等用于流程控制,

#!/bin/bash

read -p "please input a number:" x
if [ $x -gt 0 ]; then
 echo "$x is greater than zero"
elif [ $x -eq 0 ]; then
 echo "$x is equal zero"
else
 echo "$x is less than zero" 
fi

for (( i=1;i<=5;i++ ))
do
 echo "for loop: $i times"
done

num=10
while [ $num -gt 0 ]
do
 read -p "please input a number less than 0: " num
done

上述脚本中,第一段让用户输入一个输入数字,然后判断输入的值是大于、等于、小于零;第二段使用for做了5次循环;第三段会让用户输入一个数字,如果数字大于零,则让用户重新输入,一直到有个小于零的数字才退出循环。

4. 用户输入

#!/bin/bash

read -p "please input your first name: " firstname
read -p "please input your last name:" lastname
echo "Your name is: $firstname $lastname"

5. 文件读写

在下面的脚本中,第一段判断/tmp/test.log文件是否在,如果不在,则创建一个,如果在,则删除;第二段则在/tmp目录下找到7天之前的*.log日志文件并删除。

#!/bin/bash

if [ ! -e /tmp/test.log ]; then
 echo "file doesn't exist, try to create one"
 touch /tmp/test.log
else
 echo "file exists there already, try to remove it"
 rm /tmp/test.log
fi

#remove log file which is created earlier than 7 days ago
find /tmp -mtime +7 -type f -name *.log -exec rm -f {} \;
find /tmp -mtime +7 -type f -name [ab].log -exec rm -f {} \;

6. 脚本的执行

脚本的执行有下面几种方式,可以在shell中直接运行脚本,也可以使用sh来调用,也可以使用source来调用。其中source的调用执行后,脚本的变量声明都会留在当前shell中。

./test.sh
sh ./test.sh
source ./test.sh
nohup sh ./loop.sh &

最后一个是运行loop.sh脚本在后台,测试脚本如下,

#!/bin/bash

while true
do
 echo "sleep 1 seconds"
 date +%Y%m%d%H%M%S >> /tmp/date.log
 sleep 1
done

结尾

上述脚本代码可以到此git代码仓库(https://git.oschina.net/pphh/shellTmpl.git)中获取,此代码仓库提供更多的shell脚本模板,比如打印系统版本信息,硬件信息等