线程安全的singleton(单例模式,线程安全)
单例模式部分内容,来源:https://www.cnblogs.com/xudong-bupt/p/3433643.html
1.多线程安全单例模式实例一(不使用同步锁)
public class Singleton {
private static Singleton sin=new Singleton(); ///直接初始化一个实例对象
private Singleton(){ ///private类型的构造函数,保证其他类对象不能直接new一个该对象的实例
}
public static Singleton getSin(){ ///该类唯一的一个public方法
return sin;
}
}
上述代码中的一个缺点是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 。现在流行的设计都是讲“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象。所以这种适合在小系统。
2.多线程安全单例模式实例二(使用同步方法)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance(){ //对获取实例的方法进行同步
if (instance == null)
instance = new Singleton();
return instance;
}
}
上述代码中的一次锁住了一个方法, 这个粒度有点大 ,改进就是只锁住其中的new语句就OK。就是所谓的“双重锁”机制。
3.多线程安全单例模式实例三(使用双重同步锁)
public 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;
}
}
并发容器
问题抛出:
/**
*10000张票出售的问题
*10线程去抢票
* @author zhouyi
*
*/
public class TicketSeller {
static List<String> ticketList = new ArrayList<String>();
static {
for(int i = 0;i < 10000; i++)
ticketList.add("票编号:" + i);
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
new Thread(()->{
while (ticketList.size() > 0) {
System.out.println("销售了。。" + ticketList.remove(0));
}
}).start();
}
}
}
多次运行得到下面的结果:简单来说,票卖重了(原因List,ArrayList他们以及他们的方法都不是同步的,所以一定会出问题)
那么我们进一步,用具有原子性的方法(Vector以及他的方法都具有原子性)行不行呢?
public class TicketSeller {
static Vector<String> ticketList = new Vector();
static {
for(int i = 0;i < 10000; i++)
ticketList.add("票编号:" + i);
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
new Thread(()->{
while (ticketList.size() > 0) {
/**
***如果这一块有不具备原子性的操作,程序一样会挂掉。**比如我们加一块睡眠操作
*/
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("销售了。。" + ticketList.remove(0));
}
}).start();
}
}
}
得到运行结果如下:
那么我们加上锁,肯定是可以的(运行结果就不给出了)
public class TicketSeller {
static Vector<String> ticketList = new Vector();
static {
for(int i = 0;i < 10000; i++)
ticketList.add("票编号:" + i);
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
new Thread(()->{
while(true)
{
synchronized (ticketList) {
if (ticketList.size() <= 0) break;
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("销售了。。" + ticketList.remove(0));
}
}
}).start();
}
}
}
但是加上锁的效率是不高的。在java1.5后新增加的并发容器,可以很好解决这个问题。
并发容器–Queue(ConcurrentLinkedDeque,BlockingQueue等的实现)
public class TicketSeller {
//ConcurrentLinkedDeque :支持并发多线程队列
static Queue<String> ticketList = new ConcurrentLinkedDeque<String>();
static {
for(int i = 0;i < 10000; i++)
ticketList.add("票编号:" + i);
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
new Thread(()->{
while(true)
{
String string = ticketList.poll(); // 同步往外拿出一个数据
if(string == null) break; //这一步也不是原子性的,但是后面没有再对Queue进行操作,所以不会有问题
System.out.println("销售了。。" + string);
}
}).start();
}
}
}
并发容器–ConcurrentMap<K, V>
public class ConcurrentMap {
public static void main(String[] args) {
// 对应得还有set,这里就不具体列举
// Map<String, String> map = new ConcurrentHashMap<String, String>();// 并发量很高的情况下,使用
//Map<String, String> map = new ConcurrentSkipListMap<String, String>(); //并发量很高,且需要排序,使用, 默认排好顺序
Map<String, String> map = new Hashtable<String, String>(); // 所有的都带锁,但是效率低,现在基本不用
// Map<String, String> map = new HashMap<String, String>(); // 不加锁
// Map<String, String> map = new TreeMap<String, String>(); // 默认排好顺序 ,不加锁
Random random = new Random();
Thread[] ths = new Thread[100]; // 线程数组
CountDownLatch latch = new CountDownLatch(ths.length);
long start = System.currentTimeMillis();
for(int i = 0; i < ths.length; i++) {
ths[i] = new Thread(()->{
for(int j = 0; j < 10000; j++) map.put("a" + random.nextInt(10000), "a" + random.nextInt(10000));
latch.countDown(); // 门闩减一
});
}
Arrays.asList(ths).forEach(t->t.start());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start); //执行时间
}
}
并发容器–CopyOnWriteArrayList (读操作并发量大时使用)
public class CopyOnWrite {
public static void main(String[] args) {
// 如果用ArrayList和Vector都会出现并发问题
// 该容器的特点:写时复制,读的效率非常高,但写的效率就非常低
List<String> list= new CopyOnWriteArrayList<String>(); // 效率很慢
Random random = new Random();
Thread[] ths = new Thread[100]; // 线程数组
for(int i = 0; i < ths.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for(int j = 0; j < 1000; j++) list.add("a" + random.nextInt(10000));
}
};
ths[i] = new Thread(task);
}
runAndComputeTime(ths);
System.out.println(list.size());
}
static void runAndComputeTime(Thread[] ths) {
long start = System.currentTimeMillis();
Arrays.asList(ths).forEach(t->t.start());
Arrays.asList(ths).forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println(end - start); //执行时间
}
}
并发容器–Queue
public class Queue1 {
public static void main(String[] args) {
// 这是单向队列,如果又需要可以使用双向队列
// 高并发时,队列主要使用ConcurrentLinkedQueue,BlockingQueue
Queue<String> queue = new ConcurrentLinkedQueue<String>();
for(int i = 0;i < 10; i++) {
queue.offer("a" + i); // 相当于add
}
System.out.println(queue);
System.out.println(queue.size());
System.out.println(queue.poll()); // 从头哪走一个,并删除
System.out.println(queue.size());
System.out.println(queue.peek()); // 从头拿走一个,不删除
System.out.println(queue.size());
}
}
并发容器–BlockingQueue(用的非常多),阻塞队列
生产者和消费者完善。
LinkedBlockingDeque(无界队列)来实现
public class Queue2 {
// 无界队列
static BlockingQueue<String> queue = new LinkedBlockingDeque<String>();
static Random random = new Random();
public static void main(String[] args) {
new Thread(()->{
for(int i = 0; i < 100; i++) {
try {
queue.add("a" + i); // 如果填满了就会自动进入等待
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"th1").start();
for(int i = 0; i < 5; i++) {
new Thread(()->{
for(;;) { // 无限循环
try {
System.out.println(Thread.currentThread().getName() + "take" + queue.take()); // 如果空了 就继续
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"th2" + i).start();
}
}
}
ArrayBlockingQueue(有界队列)来实现
public class Queue2 {
//有界队列
static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
static Random random = new Random();
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
queue.add("a" + i);
}
queue.offer("aaa"); // 可以得到返回值,来判断是否加成功。
//queue.offer("aaa", 1, TimeUnit.SECONDS);
System.out.println(queue);
}
}
运行结果:
DelayQueue --(延迟队列,并排好序)无界队列-来实现
是需要实现Delayed接口的
这个可以用来干什么呢?可以用来定时执行任务
public class Queue3 {
//无界队列
static BlockingQueue<MyTask> tasks = new DelayQueue<>();
static Random random = new Random();
static class MyTask implements Delayed {
long runningTime;
MyTask(long rt) {
this.runningTime = rt;
}
@Override
public String toString() {
return "" + runningTime;
}
@Override
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MICROSECONDS) < o.getDelay(TimeUnit.MICROSECONDS))
return -1;
else if(this.getDelay(TimeUnit.MICROSECONDS) > o.getDelay(TimeUnit.MICROSECONDS))
return 1;
else
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MICROSECONDS);
}
}
public static void main(String[] args) throws InterruptedException { // throws InterruptedException 这里抛出同类异常,
//可以避免程序类相同异常抓取,造成代码块重复。
long now = System.currentTimeMillis();
MyTask myTask1 = new MyTask(now + 1000);
MyTask myTask2 = new MyTask(now + 2000);
MyTask myTask3 = new MyTask(now + 2500);
MyTask myTask4 = new MyTask(now + 1000);
MyTask myTask5 = new MyTask(now + 500);
tasks.put(myTask1);
tasks.put(myTask2);
tasks.put(myTask3);
tasks.put(myTask4);
tasks.put(myTask5);
System.out.println(tasks);
for(int i = 0; i < 5; i++) {
System.out.println(tasks.take());
}
}
}
并发容器–LinkedTransferQueue(有一定容量)超高并发时使用
public class LinkedTransfer {
public static void main(String[] args) throws InterruptedException {
// 有一定容量
LinkedTransferQueue<String> strs = new LinkedTransferQueue<String>();
new Thread(()->{
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 有一个队列,消费者启动,生产者生产出的产品,不先去放入队列
// 而是直接去找有没有消费者,如果有就直接扔给消费
// 在超高并发时使用
strs.transfer("aaa");
//如果先启动生产者,如果找不到消费者就会阻塞
}
}
并发容器–SynchronousQueue(容量为0)
public class SynchronousQueue1 {
public static void main(String[] args) throws InterruptedException {
// 0容量
BlockingQueue<String> strs = new SynchronousQueue<String>();
new Thread(()->{
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//strs.transfer("aaa");
strs.put("aaa"); // 阻塞等待消费者
System.out.println(strs.size());
}
}
总结:
1,对于map/set选择使用,我只列出了map,set一样有的。
没有并发:
HashMap , TreeMap,LinkedHashMap。
较小并发量:
Hashtable , Collections.synchronizedXXX
并发量较大:
ConcurrentHashMap
且要求排序:
ConcurrentSkipListMap
2,队列
不需要同步的队列:
ArrayList , LinkedList , Collections.synchronizedXXX
同步队列:
ConcurrentLinkedQueue , BlockingQueue (实现方式:LinkedBlockingDeque ,LinkedTransferQueue , ArrayBlockingQueue, SynchronousQueue), DelayQueue(执行定时任务)
特殊:CopyOnWriteArrayList(读操作并发量大时使用)