Java多线程基础汇总(下)

简介: Java多线程基础汇总(下)

五. 单例模式


5.1 饿汉模式

理解饿汉,因为太饿了,所以一拿到东西就想吃,这里就是类加载的过程就直接创建唯一实例。

好处:没有线程安全问题

坏处:不像懒汉模式调用才创建实例,这个是类加载就创建实例,所以如果一直没调用,也得创建实例浪费资源。

//饿汉模式//单例实体classSingleton {
//唯一实例的本体privatestaticSingletoninstance=newSingleton();
//获取到实例的方法publicstaticSingletongetInstance() {
returninstance;
    }
//禁止外部new实例,构造器私有化privateSingleton() {
    }
}
publicclassDemo8 {
publicstaticvoidmain(String[] args) {
Singletons1=Singleton.getInstance();
Singletons2=Singleton.getInstance();
    }
}


5.2 懒汉模式

懒汉就是懒加载,在类加载的时候不会去创建实例,在第一次调用的时候才会创建实例。

非必要不创建

//懒汉模式实现单例classSingletonLazy {
//先置为空privatestaticSingletonLazyinstance=null;
publicstaticSingletonLazygetInstance() {
//只有调用getInstance方法时,才会去newif (instance==null) {
instance=newSingletonLazy();
        }
returninstance;
    }
privateSingletonLazy() {
    }
}
publicclassDemo9 {
publicstaticvoidmain(String[] args) {
SingletonLazys1=SingletonLazy.getInstance();
SingletonLazys2=SingletonLazy.getInstance();
//s1 和 s2 指向的是同一个实例System.out.println(s1==s2);
    }
}

5.3 饿汉模式和懒汉模式的线程安全问题

显而易见,对于饿汉模式,在多线程中时线程安全的,因为并没有发生修改的操作,而懒汉模式在多线程中就存在线程安全的问题,通过代码可以看见,我们需要判断instance是否为空,然后执行new对象的操作,因为线程的执行是一个抢占式的过程,所以在这里我们需要对懒汉模式进行加锁:

//懒汉模式实现单例classSingletonLazy {
//先置为空volatileprivatestaticSingletonLazyinstance=null;
publicstaticSingletonLazygetInstance() {
//只有调用getInstance方法时,才会去newif (instance==null) {
//加锁,保证线程安全问题synchronized (SingletonLazy.class) {
if (instance==null) {
instance=newSingletonLazy();
                }
            }
        }
returninstance;
    }
privateSingletonLazy() {
    }
}
publicclassDemo9 {
publicstaticvoidmain(String[] args) {
SingletonLazys1=SingletonLazy.getInstance();
SingletonLazys2=SingletonLazy.getInstance();
//s1 和 s2 指向的是同一个实例System.out.println(s1==s2);
    }
}

注:

  • 对于 instance 用 volatile 修饰的原因是禁止指令重排序问题;
  • 在getInstance方法中,我们为什么不直接对这个方法进行加锁,而是采用一个先判断的形式,再决定要不要对其加锁,这样做的好处是提高程序执行的效率,


六. 阻塞队列


6.1 概念

阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则


阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:


  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型.


6.2 生产者消费者模型

概念:假设有两个进程(或线程)A、B和一个固定大小的缓冲区,A进程生产数据放入缓冲区,B进程从缓冲区中取出数据进行计算,这就是一个简单的生产者-消费者模型。这里的A进程相当于生产者,B进程相当于消费者。  

a082d306de0346f0bb3127f0ca84707b.png

为什么要用生产者消费者模型?

     在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完数据才能够继续生产数据,因为生产过多的数据可能会导致存储不足;同理如果消费者的速度大于生产者那么消费者就会经常处理等待状态,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区用来存储生产者生产的数据,所以就引入了生产者-消费者模式

    简单来说,这里缓冲区的作用就是为了平衡生产者和消费者的数据处理能力,一方面起到缓存作用,另一方面达到解耦合作用。

生产者消费者模型代码:

//生产这消费者模型publicclassDemo10 {
publicstaticvoidmain(String[] args) {
//阻塞队列BlockingQueue<Integer>blockingQueue=newLinkedBlockingQueue<>();
//消费者线程Threadt1=newThread(()->{
while (true){
try {
intvalue=blockingQueue.take();
System.out.println("消费元素:"+value);
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        });
t1.start();
//生产者线程Threadt2=newThread(()->{
intvalue=0;
while (true){
try {
System.out.println("生产元素:"+value);
blockingQueue.put(value);
value++;
Thread.sleep(1000);
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        });
t2.start();
    }
}

6.3 模拟实现一个阻塞队列

我们可以分为三步来实现:

  • 先实现一个普通队列
  • 加上线程安全
  • 加上阻塞功能
//模拟实现一个阻塞队列classMyBlockingQueue {
privateint[] array=newint[10000];
//[head,tail)之间为有效元素volatileprivateinthead=0;
volatileprivateinttail=0;
volatileprivateintsize=0;
synchronizedpublicvoidput(intelem) throwsInterruptedException {
//判断队列是否满了if (size==array.length) {
this.wait();
        }
