多线程总结

本文的篇幅比较长,总结了自己在学习中遇到的有关多线程的所有知识点😎

创建线程方法

有两种方法:
(1)继承Thread类
(2)实现Runnable接口
(3)Callable方式:结合Future,可以获取线程的执行结果

Thread基础API及线程状态

线程状态

NEW
RUNNABLE——》running(运行态)+ready(就绪态)
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

start()和run()

start()相当于启动一个新线程,在新线程里运行run()方法。调用start()后,线程不会立即执行,是new–》ready,等待cpu调度

run()只是使用类中的run()方法,不会创建新线程

sleep()和yield()

sleep()和yield()都是静态方法

方法状态
Thread.yield()运行态–》就绪态
Thread.sleep(long)运行态–》TIMED_WAITING

join()

线程等待:线程对象.join()/join(long)
当前线程由运行态–》WAITING,或TIMED_WAITING

实现线程等待的方式有两种:
(1)join

t.join(2000);//t线程执行完或等待2000ms后(满足其中一个条件即可)执行下面的代码
System.out.println(Thread.currentThread().getName());

(2)结合activeCount()+yield()使用

while(Thread.activeCount()>1){
	Thread.yield();
}

线程中断

方法说明
boolean isInterrupted()测试线程是否中断,不清楚标志位
static boolean interrupted()测试线程是否中断,清除标志位
void interrupt()中断线程

不是真的中断,只是告诉线程需要进行中断,具体是否中断由线程自己决定
1、线程启动后,中断标志位默认是false
2、在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来处理中断的处理逻辑,通过方法判断:(thread.isInterrupted()/Thread.interrupted())
3、线程因调用wait()/join()/sleep()处于阻塞状态时,将线程中断,会造成:
      (1)在这三个阻塞方法所在的代码行,直接抛出InterruptedException异常
      (2)抛出异常后,重置线程的中断标志位(=true)
4、使用自定义中断标志位无法满足在阻塞状态时的中断操作–》抛出异常

线程安全

原子性、可见性、有序性
原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行
特殊的原子性代码:
1、n++、n–、++n、–n都不是原子性的
      需要分解成3条指令:从内存读取变量到cpu、修改变量、写回内存
2、对象的new操作
      Object a=new Object();
      分解成3条指令:分配内存对象、初始化对象、将对象赋值给变量
可见性:多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
有序性:程序执行的顺序按照代码的先后顺序执行

synchronized关键字
synchronized关键字可以保证线程安全的三大特性
线程间同步互斥,竞争对象锁,竞争成功–》执行,竞争失败–》进入同步队列
使用方法:
1、静态方法上:对类对象加锁
2、实例方法:对this对象,哪个对象调用,对哪个对象加锁
3、代码块:synchronized(){}

进入synchronized代码行时,需要获取对象锁:
1、获取成功:往下执行
2、获取失败:阻塞在synchronized代码行

退出synchronized代码块,或synchronized方法:
1、退回对象锁
2、通知JVM及系统,其他线程可以竞争这把锁

synchronized加锁关键点:
1、对哪个对象加锁——一个对象只有一把锁
2、只有同一个对象,才有同步互斥作用(以此来保证线程安全的三大特征)
3、对于synchronized内的代码来说,在同一个时间点,只有一个线程在运行(没有并发、并行)
4、运行的线程越多,性能下降的越快(归还对象锁时,就有越多的线程在唤醒、阻塞状态切换)
5、同步代码执行的时间越短,性能下降的越快

volatile关键字
volatile关键字可以(通过建立内存屏障)保证可见性和有序性
作用:
1、禁止指令重排序
2、建立内存屏障
注意:
      1、不能保证原子性
      2、volatile修饰的变量,进行赋值不能依赖变量(常量赋值可以保证线程安全)

使用场景:
      1、volatile可以结合线程加锁的一些手段,提高线程效率
      2、只是变量的读取,常量的赋值,可以不加锁,而是使用volatile,可以提高效率
      3、使用在成员变量和静态变量中,不能使用在局部变量

线程间通信

锁对象.wait()/wait(long):释放当前对象持有的对象锁,进入阻塞态(WAITING或TIMED_WAITING),直到被notify()/notifyAll()唤醒或时间到

锁对象.notify()/notifyAll():通知调用wait()进入等待队列的线程,来竞争对象锁
notify():随机唤醒一个线程
notifyAll():唤醒全部线程

单例模式

1、volatile关键字修饰变量
2、私有构造方法
3、双重校验锁写法保证线程安全

