java 并发与多线程

并发三要素

1、原子性

一个线程在进行一个或者多个操作的时候,不会被其他线程干扰,要么全部执行要么全部不执行。

原子性实现方式:

b)原子性操作 比如"a = 1;" 、 "return a;"

a)synchronize、lock、分布式锁

2、可见性

可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。

共享变量保存在主内存中,多线程操作共享变量时候,每个线程都有自己独立的工作内存,里面保存该线程使用到的共享变量副本。

每个线程只能操作自己工作内存中的共享变量副本,操作完后 a、更新到主内存 b、更新其他线程变量副本 这样就保证了共享变量的可见性。

可见性实现方式:

a)synchronize、lock、分布式锁

保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。

b)volatile

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

但是他并不能保证变量的原子性。所以只能在有限的一些情形下使用 volatile 变量替代锁。

  • 对变量的写操作不依赖于当前值。比如 num++ 依赖当前值 num=8 不依赖当前值
  • 该变量没有包含在具有其他变量的不变式中。比如volatile修改的俩个变量存在 lower < upper 关系 

应用举例:

  • 状态标识  volatile boolean flag = true;
  • 双重检查锁+volatile 实现单例 没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的_instance的情况(另一个线程 走到这if(_instance==null) 读取_instance时候可能被初始化了一半),但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。对于volatile变量_instance,所有的写(write)都将先行发生于读(read)所以线程会将_instance实例化完成后才会让另一个线程读取
  • 结果标识  volatile String lastUser 
  • volatile + CAS结合  原子类 比如:AtomicInteger  

3、有序性

程序的执行顺序按照代码的执行顺序来执行

多线程

1、价值

发挥多核CPU优势、防止阻塞(单线程处理所有请求就容易堵塞、此时就涉及到线程模型啦BIO/NIO)

2、实现方式

1)继承Thread类创建线程类

2)通过Runnable接口创建线程类

3)通过Callable和Future创建线程

3、Callable和Runnable区别

  •  Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
  •  Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  •  Call方法可以抛出异常,run方法不可以。
  •  运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

4、线程状态

新建(new Thread())、就绪(start())、运行、堵塞、死亡

5、线程池

  java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。

优点:重用线程减小创建销毁开销、可控制线程最大并发数、能定时或者周期执行任务

(1)newCachedThreadPool创建一个可缓存线程池

(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。

(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

(4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。

6、同步集合&并发集合

同步集合如Vector、HashTable会对整个对象加锁,而并发集合例如ConcurrentHashMap(默认并发度=16),
把整个Map 划分成几个片段(默认16),只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段

7、AQS ReentrantLock

8、线程B怎么知道线程A修改了共享变量

volatile、synchronize(修饰修改变量方法)、wait/notify、while

9、sleep wait 方法区别

都是空出CPU一段时间,wait是对象方法,sleep是线程方法,如果线程持有对象锁 wait会放弃对象锁 sleep不会

exp:https://www.cnblogs.com/hongten/p/hongten_java_sleep_wait.html

10、并发编程Executor框架

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue);

corePoolSize:核心线程数,如果运行的线程少于corePoolSize则创建新线程来执行新任务,即使线程池中的其他线程是空闲的

maximumPoolSize:最大线程数,可允许创建的线程数,corePoolSize和maximumPoolSize设置的边界自动调整池大小:

corePoolSize <运行的线程数< maximumPoolSize:仅当队列满时才创建新线程

corePoolSize=运行的线程数= maximumPoolSize:创建固定大小的线程池(就是FixedThreadPool)

keepAliveTime:如果线程数多于corePoolSize,则这些多余的线程的空闲时间超过keepAliveTime时将被终止

unit:keepAliveTime参数的时间单位

workQueue:保存任务的阻塞队列,与线程池的大小有关

11、线程池队列

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

 并不是先加入任务就一定会先执行。假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务 时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13 

 排队三种策略:直接提交(SynchronousQueue)、无界队列(LinkedBlockingQueue)、有界队列(ArrayBlockingQueue)

任务拒绝情况:Executor 关闭、达到maximumPoolSize活队列容量最大值

 任务拒绝处理策略:   引用:https://www.cnblogs.com/diegodu/p/7381735.html

12、ExecutorCompletionService

管理线程池处理任务的返回结果

13、单例模式线程安全性

线程安全:饿汉式、双锁

线程不安全:懒汉式

14、synchronize、lock区别

lock:1、等待线程等待一定的时间或者能够响应中断(trylock、lockInterruptibly)2、手动释放 3、可以提高多个线程进行读操作的效率(ReentrantReadWriteLock)

synchronize:1、等待线程无限等待 2、无需手动释放(包括异常情况)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值