单例模式
应用:单例模式
饿汉式单例(线程安全)
特点:线程安全,相对于懒汉式单例,其开销相比高一点
//饿汉式单例
class Person{
//1.私有化类的构造器,使其在外部不能new
private Person(){}
//2.内部创建类的对象,要求此对象也必须声明为静态的—随着类的创建而创建,从创建到销毁只有一份
private static Person instance = new Person();
//3.提供公共的静态的方法,返回类的对象
public static Person getInstance(){
return instance;
}
}
懒汉式单例(线程不安全版本)
特点:使用的时候才去new;线程不安全,需要改造
class Person {
//1.私有化类的构造器
private Person(){}
//2.声明当前类对象(由于第3步的getInstance也是static,故此对象也必须声明为static)
private static Person instance = null;
//3.声明public、static的返回当前类对象的方法
public static Person getInstance(){
if(instance == null){
instance = new Person();
}
return instance;
}
}
懒汉式单例(线程非绝对安全版本)
class AppServiceProcessor{
private AppServiceProcessor(){}
private static AppServiceProcessor instance = null;
public static AppServiceProcessor getInstance(){
if(instance == null){
synchronized (AppServiceProcessor.class) {
if(instance == null){
instance = new AppServiceProcessor();
}
}
}
return instance;
}
}
懒汉式单例(线程安全版本)=>加入volatile关键字
class AppServiceProcessor{
private AppServiceProcessor(){}
private static volatile AppServiceProcessor instance = null;
public static AppServiceProcessor getInstance(){
if(instance == null){
synchronized (AppServiceProcessor.class) {
if(instance == null){
instance = new AppServiceProcessor();
}
}
}
return instance;
}
}
生产者/消费者问题
使用synchronized+lockObj.wait()+lockObj.notify()来实现
public class ProducerAndConsumer {
static int count = 10;
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
//消费者线程:小于0个就停止消费,否则一直消费
Thread tdConsumer = new Thread() {
@Override
public void run() {
super.run();
try {
lock.lock();
while (true) {
if (count <= 0) {
condition.signal();
condition.await();
} else {
count--;
System.out.println(" 消费一个,剩余" + count);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
tdConsumer.start();
//生产着线程:大于10个就一直生产
Thread tdProducer = new Thread() {
@Override
public void run() {
super.run();
try {
lock.lock();
while (true) {
if (count < 10) {
count++;
System.out.println("生产一个,剩余" + count);
} else {
condition.signal();
condition.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
tdProducer.start();
}
}
//效果
消费一个,剩余9
消费一个,剩余8
消费一个,剩余7
消费一个,剩余6
消费一个,剩余5
消费一个,剩余4
消费一个,剩余3
消费一个,剩余2
消费一个,剩余1
消费一个,剩余0
生产一个,剩余1
生产一个,剩余2
生产一个,剩余3
生产一个,剩余4
生产一个,剩余5
生产一个,剩余6
生产一个,剩余7
生产一个,剩余8
生产一个,剩余9
生产一个,剩余10
使用lock+ lockobj.await+ lockobj.signal来实现
需求:消费者线程小于0个就停止消费,阻塞并唤醒生产者线程生产,否则一直消费;生产者线程大于10个就一直生产,否则阻塞并唤醒消费者线程进行消费。
class Ecoco10ApplicationTests {
private Object object = new Object();
private int count = 0;
@Test
void TestThread() throws InterruptedException {
//生产者线程
Thread threadProducer = new Thread() {
@SneakyThrows
@Override
public void run() {
while (true) {
synchronized (object) {
Thread.sleep(3000);
if (count == 0) {
count++;
System.out.println("线程1进行了生产" + count);
object.notify();//已经生产完了,通知被阻塞的消费线程
}
object.wait();//已经生产完了,阻塞当前线程并释放同步监视器
}
}
}
};
//消费者线程
Thread threadConsumer = new Thread() {
@SneakyThrows
@Override
public void run() {
while (true) {
synchronized (object) {
Thread.sleep(3000);
if (count > 0) {
count--;
System.out.println("线程2进行了消费" + count);
object.notify();//已经消费完了,通知被阻塞的生产线程
}
object.wait();//已经消费完了,阻塞当前线程并释放同步监视器
}
}
}
};
threadProducer.start();
threadConsumer.start();
Thread.sleep(50000);
}
}
使用阻塞队列来实现
//实现了生产者-消费者模式的类
class MyProductorComsumer {
//默认开启,进行生产消费
//这里用到了volatile是为了保持数据的可见性,也就是当FLAG修改时,要马上通知其它线程进行修改
private volatile boolean IS_PROD = true;
//使用原子包装类,而不用number++
private AtomicInteger atomicInteger = new AtomicInteger();
//这里不能为了满足条件,而实例化一个具体的SynchronousBlockingQueue
BlockingQueue<String> blockingQueue = null;
//而应该采用依赖注入里面的,构造注入方法传入
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
//查询出传入的class是什么
System.out.println(blockingQueue.getClass().getName());
}
/**
* 生产
*
* @throws Exception
*/
public void myProd() throws Exception {
String data = null;
boolean retValue;
//多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
//当FLAG为true的时候,开始生产
while (IS_PROD) {
data = atomicInteger.incrementAndGet() + "";
//2秒生产一个数据
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "失败");
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产介绍");
}
/**
* 消费
*
* @throws Exception
*/
public void myConsumer() throws Exception {
String retValue;
//多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
//当FLAG为true的时候,开始生产
while (IS_PROD) {
// 2秒存入1个data
retValue = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (retValue != null && retValue != "") {
System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + retValue + "成功");
} else {
IS_PROD = false;
System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出");
//退出消费队列
return;
}
}
}
/**
* 停止生产的判断
*/
public void stop() {
this.IS_PROD = false;
}
}
//使用处
package com.fly.ecoco10;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class BlockingQueueDemo {
public static void main(String[] args) {
//传入具体的实现类, ArrayBlockingQueue
MyProductorComsumer myResource = new MyProductorComsumer(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
System.out.println("");
System.out.println("");
try {
myResource.myProd();
System.out.println("");
System.out.println("");
} catch (Exception e) {
e.printStackTrace();
}
}, "prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "consumer").start();
//5秒后,停止生产和消费
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("");
System.out.println("5秒中后,生产和消费线程停止,线程结束");
myResource.stop();
}
}
//效果
java.util.concurrent.ArrayBlockingQueue
prod 生产线程启动
prod 插入队列:1成功
consumer 消费线程启动
consumer 消费队列:1成功
prod 插入队列:2成功
consumer 消费队列:2成功
prod 插入队列:3成功
consumer 消费队列:3成功
prod 插入队列:4成功
consumer 消费队列:4成功
prod 插入队列:5成功
consumer 消费队列:5成功
prod 插入队列:6成功
consumer 消费队列:6成功
5秒中后,生产和消费线程停止,线程结束
prod 停止生产,表示FLAG=false,生产介绍
consumer 消费失败,队列中已为空,退出
集合类的线程安全问题
ArrayList
线程不安全写法
public class ListDemo {
static ArrayList<Integer> arrayList = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
arrayList.add(100);
System.out.println(arrayList);
}
}.start();
}
}
}
//会出现的错误:
Exception in thread "Thread-8605" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at ListDemo$1.run(ListDemo.java:17)
解决方案一:Vector
使用线程安全的Vector类,该方法在方法上加了锁。缺点是:方法加了锁,会导致并发性能下降。
public class ListDemo {
//static ArrayList<Integer> arrayList = new ArrayList<>();
static Vector<Integer> arrayList = new Vector<>();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
arrayList.add(100);
System.out.println(arrayList);
}
}.start();
}
}
}
//正常输出每次的值
解决方案二:使用Collections.synchronized()
public class ListDemo {
//static ArrayList<Integer> arrayList = new ArrayList<>();
//static Vector<Integer> arrayList = new Vector<>();
static List<Integer> arrayList = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
arrayList.add(100);
System.out.println(arrayList);
}
}.start();
}
}
}
//正常输出每次的值
解决方案三:使用JUC.CopyOnWriteArrayList类
CopyOnWriteArrayList:写时复制,主要是一种读写分离的思想。CopyOnWrite容器即写时复制的容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将Object[]进行copy,复制出一个新的容器object[] newElements,然后新的容器Object[] newElements里添加原始,添加元素完后,在将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对copyOnWrite容器进行并发的读 ,而不需要加锁,因为当前容器不需要添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写使用不同的容器。
//该类底层的添加方法
public boolean add(E e) {
//首先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//在末尾扩容一个单位
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
改造后的方法
public class ListDemo {
//static ArrayList<Integer> arrayList = new ArrayList<>();
//static Vector<Integer> arrayList = new Vector<>();
//static List<Integer> arrayList = Collections.synchronizedList(new ArrayList<>());
static CopyOnWriteArrayList<Integer> arrayList=new CopyOnWriteArrayList();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
arrayList.add(100);
System.out.println(arrayList);
}
}.start();
}
}
}
HashSet
概述:HashSet在多线程情况下是线程不安全的。
解决方案:使用CopyOnWriteArraySet进行实例化,该类底层还是使用CopyOnWriteArrayList
public class ListDemo {
static CopyOnWriteArraySet arraySet=new CopyOnWriteArraySet();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
arraySet.add(100);
}, String.valueOf(i)).start();
}
}
}.start();
}
}
}
扩展:HashSet的底层结构就是HashMap,往HashSet添加数据,key即传入的值,value即object。
public class HashSet<E>{
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
//我们能发现但我们调用add的时候,存储一个值进入map中,只是作为key进行存储,而value存储的是一个Object类型的常量,也就是说HashSet只关心key,而不关心value。value也就是固定的static final new object值
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
HashMap
概述:HashMap在多线程环境下,也是不安全的。
public class ListDemo {
static Map<String, String> map = new HashMap<>();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}.start();
}
}
}
解决方案
1、使用Map<String, String> map = Collections.synchronizedMap(new HashMap<>())
;
public class ListDemo {
static Map<String, String> map = null;
public static void main(String[] args) {
//实例化线程安全版本的Map<Key,Value>
map= Collections.synchronizedMap(new HashMap<>());
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}.start();
}
}
}
2、使用 ConcurrentHashMap
public class ListDemo {
static ConcurrentHashMap map=new ConcurrentHashMap();
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}.start();
}
}
}