public class Sington{
    //单例模式
	private Sington(){}
	//双重校验锁
	private static volatile SINGTON=null;
	public static Sington getInstance(){
	if(SINGTON==null){
	   synchronzied(Sington.class){
	   if(SIGTON==NULL){//提高效率:变量使用volatile保证可见性
	   	SINGTON=new Sington();
		}
	 }
    }
 }
}

阻塞队列

%%%%%%%后续补充%%%%%%%%

线程池

通过下面代码,来解释ThreadPoolExecutor的用法以及其中中各个参数的含义

import java.util.concurrent.*;

public class threadpool {
    public static void main(String[] args) {
        ExecutorService pool=new ThreadPoolExecutor(//线程池--快递公司
                3,//核心线程数:创建好线程池,正是员工就开始取快递

                //临时工雇佣:正式员工忙不过来就会创建临时工
                //临时工解雇:空闲时间超出设置的时间范围,就解雇
                5,//最大线程数(最多数量员工:正式员工+临时工)

                20,//时间数量
                TimeUnit.SECONDS,//时间单位(时间数量+时间单位 表示一定范围的时间)

                //阻塞队列:存放包裹的仓库(存放任务的数据结构)
                new ArrayBlockingQueue<>(1000 ),

//                new ThreadFactory() {
//                    @Override
//                    public Thread newThread(Runnable r) {
//                        return null;
//                    }
//                },//(了解)线程池创建Thread线程的工厂类。没有提供的话,就使用线程池内部默认的创建线程的方式


                //拒绝策略
                //CallerRunsPolicy:哪个线程(excute代码行所在的线程)让我(快递公司)送快递,让该线程自己去执行
                //AbortPolicy:直接抛出异常 RejectedExecutionException
                //DiscardPolicy:从阻塞队列丢弃最新的任务,把当前任务放入阻塞队列
                //DiscardOldestPolicy:从阻塞队列丢弃最旧的任务
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("送快递到西安,A");
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("送快递到杭州,B");
            }
        });
        System.out.println("正在做事情");
    }
}

乐观锁和悲观锁

乐观锁和悲观锁是在代码设计层面上来讲的
乐观锁:认为一般情况下不会产生并发冲突,在数据提交更新的时候才会检测是否发生并发冲突。

悲观锁:总是假设最坏的情况,每次拿数据的时候都会上锁。
               java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现

CAS

Compare And Swap:比较并交换

技术背景:线程执行任务,任务量比较小的时候,线程安全需要使用synchronized(多个线程同时竞争锁对象)加锁,效率比较低(竞争失败的线程很快在阻塞态和被唤醒状态间切换)

使用前提:代码块执行速度非常快

CAS实现:自旋尝试设置值的操作
缺点:
      (1)前提(很快执行)满足不了,线程就一直处于运行态循环执行CAS,性能消耗比较大
      (2)线程数量比较多,导致前提可能满足不了,或者CPU在很多线程间切换——性能消耗大

目的:在安全的前提下优化效率——使用较多的场景:对变量修改,保存线程安全

原理:使用CAS,不造成线程阻塞(一直处于运行状态)

存在问题:ABA问题
      产生原因:当前线程拷贝主存值到工作内存进行修改的时间段,其它线程把主存中的变量值A–》B–》A。相当于已经被其它线程修改过,但当前线程不知道,还在修改
      解决方案:采取乐观锁设计,引入版本号控制

jdk中采用CAS实现的API
      (1)java.util.concurrent.atomic,原子性的并发包下
      (2)synchronized中,多个线程不同时间点执行同步代码块时,jdk优化会采用CAS
      (3)其他的,如1.8版本中ConcurrentHashMap实现,put操作时,节点是空,采取CAS

在这里插入图片描述

synchronized原理

实现原理:通过对象头加锁操作 monitor锁机制:编译为字节码时,生成monitorenter、monitorexit

对象头锁状态:
(1)无锁
(2)偏向锁
(3)轻量级锁
(4)重量级锁

JVM对synchronized的优化方案:根据不同场景,使用不同的锁机制
(1)偏向锁:针对同一个线程再次申请已持有的对象锁。 实现原理–》CAS
(2)轻量级锁:大概率在同一个时间点,只有一个线程申请对象锁。实现原理–》CAS
(3)重量级锁:大概率在同一个时间,多个线程竞争同一个对象锁
                        实现原理:操作系统的mutex锁
                        缺点:涉及到操作系统的调度、用户态到内核态的切换,开销非常大,线程会阻塞、唤醒
synchronized锁只能升级不能降级

其他优方案:
(1)锁粗化:一个线程对同一个对象锁反复获取释放的操作,中间没有其他受影响的代码时,可以合并为一个锁
(2)锁消除:临界区代码中,没有对象逃逸出当前线程,说明本身是线程安全操作,可以直接删除锁(不使用锁)

死锁

