参考:
设计模式之单例模式(线程安全)
Java单例模式:为什么我强烈推荐你用枚举来实现单例模式
package demo28;
/**
* 这种代码中的一个缺点是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 。现在流行的设计都是讲“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象。所以这种适合在小系统。
*/
public class Singleton1 {
private static Singleton1 sin = new Singleton1(); ///直接初始化一个实例对象
private Singleton1() { ///private类型的构造函数,保证其他类对象不能直接new一个该对象的实例
}
public static Singleton1 getSin() { ///该类唯一的一个public方法
return sin;
}
public static void main(String[] args) {
Runnable runnable = () -> System.out.println(Singleton1.getSin());
new Thread(runnable,"t1").start();
new Thread(runnable,"t2").start();
}
}
运行结果
demo28.Singleton1@686e97cd
demo28.Singleton1@686e97cd
package demo28;
/**
* 缺点:锁住了一个方法,锁的力度有点大
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){
}
public static synchronized Singleton2 getInstance(){ //对获取实例的方法进行同步
if (instance == null)
instance = new Singleton2();
return instance;
}
public static void main(String[] args) {
Runnable runnable = () -> System.out.println(Singleton2.getInstance());
new Thread(runnable,"t1").start();
new Thread(runnable,"t2").start();
}
}
运行结果
demo28.Singleton2@47f6725c
demo28.Singleton2@47f6725c
package demo28;
/**
* 改进:不锁住整个方法,只锁住其中的new语句就OK。就是所谓的“双重锁”机制
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3(){
}
public static Singleton3 getInstance(){ //对获取实例的方法进行同步
if (instance == null){
synchronized(Singleton3.class){
if (instance == null)
instance = new Singleton3();
}
}
return instance;
}
public static void main(String[] args) {
Runnable runnable = () -> System.out.println(Singleton3.getInstance());
new Thread(runnable,"t1").start();
new Thread(runnable,"t2").start();
}
}
运行结果
demo28.Singleton3@76575e50
demo28.Singleton3@76575e50
package demo28;
public class Singleton4 {
private Singleton4() {
System.out.println("初始化Singleton4..");
}
private static class Inner { //静态内部类
static{
System.out.println("Inner静态内部类加载了...");
}
private static Singleton4 s = new Singleton4();
}
private static Singleton4 getSingle() {
System.out.println("获取Single实例...");
return Inner.s;
}
public static void main(String[] args) {
Runnable runnable = () -> System.out.println(Singleton4.getSingle());
new Thread(runnable,"t1").start();
new Thread(runnable,"t2").start();
}
}
运行结果
获取Single实例...
获取Single实例...
Inner静态内部类加载了...
初始化Singleton4..
demo28.Singleton4@1d8087e8
demo28.Singleton4@1d8087e8
从运行结果可以看出来,内部类和静态内部类都是延时加载的,也就是说只有在明确用到内部类时才加载。只使用外部类时不加载。
package demo28;
/**
* 目前最佳的单例写法——枚举模式—— 《Effective Java》
*/
public enum Singleton5 {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
public static void main(String[] args) {
Singleton5.INSTANCE.doSomething();
}
}
Effective Java这本书以后有机会拜读一下。这里就不细讲了,以后在慢慢研究一下枚举的用法。感兴趣的可以参考上面的链接。
参考:https://blog.csdn.net/zl_StepByStep/article/details/88819859
package demo29;
import java.util.ArrayList;
import java.util.List;
/**
下面程序模拟卖票可能会出现两个问题:
1.票卖重了
2.还剩最后一张票时,好几个线程同时抢,出现-1张票
出现上面两个问题主要是因为:
1.remove()方法不是原子性的
2.判断+操作不是原子性的
*/
public class TicketSeller1 {
static List<String> tickets = new ArrayList<>();
static {
for (int i = 0; i < 10000; i++) { //共一万张票
tickets.add("票编号--" + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { //共10个线程卖票
new Thread(() -> {
while (tickets.size() > 0) { //判断余票
System.out.println("销售了..." + tickets.remove(0)); //操作减票
}
}).start();
}
}
}
运行结果
Exception in thread "Thread-5" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.remove(ArrayList.java:501)
at demo29.TicketSeller1.lambda$main$0(TicketSeller1.java:26)
at java.lang.Thread.run(Thread.java:745)
package demo29;
import java.util.Vector;
/**
* 本程序虽然用了Vector作为容器,Vector中的方法都是原子性的,但是在判断size和减票的中间还是可能被打断的,即被减到-1张
*/
public class TicketSeller2 {
static Vector<String> tickets = new Vector<>(); //Vector是一个同步容器
static {
for (int i = 0; i < 100; i++) tickets.add("票编号-" + i);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (tickets.size() > 0) { //判断余票
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("销售了--" + tickets.remove(0)); //操作减票
}
}).start();
}
}
}
运行结果
Exception in thread "Thread-9" Exception in thread "Thread-8" Exception in thread "Thread-0" Exception in thread "Thread-1" Exception in thread "Thread-7" Exception in thread "Thread-5" Exception in thread "Thread-6" Exception in thread "Thread-4" Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
at java.util.Vector.remove(Vector.java:831)
at demo29.TicketSeller2.lambda$main$0(TicketSeller2.java:24)
at java.lang.Thread.run(Thread.java:745)
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
at java.util.Vector.remove(Vector.java:831)
at demo29.TicketSeller2.lambda$main$0(TicketSeller2.java:24)
at java.lang.Thread.run(Thread.java:745)
package demo29;
import java.util.LinkedList;
import java.util.List;
/*将判断和操作外面加锁,程序完全没有功能上的问题,但是效率很低*/
public class TicketSeller3 {
static List<String> tickets = new LinkedList<>();
static {
for (int i = 0; i < 100; i++) { //共100张票
tickets.add("票编号:" + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { //共10个线程卖票
new Thread(() -> {
while (true) {
synchronized (tickets) {
if (tickets.size() <= 0) break; //判断 余票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("销售了--" + tickets.remove(0)); //操作减票
}
}
}).start();
}
}
}
package demo29;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* ConcurrentLinkedQueue底层不是加锁的实现,而是使用 CAS 原子指令来处理对数据的并发访问,非阻塞式,效率高很多
*/
public class TicketSeller4 {
static Queue<String> tickets = new ConcurrentLinkedQueue<>();
static {
for (int i=0; i<1000; i++) {
System.out.println("票编号:" + i );
}
}
public static void main(String[] args) {
for (int i=0; i<10; i++) {
new Thread( ()-> {
while(true) {
String str = tickets.poll(); //poll方法是原子性的,拿出一张票
//这里的判断和操作不是原子性的,但是也不会有线程安全问题,因为没有对queue做任何修改操作
if(str == null) break; //先poll,再判断tickets是不是空的,最后没有任何操作,所以不用加锁也不会出现任何问题
else System.out.println("销售了.." + str);
}
}).start();
}
}
}
这里就简单讲一下使用,以后有机会在研究一下底层的实现。
package demo30;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
* 阅读ConcurrentSkipListMap
* http://blog.csdn.net/sunxianghuang/article/details/52221913
*
* 比较一下ConcurrentHashMap和Hashtable的效率
*
* ConcurrentHashMap效率应该是比较高的,因为ConcurrentHashMap和Hashtable加锁的方式不一样。
* Hashtable是把自己整个都加锁了,而ConcurrentHashMap是采取锁分段。
*/
public class T {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
// Map map = new ConcurrentSkipListMap<>(); //高并发,排序。插入时效率比较低。查快
// Map map = new Hashtable<>(); // 所有操作加锁的,效率低
// Map map = new HashMap<>(); //没有锁,但是可以通过Collections.synchronizedXXXX去加锁
// Map map = new TreeMap<>(); //插入时要排序,所以插入可能会比较慢
Random r = new Random();
Thread[] threads = new Thread[100];
CountDownLatch latch = new CountDownLatch(threads.length); //门闩计数器 100
long start = System.currentTimeMillis(); //开始时间
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) { //向map中加入1万个随机字符串
map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));
}
latch.countDown(); //每执行一个线程,就countdown一次
});
}
Arrays.asList(threads).forEach(Thread::start); //所有线程启动
try {
latch.await(); //主线程在这等着,直到countdown到0
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis(); //结束时间
System.out.println(end - start); //程序执行时间
}
}
简单总结:Map和Set本质上是一样的,只是Set只有key,没有value,所以下面谈到的Map可以替换成Set。
- 在不加锁的情况下,可以用:HashMap、TreeMap、LinkedHashMap。想加锁可以用Hashtable(用的非常少)。
- 在并发量不是很高的情况下,可以用Collections.synchronizedXxx()方法,在该方法中传一个不加锁的容器(如Map),它返回一个加了锁的容器(容器中的所有方法加锁)!
- 在并发性比较高的情况下,用ConcurrentHashMap ,如果并发性高且要排序的情况下,用ConcurrentSkipListMap。
这里就简单讲一下使用,以后有机会在研究一下底层的实现。
CopyOnWriteArrayList在多线程环境下,写时效率低,读时效率高,适合写少读多的环境,比如事件监听器。
package demo31;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 写时复制:添加元素的时候,会把这个容器复制一份,在复制的那份后面加一个新的,将引用指向复制的那份。
* 读的时候不用加锁,适合写的很少,读的特别多的时候。
* */
public class T {
public static void main(String[] args) {
List<String> list =
// new ArrayList<>(); //这个会出并发问题,最后size<100000,,运行时间:0.1秒多
// new Vector<>(); //size=100000,,运行时间:0.1秒多
new CopyOnWriteArrayList<>(); //size=100000,写效率很低,因为一直在"复制、写",运行时间:5秒多
Random r = new Random();
Thread[] threads = new Thread[100];
for (int i=0; i<threads.length; i++) { //起100个线程,每个线程向容器中加1000个数(最终应该是10万个数)
Runnable task = () -> {
for (int j=0; j<1000; j++) list.add("a" + r.nextInt());
};
threads[i] = new Thread(task);
}
runAndComputeTime(threads);
System.out.println(list.size());
}
static void runAndComputeTime(Thread[] threads) {
long start = System.currentTimeMillis();
Arrays.asList(threads).forEach(Thread::start);
Arrays.asList(threads).forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
返回一个加了锁的容器List
package demo32;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class T {
public static void main(String[] args) {
List<String> strs = new ArrayList<>();
List<String> strsSync = Collections.synchronizedList(strs);
}
}
package demo33;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentQueue {
public static void main(String[] args) {
Queue<String> strs = new ConcurrentLinkedQueue<>(); //还有双端队列...Deque
for (int i = 0; i < 10; i++) {
//类似于add方法,如果是ArrayQueue,add方法可能会抛异常,但是offer方法不会抛异常,返回boolean类型即是否添加成功
//strs.add("a" + i);
strs.offer("a" + i);
}
System.out.println(strs); //[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
System.out.println("队列原始大小:" + strs.size()); //队列原始大小:10
//poll方法表示从头上拿出一个删掉;peek方法表示从头上拿出一个用一下不删。
System.out.println("poll " + strs.poll() + "后的大小为:" + strs.size()); //poll a0后的大小为:9
System.out.println("peek " + strs.peek() + "后的大小为:" + strs.size()); //peek a1后的大小为:9
}
}
运行结果
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
队列原始大小:10
poll a0后的大小为:9
peek a1后的大小为:9
package demo33;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/*无界阻塞式队列*/
public class LinkedBlockingQueueTest {
static BlockingQueue<String> strs = new LinkedBlockingQueue<>();
static Random r = new Random();
public static void main(String[] args) {
new Thread(() -> { //1个生产者线程
for (int i = 0; i < 100; i++) {
try {
strs.put("a" + i); //如果满了,就会等待
TimeUnit.MILLISECONDS.sleep(r.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "producer").start();
for (int i = 0; i < 5; i++) { //5个消费者进程
new Thread(() -> {
for (; ; ) {
try {
System.out.println(Thread.currentThread().getName()
+ " take-" + strs.take()); //如果空了,就等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "customer" + i).start();
}
}
}
package demo33;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/*有界阻塞式队列*/
public class ArrayBlockingQueueTest {
static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10); //最多装10个
static Random r = new Random();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
try {
strs.put("a" + i); //向容器中添加10个,就满了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try { //strs已经满了,以下方法都加不进去,但是处理方式不同
strs.put("aaa");//发现满了,就会等待,程序阻塞
// strs.add("aaa"); //已经满了,再往里面装就会报异常
// strs.offer("aaa");//不会报异常,但是加不进去,返回是否添加成功
// strs.offer("aaa",1, TimeUnit.SECONDS); //1秒钟后加不进去,就不往里面加了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(strs);
}
}
往DelayQueue里加的元素是按时间排好序的,该队列是无界的。另外元素要实现Delayed接口,而Delayed接口又继承了Comparable接口,所以该类元素需要实现compareTo()方法;并且每个元素记载着自己还有多长时间才能被拿走,还要实现getDelay()方法。
package demo33;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueTest {
static DelayQueue<MyTask> tasks = new DelayQueue<>();
static class MyTask implements Delayed { //实现Delayed接口
long runningTime;
String name;
MyTask(long rt, String name) {
this.runningTime = rt;
this.name = name;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
return -1;
else if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS))
return 1;
else // ==
return 0;
}
@Override
public String toString() {
return name + "--" + runningTime;
}
}
public static void main(String[] args) {
long now = System.currentTimeMillis();
MyTask t1 = new MyTask(now + 1000, "task1"); //1 s 后执行 //②
MyTask t2 = new MyTask(now + 2000, "task2"); //2 s后执行 //④
MyTask t3 = new MyTask(now + 1500, "task3"); //1.5s后执行 //③
MyTask t4 = new MyTask(now + 500, "task4"); //0.5s后执行 //①
MyTask t5 = new MyTask(now + 2500, "task5"); //2.5s后执行 //⑤
tasks.put(t1);
tasks.put(t2);
tasks.put(t3);
tasks.put(t4);
tasks.put(t5);
System.out.println(tasks);
for (int i = 0; i < 5; i++) {
try {
System.out.println(tasks.take()); //按放进去的顺序拿出
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果
[task4--1578152889744, task1--1578152890244, task3--1578152890744, task2--1578152891244, task5--1578152891744]
task4--1578152889744
task1--1578152890244
task3--1578152890744
task2--1578152891244
task5--1578152891744
适用场景:消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者,若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。TransferQueue适用于更高的并发情况
消费者先启动,然后调用transfer方法,这个时候不会阻塞
package demo33;
import java.util.concurrent.LinkedTransferQueue;
/**
* 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者,
* 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。
*/
public class TransferQueueTest {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
new Thread(() -> { //消费者先启动,可以拿走aaa
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
strs.transfer("aaa");
}
}
运行结果
aaa
如果是先调用transfer方法,然后在启动消费者线程,这个时候就会阻塞了。
package demo33;
import java.util.concurrent.LinkedTransferQueue;
/**
* 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者,
* 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。
*/
public class TransferQueueTest2 {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
strs.transfer("aaa");
new Thread(() -> { //消费者在生产者后启动,拿不到aaa,程序阻塞
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
如果是调用put方法的话就不会阻塞
package demo33;
import java.util.concurrent.LinkedTransferQueue;
/**
* 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者,
* 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。
*/
public class TransferQueueTest3 {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
strs.put("aaa"); //如果用put的话就不会阻塞了
new Thread(() -> { //后启动消费者
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
运行结果
aaa
特殊的TransferQueue,容量为0。扔在队列的东西必须被消费者马上消费掉,否则就会出问题。
package demo33;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
/*一种特殊的TransferQueue,生产的任何一个东西必须直接交给消费者消费,不能搁在容器里,容器的容量为0*/
public class SynchronizeQueueTest {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> strs = new SynchronousQueue<>();
new Thread(() -> { //消费者线程
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
strs.put("aaaa"); //不能调用add(报错),add不进去,put阻塞,等待消费者消费,内部调用的transfer.
System.out.println(strs.size()); //0
}
}
运行结果
0
aaaa
https://www.bilibili.com/video/av33688545?p=20
设计模式之单例模式(线程安全)
Java单例模式:为什么我强烈推荐你用枚举来实现单例模式
https://blog.csdn.net/zl_StepByStep/article/details/88819859
https://gitee.com/cckevincyh/java_concurrent_learning