array[tail] =elem;
tail++;
//判断tail是否达到末尾,如果达到了,把tail置为0,从头开始if (tail==array.length) {
tail=0;
        }
size++;
this.notify();
    }
synchronizedpublicIntegertake() throwsInterruptedException {
//判断队列是否为空if (size==0) {
this.wait();
        }
intvalue=array[head];
head++;
if (head==array.length) {
head=0;
        }
size--;
this.notify();
returnvalue;
    }
}


七. 定时器


  7.1 定时器概念

   定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码.


  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)
//实现一个定时器,先执行任务2,等待2s后再执行任务1publicclassDemo12 {
publicstaticvoidmain(String[] args) {
Timertimer=newTimer();
timer.schedule(newTimerTask() {
@Overridepublicvoidrun() {
System.out.println("任务1");
            }
        },2000);//等待2s后再执行任务1System.out.println("任务2");
    }
}

6c81e0f61f424e7fae8ed0cb9f9544c7.png


7.2 模拟实现定时器

//模拟实现一个定时器//表示一个任务classMytaskimplementsComparable<Mytask> {
publicRunnablerunnable;
publiclongtime;
publicMytask(Runnablerunnable, longdelay) {
this.runnable=runnable;
//当前时刻的时间戳+定时器参数列表的时间this.time=System.currentTimeMillis() +delay;
    }
@OverridepublicintcompareTo(Mytasko) {
//确保每次取出的是时间最小的元素return (int) (this.time-o.time);
    }
}
classMyTimer {
privatePriorityBlockingQueue<Mytask>queue=newPriorityBlockingQueue<>();
publicvoidschedule(Runnablerunnable, longdelay) {
//根据参数,构造Mytask,插入到队列Mytaskmytask=newMytask(runnable, delay);
queue.put(mytask);
synchronized (locker) {
locker.notify();
        }
    }
//创建锁对象privateObjectlocker=newObject();
//创建线程,执行任务publicMyTimer() {
Threadt=newThread(() -> {
while (true) {
try {
synchronized (locker) {
Mytaskmytask=queue.take();
//获取当前时间longcurTime=System.currentTimeMillis();
//判断时间是否到了if (mytask.time<=curTime) {
mytask.runnable.run();
                        }
//时间没到else {
//取出当前执行的任务,重新塞回阻塞队列中queue.put(mytask);
locker.wait(mytask.time-curTime);
                        }
                    }
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        });
t.start();
    }
}
publicclassDemo13 {
publicstaticvoidmain(String[] args) {
MyTimermyTimer=newMyTimer();
myTimer.schedule(newRunnable() {
@Overridepublicvoidrun() {
System.out.println("任务1");
            }
        }, 1000);
myTimer.schedule(newRunnable() {
@Overridepublicvoidrun() {
System.out.println("任务2");
            }
        }, 1000);
myTimer.schedule(newRunnable() {
@Overridepublicvoidrun() {
System.out.println("任务3");
            }
        }, 1000);
System.out.println("最开始的任务");
    }
}

ceb3ebbabad74e718ed51e2adc719097.png



八. 线程池


  线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。


8.1 构造方法参数解析

jdk1.8官方文档关于ThreadPoolExecutor的构造方法的参数解析:

39ca5b55015944dea84463ebed978479.png

corePoolSize:核心线程数(相当于一个公司的正式员工);


maximumPoolSize:最大线程数(相当于一个公司的正式员工+实习生);


如果当前任务比较多的时候,线程池会多创建一些  “临时线程”  去帮助解决任务;


如果任务比较少,线程池会多出来的  “临时线程”  给销毁掉,但是核心线程数不会销毁;


long  keepAliveTime:描述了  “临时线程”  最大存活时间,超出这个时间,就会被销毁;


TimeUnit   unit:是数值的单位;


BlockingQueue<Runnable>:阻塞队列;


ThreadFactroy threadFactory:线程工厂,用来创建线程;


RejectedExecutionHandler handler:线程池的拒绝策略;如果线程池满了,继续往里面添加任务,就是触发拒绝策略


8.2 模拟实现线程池

classMyThreadPool {
privateBlockingQueue<Runnable>queue=newLinkedBlockingQueue();
publicvoidsubmit(Runnablerunnable) throwsInterruptedException {
queue.put(runnable);
    }
publicMyThreadPool(intn) {
for (inti=0; i<n; i++) {
Threadt=newThread(() -> {
try {
while (true) {
Runnablerunnable=queue.take();
runnable.run();
                    }
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            });
t.start();
        }
    }
}
publicclassDemo15 {
publicstaticvoidmain(String[] args) throwsInterruptedException {
MyThreadPoolmyThreadPool=newMyThreadPool(10);
for (inti=0; i<1000; i++) {
intret=i;
myThreadPool.submit(newRunnable() {
@Overridepublicvoidrun() {
System.out.println("任务 "+ret);
                }
            });
        }
    }
}


/

目录
相关文章
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
4天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
4天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
3天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
32 9
|
6天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
12天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
9天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
12天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
26 3
|
11天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
下一篇
无影云桌面