前提条件
同步的本质在于:一个线程等待另一个线程执行完毕后才可以继续执行,但如果现在几个相关的线程彼此之间相互等待着,就会造成死锁。

至少有两个线程,互相持有对方申请的对象锁,造成互相等待,导致没法继续执行

后果:线程阻塞等待,无法向下执行
造成死锁的4个必要条件
1、互斥使用:资源被一个线程占有后,别的线程不能使用
2、不可抢占:资源请求者不能强制从资源占有者手中夺取资源
3、请求和保持:资源请求者在请求资源的同时,会保持对原有资源的占用
4、循环等待:发生死锁时,所等待的线程必定会形成一个死循环,造成永久阻塞
解决方案
(1)资源一次性分配(破坏请求与保持条件)
(2)可剥夺资源:在线程满足条件时,释放掉已占有的资源
(3)资源有序分配:系统为每类资源赋予一个编号,每个线程按照编号请求资源,释放则相反

检测死锁的手段
使用jdk的监控工具,如jconsole、jstack查看线程状态

Lock体系

Lock lock=new ReentrantLock();
lock.lock();//设置当前线程的同步状态,并在队列中保存线程及线程的同步状态
try{
……
}finally{
lock.unlock();//线程出队列
}

Lock锁的实现原理

Lock锁的实现原理:AQS(AbstractQueuedSynchronizer)
AQS:队列式同步器
实现原理:双端队列保存线程及线程同步状态。并通过CAS提供设置同步状态的方法

Lock锁特点

1、提供公平锁和非公平锁 是否按照入队的顺序设置线程的同步状态——多个线程申请加锁操作时,是否按照时间顺序来加锁
2、AQS提供的独占式、共享式设置同步状态(独占锁、共享锁
      独占锁:只允许一个线程获取到锁
      共享锁:一定数量的线程共享式获取锁
3、带Reentrant关键字的lock包下的API:可重入锁
      允许多次获取同一个Lock对象锁(和synchronized中的偏向锁对应

ReentrantReadWriteLock

ReentrantReadWriteLock:提供的读写锁API
使用场景:多线程执行某个操所时,允许读-读并发/并行执行,不允许读写、写-写并发/并行执行
            读读并发,读写、写写互斥
读锁和写锁之间只能降级,不能升级(只能 写锁–》读锁)和synchronized锁相反,synchronized锁只能升级

优势:针对读读并发执行,提高运行效率

Condition

作用:线程间通信
使用方法:
1、通过Lock对象.newCondition() 获取Condition对象
2、调用Condition对象.await()让当前线程阻塞等待,并释放锁(等同于:synchronized锁对象.wait()
3、调用Condition对象.signal()/signalAll()通知之前await()阻塞的线程(=等同于:synchronized锁对象.notify()/notifyAll()

ThreadLocal

使用场景:隔离线程间的变量,保证每个线程是使用自己线程内的副本,
代码推荐写法:

//定义类变量:
//ThreadLocal多个线程使用的都是同一个,但里面的值是和线程绑定的,线程间互不相干
static ThreadLocal<保存的数据类型> HOLDER=new ThreadLocal<>();
new Thread(()-{
try{
	HOLDER.set(设置的值);
}
finally{
	HOLDER.remove();//当有线程设置值的时候,在线程结束前,remove
}
}) 

原理:Thread对象都有自己的ThreadLocalMap,调用ThreadLocal对象设置值set(value),获取值操作get(),删除值remove(),都是对当前线程中ThreadLocalMap对象的操作。所以线程间的变量是隔离开的。

ConcurrentHashMap

HashMap:非线程安全的,jdk1.7基于数组+链表,jdk1.8基于数组+链表+红黑树

Hashtable的实现:线程安全的,1.7和1.8都是数组+链表,全部方法都是基于synchronized加锁,效率非常低

ConcurrentHashMap:线程安全的,并且支持很多场景下并发操作,提高了效率
jdk1.7:
        基于数组+链表,本质上基于 Segment分段锁技术,Segment继承了ReentrantLock,不同的Segment之间多线程可以并发操作。同一个Segment是使用Lock加锁

jdk1.8:
        基于数组+链表+红黑树,本质上是使用CAS+synchronized加锁实现线程安全,不同节点多线程可以并发操作,如put操作,同一个节点,如果为空使用CAS,如果不为空,使用synchronized加锁

1.7和1.8效果:读写分离可以提高效率:多线程对不同的Node/Segment的插入/删除是可以并发,并行执行,对同一个Node/Segment的写操作是互斥的,读操作都是无锁操作,可以并发,并行执行(读读、读写并发,写写互斥)
                和ReentrantReadWriteLock读写锁不同:读读并发,读写、写写互斥

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值