并发编程学习笔记

  • 线程

   1线程的定义和实现方式

    1.1资源分配的基本单位是进程,而调度的基本单位是线程。进程就是一段程序的执行(进程是程序的一部分),程序运行的时候会产生一个或多个进程。线程是进程的一部分,是应用程序中的执行流。

 

   2线程状态:

七个状态:

初始化状态---------就绪状态-------运行中状态(抢占到cpu资源)--------死亡状态

 

三种 阻塞状态:(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。

    (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

    (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

 

     

   3 线程之间的通信:

3.1.wait()和notify()方法都需要在同步代码块中执行,用当前锁对象执行

 

调用wait()方法会释放锁,notify()会随机叫醒一个处于等待状态的线程,调用notify会拿到锁

例子:

3.2.生产和消费者模型:

 

public class Tmall {

private int count;

public final int MAX_COUNT = 10;

public synchronized void push () {

while(count >= MAX_COUNT) {

try {

System.out.println(Thread.currentThread().getName() + " 库存数量达到上限,生产者停止生产。");

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count ++;

System.out.println(Thread.currentThread().getName() + " 生产者生产,当前库存为:" + count);

notifyAll();

}

public synchronized void take () {

while(count <= 0) {

try {

System.out.println(Thread.currentThread().getName() + " 库存数量为零,消费者等待。");

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count --;

System.out.println(Thread.currentThread().getName() + " 消费者消费,当前库存为:" + count);

notifyAll();

}

 

}

3.3.join方法:加塞线程

public class Demo {

 

public void a(Thread joinThread) {

 

System.out.println("方法a执行了...");

joinThread.start();

try {

joinThread.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("a方法执行完毕...");

 

}

 

public void b() {

System.out.println("加塞线程开始执行....");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("加塞线程执行完毕...");

}

 

public static void main(String[] args) {

Demo demo = new Demo();

Thread joinThread = new Thread(new Runnable() {

 

@Override

public void run() {

demo.b();

}

});

 

new Thread(new Runnable() {

 

@Override

public void run() {

demo.a(joinThread);

}

}).start();

}

 

}

 

   4 线程安全性问题:

4.1出现前提:

(1)在多线程的环境下

(2)必须有共享资源

(3)对共享资源进行非原子性操作

4.2活跃性:

死锁、饥饿、活锁,饥饿指的是某个现场永远无法获取到锁

4.3例子:

public class sequence{

private int value;

public int getNext(){

return value++;

}

public static void main(String [] args){

sequence s=new sequence();

new Thread(new Runnable(){

System.out.println(s.getNext());

}).start();

 

new Thread(new Runnable(){

System.out.println(s.getNext());

}).start();

 

new Thread(new Runnable(){

System.out.println(s.getNext());

}).start();

}

}

 

4.4单例模式的线程安全:

一.饿汉模式:

public class Singleton{

//私有化构造方法

private Singleton(){}

//饿汉模式,不会出现线程安全问题

private static Singleton instance = new Singleton();

public static Singleton getInstance(){

return instance;

 

}

 

}

 

二.懒汉模式:

public static Singleton getInstance(){

if(instance==null){

instance = new Singleton();

}

return instance;

}

三.线程安全的懒汉模式:

3.1 同步方法的方式:由于轻量级锁的自旋的特性,每次自旋都会消耗cpu资源,这个代码会成为单线程执行,性能很低

public static synchronized Singleton getInstance(){

if(instance==null){

instance = new Singleton();

}

return instance;

}

3.2双重锁模式

public static Singleton getInstance(){

if(instance==null){

synchronized(Singleton.class){

if(instance==null){

instance = new Singleton();//指令重排序的原因

}

}

}

return instance;

}

指令重排序:

1.申请一块内存空间 2.在这块空间里实例化对象 3.instance引用指向这块空间地址

但是执行顺序会改变,因此采取双重锁,并且给

给成员变量volatile修饰。private static volatile Singleton instance;

 

 

 

自旋锁:自旋是指某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。虽然CPU的时间被消耗了,但是比线程下文切换时间要少。这个时候使用自旋是划算的。

偏向锁:每次获取锁和释放锁会浪费资源、很多情况下,竞争锁不是由多个线程,而是由一个线程。

它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。

 

轻量级锁:

轻量级锁是由偏向升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

重量级锁:

当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。

重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。

 

 

   1synchronized:内置锁

synchronized 放在普通方法上,内置锁就是当前实例,保证方法体执行的原子性

synchronized修饰静态方法,内置锁是当前的clas字节码对象

synchronized同步代码块

   2 ReentrentLock:

与synchronized比较:可重入、非阻塞的获取锁、可公平、可中断、可超时获取锁()。代码灵活

synchronized:可重入、可中断、非公平。简单

2.1例子:

public class Demo {

private int signal;

Lock lock = new ReentrantLock();

Condition a = lock.newCondition();

Condition b = lock.newCondition();

Condition c = lock.newCondition();

public void a() {

lock.lock();

while(signal != 0 ) {

try {

a.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("a");

signal ++;

b.signal();

lock.unlock();

}

public void b() {

lock.lock();

while(signal != 1) {

try {

b.await();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.println("b");

signal ++;

c.signal();

lock.unlock();

}

public void c () {

lock.lock();

while(signal != 2) {

try {

c.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("c");

signal = 0;

a.signal();

lock.unlock();

}

public static void main(String[] args) {

Demo d = new Demo();

A a = new A(d);

B b = new B(d);

C c = new C(d);

new Thread(a).start();

new Thread(b).start();

new Thread(c).start();

}

}

 

class A implements Runnable {

private Demo demo;

public A(Demo demo) {

this.demo = demo;

}

 

@Override

public void run() {

while(true) {

demo.a();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

class B implements Runnable {

private Demo demo;

public B(Demo demo) {

this.demo = demo;

}

@Override

public void run() {

while(true) {

demo.b();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

class C implements Runnable {

private Demo demo;

public C(Demo demo) {

this.demo = demo;

}

@Override

public void run() {

while(true) {

demo.c();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

2.2实现一个有界队列:

public class MyQueue<E> {

 

private Object[] obj;

 

private int addIndex;

private int removeIndex;

private int queueSize;

 

private Lock lock = new ReentrantLock();

Condition addCondition = lock.newCondition();

Condition removeCondition = lock.newCondition();

 

public MyQueue(int count) {

obj = new Object[count];

}

 

public void add(E e) {

lock.lock();

while (queueSize == obj.length) {

try {

addCondition.await();

} catch (InterruptedException e1) {

e1.printStackTrace();

}

}

obj[addIndex] = e;

 

if (++addIndex == obj.length) {

addIndex = 0;

}

 

queueSize++;

removeCondition.signal();

lock.unlock();

}

 

public void remove() {

lock.lock();

 

while (queueSize == 0) {

try {

removeCondition.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

obj[removeIndex] = null;

 

if (++removeIndex == obj.length) {

removeIndex = 0;

}

 

queueSize--;

 

addCondition.signal();

 

lock.unlock();

}

}

 

 

  1. 使用synchronized实现一个重入锁:

public class Test implements Lock {

private boolean isLocked = false; //标志位,是否已经上锁

@Override

public synchronized void lock() {

while(isLocked){//自旋

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

}

}

isLocked = true;

}

@Override

public synchronized void unlock() {

isLocked = false;

notify();

 

}

}

//这个锁不可重入

改造

private boolean isLocked = false; //标志位,是否已经上锁

private Thread lockby;

int lockCount; //记录重入次数

@Override

public synchronized void lock() {

Thread currentLock = Thread.currentThread();

while(isLocked && currentLock != lockby){//自旋

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

}

}

isLocked = true;

lockby = currentLock;

lockCount++;

}

@Override

public synchronized void unlock() {

//判断当前线程

if(lockby==Thread.currentThread()){

lockCount--;

if(lockCount == 0){

isLocked = false;

notify();

}

}

}

 

 

  1. 读写锁:

   

ReentrantReadWriteLock

 

读写锁需要保存的状态:

写锁重入的次数

读锁的个数

每个读锁重入的次数,只有当读锁重入次数为0了,写锁才能进入

16位写锁数量低八位,读锁数量高八位

锁降级:写锁降级为读锁,在写锁没有释放的时候,获取到读锁,在释放写锁。一个操作既有读又有写

public void readWrite() {

r.lock(); // 为了保证isUpdate能够拿到最新的值

if (isUpdate) {

r.unlock();

w.lock();

map.put("xxx", "xxx");

r.lock();//需要在读锁释放之前再加一把读锁

w.unlock();

}

 

Object obj = map.get("xxx");

 

System.out.println(obj);

r.unlock();

 

}

  1. 死锁:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

private Object obj1 = new Object();

private Object obj2 = new Object();

public void a(){

synchronized (obj1){

synchronized (obj2){

System.out.print("a");

}

}

}

public void b(){

synchronized (obj2){

synchronized (obj1){

System.out.print("b");

}

}

}

  1. 重入锁源码分析

 

  • Jdk中的原子类操作

 

 

  • Aqs详解

 

 

  • Volatile和Threadlocal关键字

 

 

  • 并发工具包

1CountDownLatch:同步功能的辅助类,效果是给定一个计数,当使用这个latch类的线程判断计数不为0,则呈现wait状态:

例子:

 

 

2 cyclicBarrier

 

3 semaphore

 

4 exchanger

 

5 fork-join框架

6线程池原理(源码分析):线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟

* corePoolSize 线程池维护线程的最少数量

* maximumPoolSize 线程池维护线程的最大数量

* keepAliveTime 线程池维护线程所允许的空闲时间

* workQueue 任务队列,用来存放我们所定义的任务处理线程

三种提交策略:

1.直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue

2.有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

3.无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

拒绝策略:当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

1.CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)

2.AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException

这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行,丢弃一些也是可以接收的,记得做好记录)

3.DiscardPolicy:不能执行的任务将被删除

这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

4.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) 也就是丢弃最早的任务

七.并发容器

同步容器:vector和hashtable或者调用collections来对集合进行同步

1.CopyOnWriteArrayList解决ArrayList的并发问题

2.ConcurrentHashMap 解决hashmap的并发问题

3.ConcurrentLinkedQueue

4.阻塞队列BlockingQueue:

 

concurrentHashMap源码分析:

 

java重排序():编译器和处理器为了提高程序的运行性能,对指令进行重新排序

原则:as if serial数据依赖性:写后读、读后写、写后写。对于没有数据依赖性的代码cpu会进行指令重排序

分类:编译器重排序和处理器重排序

影响:单线程情况下,会提高性能,但是多线程情况下

Happen-Before

在java内存模型中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必然存在Happen-Before关系

 

《深入理解Java内存模型(二)——重排序》

《happens-before俗解》

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值