前言:函数式编程
函数式接口Function interface: 任何接口,如果只包含唯一 一个抽象方法,那么他就是一个函数式接口
如:Runnable接口 当中只有一个抽象方法,他就是函数式接口 如果有两个及以上的抽象方法,它就不是函数式接口
lambda 表达式:
//Lambda 表达式
class T{
public void run(){
new Thread(()->System.out.println()).start();
new Thread(new Runnable(){
@Override
public void run() {
System.out.println();
}
}).start(); ;
}
}
一.多线程基础:
1.1线程与进程
1.1.1进程:
进程是对应操作系统而言的,一个应用程序或者服务,操作系统主要的任务就是进程调度。目前的操作系统都支持多进程的任务调度。
1.1.2线程:
线程是进程的细分,一个进程可以包含多条线程,线程也叫轻量级进程。CPU调度的最小单位是线程
1.2 线程两种创建方式:
1.2.1 继承Thread类
1.定义类来继承Thread根据自己的要求
2.重写run()
3.创建并启动线程
1.2.2实现Runnable接口:
实现Runnable然后使用Thread去用start方法 实现的是静态代理<br /> 创建Runnable实现类( Task任务 ):<br />
//线程任务
class WashFeet implements Runnable{
@Override
public void run() {
while (true)
System.out.println("洗脚.....");
}
}
创建线程并启动:
public class SkyAndEarth {
public static void main(String[] args) {
Thread son = new Thread( new WashFeet() );
son.start();
while (true){
System.out.println("正在营业....");
}
}
<br />**还有一种实现Callable接口: 在第六章(了解)**
1.2.3 两种方式对比
方式一,线程对象和线程任务是耦合的,方式二,线程对象和线程任务是分离的<br /> 方法一,扩展性不好,类单继承的。 方式二,扩展性更好,接口是多实现。<br /> 方法一,调用线程方法直接 ,方式二,不可直接调用线程方法,需要先调用 Thread.currentThread(),<br /> 这个方法的作用是拿到与当前线程任务绑定的线程对象<br />
1.3 线程常见方法:
<br />Thread.currentThread() //拿到线程本身<br />.getState() 获得线程状态<br />start() 启动线程。<br />.isAlive(); 查看是否还活着<br /> Thread.sleep( long ms ) 睡眠 暂停执行,直到睡眠时间到。<br /> setName()/getName() 取名字/获得名字,如果没有设置名字,默认名字为Thread-(0-n)<br />Thread.yield()线程礼让. 让出cpu执行时间片,自己进入就绪状态,再次等待调度。<br />join() 同步,加入线程线程中<br /> setDeamon(boolean xx)/isDeamon()设置守护线程/判断守护线程<br />守护线程:c()垃圾回收 虚拟机必须确保用户线程执行完毕 <br /> 虚拟机不用等待守护线程执行完毕 如 后台记录操作日志、监控内存、垃圾回收用户线程:执行完就结束了<br /> setPriority(int xx)/getPriority() 设置/获取优先级 ,<br /> 默认三个 MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY = 10;<br /> ava提供一个线程调度器(控制cpu) 用来监控程序启动后进入就绪状态的所有线程;<br /> 线程调度按照优先级决定调度哪个线程来执行<br /> 线程优先级1~10 线程优先级越高cpu执行时不一定会调它 只是他的权重会变大;<br />
1.4 理解同步与异步
同步:一个线程全部做完以后,另一个线程才开始执行,也就是一个线程的开始总是接着另一个线程的结尾。<br /> 异步:一个线程不需要等待,另一个线程执行完毕后,才执行,从宏观上看线程是同时执行,但是微观上是一个线程执行一点点后,另外一个线程又执行一点点,这样交替执行。线程默认是异步的,同步需要自己控制<br /> 多线程好处: 可以充分利用CPU,尤其是在处理阻塞问题的时候,使用多线程可以提高效率<br />
二.线程同步:
2.1 为什么要线程同步
就是为了数据一致,数据的安全性。<br />
2.2.1 同步代码块
class Window implements Runnable{
//模拟100张票
static int total = 100;
@Override
public void run() {
while (true){
synchronized( "" ){ // 小括号()里需要一个对象,这个对对象必须满足,不同线程进入时是同一个对象。这样才有互斥性。
if(total>0) {
total--;
//没时间了
System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);
}else{
break;
}
}
}
}
}
2.2.2 同步方法
和同步代码块原理基本一致也是使用synchronized 只是把同步范围扩大至整个方法,也就是锁的粒度更粗。<br />
//卖票软件
class SoftWare{
private static int total = 100 ;
public static synchronized void sales(){
fwefw
wfewfwef
wfewf
if( total>0 ){
total--;
System.out.println( Thread.currentThread().getName() +"销售1张,还剩余"+total);
}
}
public static int size(){
return total;
}
}
2.2.3 同步不安全的集合
static <T> List<T> synchronizedList(List<T> list) <br /> 返回指定列表支持的同步(线程安全的)列表。
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)<br /> 返回由指定映射支持的同步(线程安全的)映射。
static <T> Set<T> synchronizedSet(Set<T> s)<br /> 返回指定 set 支持的同步(线程安全的)set。<br /> <br /> 以 synchronizedList(List<T> list) 为例, 实现原理就是使用 同步代码块,这方法返回值实际上是 SynchronizedList 的实例,<br /> 这个实例套娃使用了 出入参数不安全的集合 ,在不安全外围包裹了一个 同步代码块<br />
2.2.4 死锁问题[高频面试]
<br />**死锁**:多个线程各自占着共享资源 并且互相等待其他线程占有的资源才可以运行,导致两者或者多个线程都在等待对象释放资源停止的这种情形 <br />**死锁产生的必要条件:**<br /> **1.互斥条件:一个资源只能被一个线程所使用**<br />** 2.请求与保持条件:一个线程因请求资源而阻塞时,以获取的资源保持不放**<br />** 3.不剥夺条件:线程获取资源,在未使用完成时,不能强行剥夺**<br />** 4.循环等待:若干线程之间形成一种头尾衔接的循环等待资源的关系**<br />** 上面四个死锁出现的必要条件,我们只要想办法破坏其中一种,就可以避免死锁**
class test{
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
Thread son1 = new Thread( new H(o1,o2) );
son1.start();
Thread son2 = new Thread( new Boo(o1,o2) );
son2.start();
}
}
//死锁
class H implements Runnable{
Object o1 = null;
Object o2 = null;
public H(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
synchronized (o1){
System.out.println( "Foo线程获得o1,想去锁o2");
//可选 主要是为其他线程争取时间
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("Foo线程获得o2");
}
System.out.println("Foo释放o2");
}
System.out.println("Foo 释放o1");
}
}
class Boo implements Runnable{
Object o1 = null;
Object o2 =null;
public Boo(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
System.out.println( "Boo线程获得o2,想去锁o1");
synchronized (o1){
System.out.println("Boo线程获得o1");
}
System.out.println("Boo释放o1");
}
System.out.println("Boo 释放o2");
}
}
三.线程生命周期[高频面试]
创建 就绪 -(阻塞)- 运行 结束
四..JUC锁[高频面试]
4.1 锁分类
锁的作用就是保证数据一致性,但是往往就会牺牲效率,所以使用锁需要自己做好平衡和取舍。<br /> 同等情况下,当然使用性能更好的锁,是最好的方案。<br />
4.1.1 悲观锁
怀疑任何情况都会出现并发问题,所以在设计的时候 就默认锁定,jdk1.5前就是 synchronized ,<br /> 早期说这个性能有问题(经过优化现在一般不去比较了)。<br /> 在JDK1.5新的解决方法是提供了Lock接口和他的实现类。例如 ReentrantLock ReentrantReadWriteLock等。<br />
4.1.2 乐观锁
乐观锁就是和悲观锁相反的思想,就是先不怀疑存在并发问题,如果确有存在再解决,<br /> 通常需要为并发修改的数据设定一个版本号,<br /> 修改前对一下先前获取的版本号,修改时在比较版本号如果一致才修改,<br /> 不一致说明有版本变化不可修改。CAS算法就是一种乐观锁算法,<br /> 它是硬件层面实现的,把多语句指令和为一个指令,确保了原子操作。<br /> Compare And Swap( 比较 和 交换 ) V (内存值) E(期望值) N(新值) 当且仅当 V==E时 才将 V=N 。
4.1.3Lock 锁
java1.5之后的更为强大的同步机制,通过显示定义同步锁对象来实现同步,同步锁使用Lock对象来<br /> Lock接口和他的实现类。例如 **ReentrantLock ReentrantReadWriteLoc**k等
Lock和对比 synchronized
lock 是显式锁 synchroized是隐式锁 lock只能锁代码块 使用lock可以提高效率<br />synchronized 关键字 ReentrantLock 类(jdk1.5新引入)<br /> 都是重入锁(ReentrantLock 功能强大 提供了更多的方法,以及实现公平锁策略)<br /> synchronized 上锁与解锁自动完成 , ReentrantLock 需要自己上锁和解锁。
class L implements Runnable{
public static void main(String[] args) {
Thread T1 = new Thread(new L());
T1.setName("黄牛");
Thread T2 = new Thread(new L());
T2.setName("商家");
Thread T3 = new Thread(new L());
T3.setName("学生");
T3.setPriority(10);
T1.start();
T2.start();
T3.start();
}
static int num =100;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock();
if (num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"抢到一张票,现在剩余"+num+"张票");
}else{
break;
}
}finally {
lock.unlock();
}
}
}
}
五.线程通信;
什么是线程通信:协调多个线程有序访问某些资源。<br /> 线程通信方法:这些方法必须使用在存在 synchronized 代码块内 或者 synchronized 方法中。<br /> wait():void , 它是Object的方法,不是线程提供的方法,让执行这个wait()调用的线程等待。无限期等待,直到有被唤醒。<br /> wait( long ms ) : 等待指定的毫米数,如果没有被唤醒,则自动唤醒<br /> wait( long ms, int naos) : 更精确的等待时间<br /> notify(): 唤醒等待在该对象上的线程(只会唤醒一个)。<br /> notifyAll(): 唤醒全部等待在该对象上的全部线程。<br /> 开发协作模式”生产者/消费者”===》
//线程通信方法
class WaitAndNotifyCase {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread son1 = new Thread( new Foo(obj) );
son1.setName("张三");
Thread son2 = new Thread( new Foo(obj) );
son2.setName("李四");
son1.start();
son2.start();
//让主线程等5s后去唤醒其他线程
Thread.sleep(5000);
synchronized (obj){
//obj.notify();//唤醒一个
obj.notifyAll();//唤醒全部
}
}
}
class Foo implements Runnable{
Object obj = null;
public Foo(Object obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj){
System.out.println(Thread.currentThread().getName()+ "执行开始...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "执行完毕...");
}
}
}
管程法
生产者:负责生产(可能是方法、对象、线程、进程)<br /> 消费者:负责消费(可能是方法、对象、线程、进程)<br /> 缓冲区:消费者不能直接使用生产者数据需要有一个缓冲区<br />开发协作模式"生产者/消费者”===》
//生产者消费者模型 管程法
//容器
class Container{
Object[] data = new Object[10];
int size;
public synchronized void add (Object obj){
while(size==data.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
data[size++]=obj;
System.out.println(Thread.currentThread().getName()+"生产了"+obj);
this.notifyAll();
}
public synchronized Object get(){
while (size==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object result = data[--size];
System.out.println(Thread.currentThread().getName()+"消费了"+result);
this.notifyAll();
return result;
}
}
//生产者
class Product implements Runnable{
Container container;
public Product(Container container){
this.container=container;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
container.add("鸭子"+i);
}
}
}
//消费者
class Consumer implements Runnable{
Container container;
public Consumer(Container container){
this.container=container;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
container.get();
}
}
}
class test{
public static void main(String[] args) {
Container container = new Container();
Thread xfz1 = new Thread( new Consumer(container) );
xfz1.start();
Thread scz1 = new Thread( new Product( container ) );
scz1.start();
}
}
信号灯法
*通过标志位区判断:如果标志位为真 等待 标志位为假通知另一个等待
//信号灯法
class demo{
public static void main(String[] args) {
DT d = new DT();
Gz g = new Gz(d);
Yy y = new Yy(d);
g.start();
y.start();
}
}
class Yy extends Thread{
DT dt;
public Yy(DT dt){
this.dt=dt;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.dt.play("快乐大本营");
}
}
}
class Gz extends Thread{
public Gz(DT dt){
this.dt=dt;
}
DT dt;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.dt.watch();
}
}
}
class DT{
String name;
boolean flag = true;
public synchronized void play(String name){
while(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+name);
this.name=name;
this.notifyAll();
this.flag=!flag;
}
public synchronized void watch(){
while(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:"+name);
this.notifyAll();
this.flag=!flag;
}
}
六.线程池
先讲实现Callable接口;call 是一个有返回值的方法 Callable<> 带泛型<br />
6.1 池:
是一种容器的概念,池化技术,都是容器技术。创建多个线程对象存入一个容器中,用就去池中获取,用完再放回去
因为线程也是宝贵的资源,频繁的创建销毁线程会存在内存开销。
Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法
6.2 如何创建线程池
Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法
corePoolSize:
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会停止
<br />Executors 线程池的工具类 ,可以创建出线程池实例 多个S就是 工具类 没有就是接口<br />ExecutorService: Executor 的子接口<br /> 方法:<br />void execute(Runnable command); 执行命令 无返回值 一般用于执行Runnable接口<br /><T>future<T> submit (Callable<T> task) ; 执行命令 有返回值 一般用于执行Callable接口<br />void shutdown(); 关闭连接 <br />提交任务到线程池,Runnable 类型的任务,Callable 类型的任务 <br />Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,<br /> 但是调用get()方法会造成调用线程阻塞,直到获得结果为止。<br /> <br />//1 单线程池<br /> ExecutorService es = Executors.newSingleThreadExecutor();
//2 固定线程池
ExecutorService es2 = Executors.newFixedThreadPool(10);
//3 可变数量的线程池
ExecutorService es3 = Executors.newCachedThreadPool();
//4 创建定时任务线程池
ScheduledExecutorService se4 = Executors.newScheduledThreadPool(10);
6.3.提交任务到线程池
6.3.1 Runnable 类型的任务
//关注运行没有结果的任务
class WashFeet implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"洗脚...");
try {
Thread.sleep(3000);//模拟一个延时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
方法名为 run() 没有返回值 不可抛出异常
6.3.2 Callable 类型的任务
class Numcount implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
sum+=i;
}
System.out.println(Thread.currentThread().getName() +"sum:"+sum);
return sum;
}
}
class demo2{
public static void main(String[] args) {
Numcount num = new Numcount();
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Integer> receive = es.submit(num);
Integer result = null;
try {
result = receive.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(result);
}
}
特点: 方法名为 call() 有返回值 可抛出异常
6.3.3 Future 异步结果
Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,但是调用get()方法会造成调用线程阻塞,直到获得结果为止。
Future<Integer> result = es2.submit( new NumberCount() );
Integer sum = result.get(); //阻塞调用语句的线程
System.out.println(sum);
6.4 关闭线程池
shutdown() 方法,调用后线程池可拒绝接收新的任务,带全部现有任务执行完毕后关闭线程池。<br />
6.5 统计多个任务运行的总时间
使用 shutdown()+isTerminated() 循环判断<br /> CountDownLatch 同步辅助工具类 , 构造一个任务总数,每个任务结束就减一,直到为0,把调用 await() 的线程唤醒。
七.并发集合 (JUC 集合)
CopyOnWriteArrayList : 底层使用 ReentrantLock实现锁,并且修改数据,拷贝一个新的数字操作。
CopyOnWriteArraySet : 底层套娃使用 CopyOnWriteArrayList ,添加前通过addIfAbsent(e) 判断元素是否已经存在,如果存在着不添加。
ConcurrentHashMap : 底层使用(1.8早期分段锁 后期使用CAS 无锁算法)
ConcurrentLinkedQueue : 并发安全的 队列,链表实现
ArrayBlockingQueue : 阻塞队列,基于数组实现 ,也是有有界队列
LinkedBlockingQueue: 阻塞队列,基于链表实现, 是无界队列
补充:
volatile 【面试题】
关键字 用于修饰变量 , 它作用是,可以将线程工作内存中的数据,立即同步到主存,以保证其他线程池立即可见。简单的说就是解决内存可见性问题。但是不能保证原子性问题。
JAVA (JMM)java内存模型:
JUC 中除有线程池 锁 同步工具, 还提供了一大堆 原子操作类。
AtomicInger AtomicLong……. 类它们的底层使用cas算法保证线程安全问题。
附图
JVM内存模型:
案例:
用sychorized 不交互打印
class Windows implements Runnable{
public static void main(String[] args){
Thread t1 =new Thread(new Windows("a"));
t1.start();
Thread t2 =new Thread(new Windows("daa"));
t2.start();
}
private String data;
public Windows(String data ){
this.data=data;
}
//不交互打印 data
public void run(){
synchronized (System.in) {
for (int i = 0; i < 100; i++) {
System.out.println(data);
}
}
}
}
作业:计算1-100000之间的素数有哪些
class Prime implements Runnable{
@Override
public void run() {
int a = 0;
for (int i = 2; i < 100000; i++) {
boolean b = true;
for (int j =2; j < i ; j++) {
if(i%j==0){
b = false;
break;
}
}
if(b){
a++;
}
}
System.out.println("2~100000之间的素数有"+a+"个");
}
}
计算1-100000之间的素数有哪些
class Prime2 implements Runnable{
@Override
public void run() {
int n = 0;//用来计数
for (int i =100000; i <= 200000; i++) {
if (isPrimeNumber(i))n++;
}
System.out.println("100000~200000之间的素数"+n+"个");
}
static Boolean isPrimeNumber(long num) {//判断是否为素数
//判断一个数是否为素数
if (num == 2) return true;//2特殊处理
if (num < 2 || num % 2 == 0) return false;//识别小于2的数和偶数
for (int i = 3; i <= Math.sqrt(num); i += 2) {
if (num % i == 0) {//识别被奇数整除
return false;
}
}
return true;
}
}