饿汉模式:在类加载的时候就创建实例.
class SingleTon{
//在类加载的时候就创建了单一实例
private static SingleTon instance = new SingleTon();
//构造器设置成私有防止用户创建别的实例
private SingleTon(){
}
//设置成static方便调用该实例
public static SingleTon getInstance(){
return instance;
}
}
懒汉模式:类加载的时候不创建实例,在第一次调用的时候才创建实例
class SingleTon1{
//类加载额时候instance 为null
private static SingleTon1 instance = null;
private SingleTon1(){}
public static SingleTon1 getInstance(){
//第一次调用时创建实例
if(instance == null){
instance = new SingleTon1();
}
return instance;
}
}
上述懒汉模式的代码在单线程的情况下是可以正常运行的.但是当多个线程并发调用懒汉模式时,上述代码就会因为并发冲突产生一些问题.
出现问题的原因是因为当多个线程同时调用getInstance()方法时,判断instance是否为null和给instance创建实例这两个操作不是原子性的.
因此在多线程环境下,我们需要给getInstance方法加锁.
class SingleTon1{
private static SingleTon1 instance = null;
private static Object locker = new Object();
private SingleTon1(){}
public static SingleTon1 getInstance(){
synchronized (locker) {
if (instance == null) {
instance = new SingleTon1();
}
}
return instance;
}
}
加锁之后,多个线程在调用getInstance方法时就不会产生线程冲突.但此时又引发了一个问题:效率问题.我们知道单例模式只创建1个实例对象.因此当第1个线程抢占到锁并为instance创建实例之后,剩下的线程再调用getInstance方法就直接return就好.因此对于除第一个线程之外的线程加锁其实上是在浪费资源.因此我么可以在锁外面在加上一层判断语句.
class SingleTon1{
private static SingleTon1 instance = null;
private static Object locker = new Object();
private SingleTon1(){}
public static SingleTon1 getInstance(){
if(instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new SingleTon1();
}
}
}
return instance;
}
}
这样我们就可以节省不必要的锁开销.但尽管如此,当线程的数量变多之后,最外层的判断语句就会被大量执行,这时就会引发内存可见性问题:即原本应该从内存中读取的instance被编译器自动优化到了从寄存器中读取.这时如果我们对instance进行了其他的操作导致instance重新变为null那么也不会触发创建实例的操作.因此我们需要给instance设置volatile属性来抑制内存可见性.
class SingleTon1{
private static volatile SingleTon1 instance = null;
private static Object locker = new Object();
private SingleTon1(){}
public static SingleTon1 getInstance(){
if(instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new SingleTon1();
}
}
}
return instance;
}
}
至此,一个简单完整的懒汉模式则创建好了.懒汉模式相比较与饿汉模式而言效率更高,但需要注意的问题也更多.
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
blockingQueue.put(10);
int ans1 = blockingQueue.take();
//ans2在取元素时会因为队列空而阻塞.
int ans2 = blockingQueue.take();
}
class MyBlockingQueue{
private int[] item = new int[1000];
private int front = 0;
private int rear = 0;
private int size = 0;
synchronized public void put(int num) throws InterruptedException {
if(size >= item.length){
this.wait();
}
item[rear] = num;
//模拟循环队列,当队尾指针指向队列的最后一个元素时,将其指向0
if(rear == item.length-1){
rear = 0;
}else {
rear++;
}
this.notify();
size++;
}
synchronized public Integer take() throws InterruptedException {
if(size == 0){
this.wait();
}
int ans = item[front];
//模拟循环队列,当队首指针指向队列的最后一个元素时,将其指向0
if(front == item.length-1) {
front = 0;
}else{
front++;
}
this.notify();
size--;
return ans;
}
public int getSize(){
return size;
}
}
对于阻塞队列处于阻塞状态,我们可以通过Thread类的wait()和notify()方法实现阻塞和唤醒的状态.
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行任务");
}
},2000);
}
@SuppressWarnings("all")
class MyTask implements Comparable<MyTask>{
//创建任务类:该类对象在传入的时候需要传入该任务的执行时刻和具体的任务.
//因为任务类需要存储到堆中,所以要指定任务类在堆中的排列规则
private Runnable runnable;
private long delay;
public MyTask(Runnable r,long d){
runnable = r;
delay = System.currentTimeMillis()+d;
}
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask t) {
return (int)(this.delay - t.getDelay());
}
public long getDelay(){
return this.delay;
}
}
@SuppressWarnings("all")
class MyTimer{
//创建优先级队列
private PriorityBlockingQueue<MyTask> p = new PriorityBlockingQueue<>();
private Object locker = new Object();
public void MyTimer(){
Worker worker = new Worker();
worker.start();
}
//需要创建一个线程来持续不断的读取堆中的任务
class Worker extends Thread{
@Override
public void run() {
while(true){
long time = System.currentTimeMillis();
MyTask task = p.peek();
//获取到当前堆中的首元素的待执行时刻和当前时刻进行比较
if(task.getDelay() <= time){
task.run();
p.poll();
}else{
//代码执行到这里说明任务还没有到达执行的时刻,所以需要等待.
synchronized (locker){
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public void schedule(Runnable runnable,long delay) {
MyTask task = new MyTask(runnable,delay);
p.offer(task);
//考虑到在任务等待执行时刻到来的过程中仍有可能有任务入队列,且该任务的执行时刻要早于堆中的首任务,所以在
//每次安排任务的时候都需要唤醒一次等待.
synchronized (locker){
locker.notify();
}
}
}
class ThreadPool{
//用一个阻塞队列来存储任务,阻塞队列可以保证线程安全
private BlockingQueue<Runnable> p = new LinkedBlockingQueue<>();
//创建工作线程
static class Worker extends Thread{
BlockingQueue<Runnable> q;
public Worker(BlockingQueue q){
this.q = q;
}
@Override
public void run() {
while(true){
try {
Runnable r = q.take();
r.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//用一个顺序表来模拟线程池存储多个工作线程
private List<Worker> list = new ArrayList<>();
public ThreadPool(int n){
for(int i = 0; i < n; i++){
Worker worker = new Worker(p);
worker.start();
list.add(worker);
}
}
public List<Worker> getList(){
return list;
}
//将任务呈递到阻塞队列中
public void submit(Runnable r){
p.offer(r);
}
}
本篇文章主要介绍了多线程的一些常见案例:单例模式,阻塞队列,定时器以及线程池并根据其原理进行了简单的实现.