JUC编程

本文详细探讨了Java并发编程中的JUC库,涉及线程与进程的区别、线程状态、锁的使用(包括Synchronized和Lock)、生产者消费者问题的改进版本、8锁现象、集合类的并发控制、线程池与四大函数式接口等内容,帮助理解并发编程和优化技巧。
摘要由CSDN通过智能技术生成

JUC编程



1.简介

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,
用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中
的 Collection 实现等;

2.线程和进程

线程进程的区别
  • 进程:一个程序,QQ.exe Music.exe 程序的集合;
    一个进程往往可以包含多个线程,至少包含一个!
    Java默认有几个线程? 2 个 mian、GC
  • 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
    对于Java而言:Thread、Runnable、Callable

java不能开启线程,调用C++ 开启线程,看start()方法源码

public synchronized void start() {
    
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

private native void start0();
并发、并行

并发(多线程操作同一个资源)

  • CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替

并行(多个人一起行走)

  • CPU 多核 ,多个线程可以同时执行; 线程池

ps:并发编程的本质:充分利用CPU的资源

线程有几个状态
     /** @since   1.5
     * @see #getState
     */
    public enum State {
        //新生
        NEW,
        //运行
        RUNNABLE,
        //阻塞
        BLOCKED,
        //等待
        WAITING,
        //超时等待(超时抛异常)
        TIMED_WAITING,
        //终止
        TERMINATED;
    }
wait/sleep 区别
  • 1.来自不同的类

wait=>Object
sleep=>Thread

  • 2.关于锁的释放

wait会释放锁资源等待,sleep抱着所资源等待

  • 3.使用的范围不同

wait必须在同一代码块
sleep任意位置

  • 4.是否需要捕获异常

wait 不需要捕获异常
sleep 必须要捕获异常

3.Lock锁

传统的Synchronized
/**
 * @Description
 * @Author zhoumm
 * @Version v1.0.0
 * Since 1.0
 * @Date 2021/2/8 0008
 */
public class SaleTickDemo1 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}

class Ticket {
    private int number = 30;

    //synchronized 给调用对象加锁,保证共享变量在多个线程访问的时候多个线程是互斥的。防止了数据一致性的问题
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,剩余:" + number);
        }
    }
}
Lock 接口

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。

随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用synchronized方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:

Lock l = …; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } 当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。

所有已知实现类:
ReentrantLock(可重入锁) , ReentrantReadWriteLock.ReadLock (读锁), ReentrantReadWriteLock.WriteLock(写锁)

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//非公平锁
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
//公平锁
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

ps:公平锁:十分公平:可以先来后到; 非公平锁:十分不公平:可以插队 (默认)

/**
 * @Description Lock
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2021-02-09
 */
public class SaleTickDemo2_lock {
	public static void main(String[] args) {
		// 并发:多线程操作同一个资源类, 把资源类丢入线程
		Ticket2 ticket2 = new Ticket2();

		new Thread(() ->{
			for (int i = 0; i < 40; i++) {
				ticket2.sale();
			}
		},"A").start();

		new Thread(() ->{
			for (int i = 0; i < 40; i++) {
				ticket2.sale();
			}
		},"B").start();

		new Thread(() ->{
			for (int i = 0; i < 40; i++) {
				ticket2.sale();
			}
		},"C").start();
	}
}

/**
 * Lock三部曲
 * 1.new ReentrantLock();
 * 2.lock.lock();  //加锁
 * 3.finally=>  lock.unlock();  //解锁
 */
class Ticket2{
	//属性、方法
	private int number = 30;

	Lock lock = new ReentrantLock();

	public void sale(){
		//加锁
		lock.lock();
		try {
			if(number > 0){
				System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,剩余:" + number);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//解锁
			lock.unlock();
		}
	}
}
Synchronized 和Lock 区别
  • 1、Synchronized 内置的Java关键字, Lock 是一个Java类
  • 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • 3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  • 4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
  • 5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以中断锁,非公平(可以自己设置);
  • 6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

4.生产者和消费者问题

Synchronized生产者和消费者
/**
* @Description
* 线程之间的通信问题:生产者和消费者问题!
* 等待唤醒,通知唤醒 * 线程交替执行 A B 操作同一个变量 num = 0 *
* A num+1 * B num-1
* @Author zhoumm
* @Version V1.0.0
* @Since 1.0
* @Date 2021-02-09
*/
public class ProducerAndComsumer {
   public static void main(String[] args) {
   	Data data = new Data();

   	new Thread(() -> {
   		for (int i = 0; i < 10; i++) {
   			try {
   				data.producer();
   			} catch (Exception e) {
   				e.printStackTrace();
   			}
   		}
   	}, "A").start();

   	new Thread(() -> {
   		for (int i = 0; i < 10; i++) {
   			try {
   				data.consumer();
   			} catch (Exception e) {
   				e.printStackTrace();
   			}
   		}
   	}, "B").start();
   }
}

// 判断等待,业务,通知
class Data {
   private int number = 0;

   public synchronized void producer() throws InterruptedException {
   	//判断等待
   	if (number != 0) {
   		//等待
   		this.wait();
   	}
   	//业务
   	number++;
   	System.out.println(Thread.currentThread().getName() + "=>" + number);
   	//通知
   	this.notifyAll();
   }

   public synchronized void consumer() throws InterruptedException {
   	if (number == 0) {
   		//等待
   		this.wait();
   	}
   	number--;
   	System.out.println(Thread.currentThread().getName() + "=>" + number);
   	this.notifyAll();
   }
}

ps:由于采用if(number == 0)判断,只有一个生产者,一个消费者,没什么问题,程序启动会生产一个消费一个,有序进行。
如果有多个生产者、消费者呢?

/**
 * @Description
 * 线程之间的通信问题:生产者和消费者问题!
 * 等待唤醒,通知唤醒 * 线程交替执行 A B C D 操作同一个变量 num = 0 *
 * A num+1      C num+1
 * B num-1      D num-1
 * 
 * 会出现【虚假唤醒】的现象
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2021-02-09
 */
public class ProducerAndComsumer {
	public static void main(String[] args) {
		Data data = new Data();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.producer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "A").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.producer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "C").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.consumer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "B").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.consumer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "D").start();

	}
}

// 判断等待,业务,通知
class Data {
	private int number = 0;

	public synchronized void producer() throws InterruptedException {
		//判断等待
		if (number != 0) {
			//等待
			this.wait();
		}
		//业务
		number++;
		System.out.println(Thread.currentThread().getName() + "=>" + number);
		//通知
		this.notifyAll();
	}

	public synchronized void consumer() throws InterruptedException {
		if (number == 0) {
			//等待
			this.wait();
		}
		number--;
		System.out.println(Thread.currentThread().getName() + "=>" + number);
		this.notifyAll();
	}
}
A=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
B=>0
C=>1
A=>2
C=>3
A=>4
D=>3
D=>2
D=>1
D=>0
C=>1
D=>0
A=>1
D=>0
C=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0

ps: 当有多个生产者消费者时并没有,有序生成消费,number出现大于1的值。什么原因呢?

查看api Object.wait()方法

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样: 

  synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     } (有关此主题的更多信息,请参阅Doug Lea的“Java并行编程(第二版)”(Addison-Wesley,2000)中的第3.2.3节或Joshua Bloch的“有效Java编程语言指南”(Addison- Wesley,2001)。 
如果当前线程interrupted任何线程之前或在等待时,那么InterruptedException被抛出。 如上所述,在该对象的锁定状态已恢复之前,不会抛出此异常。 

ps:将if (number == 0) 判断 改为while

JUC版的生产者和消费者问题

Synchronized wait notify
Lock (Condition)await signal

通过Lock.newCondition() 找到 Condition
一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

例如,假设我们有一个有限的缓冲区,它支持put和take方法。 如果在一个空的缓冲区尝试一个take ,则线程将阻塞直到一个项目可用; 如果put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。 我们希望在单独的等待集中等待put线程和take线程,以便我们可以在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。 这可以使用两个Condition实例来实现。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock(); try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally { lock.unlock(); }
   }

   public Object take() throws InterruptedException {
     lock.lock(); try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally { lock.unlock(); }
   }
 } 

下面通过Lock + Condition 来实现Synchronized 可达到的生产消费模式

/**
 * @Description  Lock + COndition 
 * 线程之间的通信问题:生产者和消费者问题!
 * 等待唤醒,通知唤醒 * 线程交替执行 A B 操作同一个变量 num = 0 *
 * A num+1 * B num-1
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2021-02-09
 */
public class ProducerAndComsumerByCondition {
	public static void main(String[] args) {
		Data2 data = new Data2();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.producer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "A").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.producer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "C").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.consumer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "B").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				try {
					data.consumer();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "D").start();

	}
}

// 判断等待,业务,通知
class Data2 {
	private int number = 0;

	Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();


	public  void producer(){
		lock.lock();
		try {
			//判断等待
			while(number != 0){
				//等待
				condition.await();
			}
			//业务
			number++;
			System.out.println(Thread.currentThread().getName() + "=>" + number);
			//通知
			condition.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public  void consumer() {
		lock.lock();
		try {
			while(number == 0){
				//等待
				condition.await();
			}
			number--;
			System.out.println(Thread.currentThread().getName() + "=>" + number);
			condition.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

ps:后出现的Conditon当然是要弥补Synchroniaed 的不足。

Condition 精准的通知和唤醒线程

A 执行完调用B,B执行完调用C,C执行完调用A

/**
 * @Description 通知 审批按指定流程
 * 员工 请假通知领导B审批,领导B审批通知领导C审批,领导C审批可以休假了,通知下一个员工可以来请假了
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2021-02-09
 */
public class LeaveApprovalProcess {
	public static void main(String[] args) {
		LeaveRecord leaveRecord = new LeaveRecord();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				leaveRecord.approveA();
			}
		},"员工").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				leaveRecord.approveB();
			}
		},"领导B").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				leaveRecord.approveC();
			}
		},"领导C").start();
	}
}

class LeaveRecord{
	private Lock lock = new ReentrantLock();
	private Condition conditionA = lock.newCondition();
	private Condition conditionB = lock.newCondition();
	private Condition conditionC = lock.newCondition();
	private int number = 1;  //1A   2B   3C
	private int userNum = 1;

	public void approveA(){
		lock.lock();
		try {
			while(number != 1){
				conditionA.await();
			}
			//
			System.out.println (Thread.currentThread().getName()+userNum+"申请休假");
			userNum++;
			//唤醒指定的人
			number = 2;
			conditionB.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void approveB(){
		lock.lock();
		try {
			while(number != 2){
				conditionB.await();
			}
			//
			System.out.println (Thread.currentThread().getName()+"审批同意");
			//唤醒指定的人
			number = 3;
			conditionC.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void approveC(){
		lock.lock();
		try {
			while(number != 3){
				conditionC.await();
			}
			//
			System.out.println (Thread.currentThread().getName()+"审批同意,可以休假了");
			//唤醒指定的人
			number = 1;

			conditionA.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}
员工1申请休假
领导B审批同意
领导C审批同意,可以休假了
员工2申请休假
领导B审批同意
领导C审批同意,可以休假了
员工3申请休假
领导B审批同意
领导C审批同意,可以休假了
员工4申请休假
领导B审批同意
领导C审批同意,可以休假了
员工5申请休假
领导B审批同意
领导C审批同意,可以休假了
员工6申请休假
领导B审批同意
领导C审批同意,可以休假了
员工7申请休假
领导B审批同意
领导C审批同意,可以休假了
员工8申请休假
领导B审批同意
领导C审批同意,可以休假了
员工9申请休假
领导B审批同意
领导C审批同意,可以休假了
员工10申请休假
领导B审批同意
领导C审批同意,可以休假了

5.8锁现象

关键在于识别锁对象

  • 1
/**
 * @Description
 *
 * 1、一个对象,两个同步方法
 * 固定
 * 发短信
 * 打电话
 * // synchronized 锁的对象是方法的调用者!、
 * // 两个方法用的是同一个锁,谁先拿到谁执行!
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test1 {
	public static void main(String[] args) {
		Phone1 phone = new Phone1();
		//锁的存在
		new Thread(()->{
			phone.sendSms();
		},"A").start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(()->{
			phone.call();
		},"B").start();
	}
}
class Phone1 {
	// synchronized 锁的对象是方法的调用者!、
	// 两个方法用的是同一个锁,谁先拿到谁执行!
	public synchronized void sendSms(){System.out.println("发短信");}
	public synchronized void call(){System.out.println("打电话");}
}
  • 2
/**
 * @Description
 * 2、一个对象,两个同步方法,sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
 * 发短信
 * 打电话
 * // synchronized 锁的对象是方法的调用者!、
 * // 两个方法用的是同一个锁,谁先拿到谁执行!
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone2 {
    // synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个锁,谁先拿到谁执行!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
  • 3
/**
 * @Description
 * 3、 一个对象,一个普通同步方法,一个普通方法!先执行发短信还是Hello? 普通方法
 * hello
 * 发短信
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test3 {
    public static void main(String[] args) {
        Phone3 phone3 = new Phone3();
        //锁的存在
        new Thread(()->{
            phone3.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone3.hello();
        },"B").start();
    }
}
class Phone3 {
    // synchronized 锁的对象是方法的调用者!、
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}
  • 4
/**
 * @Description
 * 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
 * 打电话
 * 发短信
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1= new Phone4();
        Phone4 phone2= new Phone4();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone4 {
    // synchronized 锁的对象是方法的调用者!、
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
  • 5
/**
 * @Description
 * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
 * 发短信
 * 打电话
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test5 {
    public static void main(String[] args) {
        Phone5 phone = new Phone5();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone5 {
    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
  • 6
/**
 * @Description
 * 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
 * 发短信
 * 打电话
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test6 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone6 phone1 = new Phone6();
        Phone6 phone2 = new Phone6();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

class Phone6 {
    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class 模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
  • 7
/**
 * @Description
 * 7、 1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
 * 打电话
 * 发短信
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test7 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone7 phone1 = new Phone7();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.call();
        },"B").start();
    }
}
class Phone7 {
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通的同步方法 锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}
  • 8
/**
 * @Description
 * 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
 * 打电话
 * 发短信
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-07-03
 */
public class Test8 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone8 phone1 = new Phone8();
        Phone8 phone2 = new Phone8();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone8 {
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通的同步方法 锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}

6.集合类不安全

List
public class ListTest {
	public static void main(String[] args) {
		//List<String> list = new ArrayList<>();//效率高
//		List<String> list = Collections.synchronizedList(new ArrayList<>());
//		List<String> list = new Vector<>();//读少写多,强一致性场景
		List<String> list = new CopyOnWriteArrayList<>();//高并发场景,读多写少

		for (int i = 0; i < 100; i++) {
			new Thread(() -> {
				list.add(UUID.randomUUID().toString().substring(0,5));
				System.out.println (list);
			},String.valueOf(i)).start();
		}
	}
}

ps:抛异常:java.util.ConcurrentModificationException
并发修改异常!

解决方案:

  • 1.List list = new Vector<>();
  • 2.List list = Collections.synchronizedList(new ArrayList<> ());
  • 3.List list = new CopyOnWriteArrayList<>();

java不推荐使用vector

1.因为vector是线程安全的,简单粗暴的锁同步机制,性能较差.
2.Vector空间满了之后,扩容是一倍,而ArrayList仅仅是一半
3.Vector分配内存的时候需要连续的存储空间,如果数据太多,容易分配内存失败
4.只能在尾部进行插入和删除操作,效率低
5.相对来说更适用于写多读少的场合

CopyOnWriteArrayList优缺点

原理:

  • 1.CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
  • 2.CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
  • 3.CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

缺点:

  • 1.由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc。
    • 1、young gc :年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。
    • 2、年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC)
  • 2.不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

ps:所谓并发容器的优缺点,无非是取决于我们在面对特定并发场景时,选择看需求。

Set
public class ListTest {
	public static void main(String[] args) {
		//List<String> list = new ArrayList<>();//效率高
//		List<String> list = Collections.synchronizedList(new ArrayList<>());
//		List<String> list = new Vector<>();//读少写多,强一致性场景
		List<String> list = new CopyOnWriteArrayList<>();//高并发场景,读多写少

		for (int i = 0; i < 100; i++) {
			new Thread(() -> {
				list.add(UUID.randomUUID().toString().substring(0,5));
				System.out.println (list);
			},String.valueOf(i)).start();
		}
	}
}
HashSet底层原理
public HashSet() {
    map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

ps:
HashSet这个类实现了Set集合,实际为一个HashMap的实例.

Map 不安全
HashMap1.7

详细分析参考:com.mmz.tkp.controller.javabasic.Collection.map.HashMap7Test

HashMap1.8

详细分析参考:com.mmz.tkp.controller.javabasic.Collection.map.HashMap8Test

小结:
https://blog.csdn.net/xiaoxiaole0313/article/details/104586031/
(1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法
(2)扩容后数据存储位置的计算方式也不一样:
(3)JDK1.7的时候使用的是数组+ 单链表的数据结构。JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构
(4)在JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容

ConcurrentHashMap

1.7
详细分析参考:com.mmz.tkp.controller.javabasic.Collection.map.ConcurrentHashMap7Test
1.8
详细分析参考:
com.mmz.tkp.controller.javabasic.Collection.map.ConcurrentHashMap8
com.mmz.tkp.controller.javabasic.Collection.map.ConcurrentHashMap8Test

Callable

1、可以有返回值
2、可以抛出异常
3、方法不同,run()/ call()

// 方式三
class CallableTest implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
			sum += i;
		}
		return sum;
	}
}

// 实现Callable<> 有返回值
CallableTest callableTest = new CallableTest();
//适配类
FutureTask<Integer> futureTask = new FutureTask<>(callableTest);
new Thread(futureTask, "方式三").start();
// 返回值
try {
    //这个get 方法可能会产生阻塞!把他放到 最后
    Integer integer = futureTask.get();
    System.out.println("返回值(sum):" + integer);
} catch (Exception e) {
    e.printStackTrace();
}

8.常用辅助类

CountDownLatch
/**
 * 计数减法器
 */
public class CountDownLatchDemo { 
	public static void main(String[] args) throws InterruptedException {
		// 总数是6,必须要执行任务的时候,再使用! 
		CountDownLatch countDownLatch = new CountDownLatch(6);
		for (int i = 1; i <=6 ; i++) {
			new Thread(()->{ 
				System.out.println(Thread.currentThread().getName()+" Go out");
				countDownLatch.countDown();// 数量-1 
			},String.valueOf(i)).start(); 
		}countDownLatch.await(); 
		// 等待计数器归零,然后再向下执行 
		System.out.println("Close Door"); 
	} 
}
CyclicBarrier
/**
 * @Description 加法计数器
 * @Author zhoumm
 * @Version v1.0.0
 * Since 1.0
 * @Date 2021/2/15 0015
 */
public class CyclicBarrierDemo {
    public static void main(String[] args){
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 0; i <= 7 ; i++) {
            // lambda不能操作到 i,所以用temp转换
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"收 集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
Semaphore
/**
 * @Description 作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
 * @Author zhoumm
 * @Version v1.0.0
 * Since 1.0
 * @Date 2021/2/15 0015
 */
public class SemaphoreDemo {
    public static void main(String[] args){
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i <= 6 ; i++) {
            new Thread(() -> {
                try {
                    //获得,假设如果已经满了,等待,等待被释放为止!
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车 位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车 位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
                    semaphore.release(); // release() 释放
                }
            },String.valueOf(i)).start();
        }
    }
}

9.读写锁

/**
 * @Description
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读 可以共存!
 * 读-写 不能共存!
 * 写-写 不能共存!
 *
 * @Author zhoumm
 * @Version v1.0.0
 * Since 1.0
 * @Date 2021/2/15 0015
 */
public class ReadWriteLockDemo {
    public static void main(String[] args){
//        MyCache myCache = new MyCache();//不加锁时,写入或读取时会被别的线程抢占
        MyCacheLock myCache = new MyCacheLock();
        //写入
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp+"", temp+"");
            },String.valueOf(i)).start();
        }
        //读取
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

//加锁的缓存
class MyCacheLock{
    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();

    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
//自定义缓存,无锁,只开启内存可见
class MyCache {
    private volatile Map<String,Object> map = new HashMap<>();
    // 存,写
    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入OK");
    }
    // 取,读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
}

10.阻塞队列

Collection(List,Set,Queue…)
Queue(双端队列:Deque,阻塞队列:BlockingQueue,非阻塞队列:AbstractQueue, 同步队列:SynchronousQueue…)
BlockingQueue(ArrayBlockingQueue,LinkedBlockingDeque…)

阻塞队列

什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
学会使用队列
添加、移除
四组API

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add offer() put() offer(,)
移除 remove poll() take() poll()
检测队首元素 element peek - -

同步队列

SynchronousQueue
没有容量,
put进去一个元素,必须等待取出take来之后,才能再往里面放一个元素!

11.线程池

三大方法、7大参数、4种拒绝策略

线程池的好处:

1、降低资源的消耗
2、提高响应的速度
3、方便管理。
线程复用、可以控制最大并发数、管理线程

三大方法

ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线 程
ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一 个固定的线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩 的,遇强则强,遇弱则弱

7大参数

几大方法的本质还是调用ThreadPoolExecutor(…)方法
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般 不用动
RejectedExecutionHandler handle // 拒绝策略) {}

4种拒绝策略

new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异 常*
new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里! *
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常! *
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会 抛出异常!

小结和拓展
池的最大的大小如何去设置!
了解:IO密集型,CPU密集型:(调优)

12.四大函数式接口

lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:

只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 泛型、枚举、反射
// lambda表达式、链式编程、函数式接口、Stream流式计算
// 超级多FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用!
// foreach(消费者类的函数式接口)

在java.util.function包下
四大函数式接口
Consumer
Function
Predicate
Supplier

Function函数式接口

Function 函数型接口, 有一个输入参数,有一个输出
只要是 函数型接口 可以 用 lambda表达式简化

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
/**
 * Function 函数型接口, 有一个输入参数,有一个输出
 * 只要是 函数型接口 可以 用 lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {
        //
//        Function<String,String> function = new Function<String,String>() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };
        Function<String,String> function = str->{return str;};
        System.out.println(function.apply("asd"));
    }
}
Predicate断定型接口

断定型接口:有一个输入参数,返回值只能是 布尔值!

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
public class Demo02 {
    public static void main(String[] args) {
        // 判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>(){
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };

        Predicate<String> predicate = (str)->{return str.isEmpty(); };
        System.out.println(predicate.test(""));

    }
}
Consumer 消费型接口

只有输入,没有返回值

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
/**
 * Consumer 消费型接口: 只有输入,没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer<String> consumer = (str)->{System.out.println(str);};
        consumer.accept("sdadasd");

    }
}
Supplier 供给型接口

没有参数,只有返回值

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
/**
 * Supplier 供给型接口 没有参数,只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier<Integer>() {
//            @Override
//            public Integer get() {
//                System.out.println("get()");
//                return 1024;
//            }
//        };

        Supplier supplier = ()->{ return 1024; };
        System.out.println(supplier.get());
    }
}

13.Stream流式计算

集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作

中间操作又分为:

无状态操作,元素处理不受之前元素影响,比如map()、filter()、skip()、peek()等

有状态操作,需要拿到所有元素才能进行,比如sorted()、limit()、distinct()等
结束操作又分为:

短路操作,遇到符合条件的元素后就可以终止,比如anyMatch()、findFirst()、findAny()等

非短路操作,需要处理所有元素,比如forEach()、collect()、max()、count()等。

在处理一般的数据量下,使用循环方式处理集合和通过stream方式处理集合的性能相差不大,但在数据里更大逻辑更复杂的情况下stream要更优。

parallelStream并行流,利用多核处理器的优势,并行(同一时间发生)处理数据(这意味着所处理的数据,不应该和顺序相关,否则会因为并行得到错误的结果),能够显著的提高执行速度。
Java8函数式编程书中提到,影响并行流的因素包括:

1.数据大小,并行化处理会带来额外的开销,只有每个数据处理管道花费的时间足够多时才有意义

2.数据结构,分割数据源,即把原有集合分成若干个集合到管道中

3.装箱,处理基本类型要比处理装箱类型快,装箱类型可以使用mapToInt等方法

4.处理器核的数量

5.单元处理开销:处理单个元素的花费时间越长,并行操作带来的性能提升越明显

Stream处理过程简述:

首先将Collection转化为Stream,流上的每个结点都只会返回包含上一结点引用的中间结点,使结点们组成了一个双向链表结构,而不会立即进行任何数据处理。

每个中间操作都实现了Sink接口,实现了 makeSink() 方法用来返回自己的 Sink 实例,

只有当终止操作出现时,才开始将 Sink 实例化执行数据处理,

无状态操作的 Sink 接收到数据,会立即通知下游,有状态操作的 Sink 则会等要处理的数据处理完了才开始通知下游,

终止节点将数据收集起来后就完成了这次操作。

14.ForkJoin

什么是 ForkJoin

ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!
大数据:Map Reduce (把大任务拆分为小任务)

ForkJoin 特点:工作窃取

这个里面维护的都是双端队列

ForkJoinTask()

RecursiveAction extends ForkJoinTask递归任务没有返回值
public final Void getRawResult();

RecursiveTask extends ForkJoinTask 递归任务有返回值
public final V getRawResult();

异步回调 Future

设计的初衷: 对将来的某个事件的结果进行建模

public interface Future<V> {
    //方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false.
    boolean cancel(boolean mayInterruptIfRunning);
    //方法判断当前方法是否取消
    boolean isCancelled();
    //方法判断当前方法是否完成
    boolean isDone();
    //方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕
    V get() throws InterruptedException, ExecutionException;
    //最多等待timeout的时间就会返回结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
应用场景:

在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,
都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。

Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

Future的类图结构

Future接口定义了主要的5个接口方法,有RunnableFuture和SchedualFuture继承这个接口,
以及CompleteFuture和ForkJoinTask继承这个接口

https://img-blog.csdn.net/20180606202542500

RunnableFuture

    这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。这个接口都实现类是FutureTask,一个可取消的异步计算,这个类提供了Future的基本实现,后面我们的demo也是用这个类实现,它实现了启动和取消一个计算,查询这个计算是否已完成,恢复计算结果。计算的结果只能在计算已经完成的情况下恢复。如果计算没有完成,get方法会阻塞,一旦计算完成,这个计算将不能被重启和取消,除非调用runAndReset方法。

    FutureTask能用来包装一个Callable或Runnable对象,因为它实现了Runnable接口,而且它能被传递到Executor进行执行。为了提供单例类,这个类在创建自定义的工作类时提供了protected构造函数。

SchedualFuture

    这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果

CompleteFuture

    一个Future类是显示的完成,而且能被用作一个完成等级,通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时,只有一个能够成功。

ForkJoinTask

    基于任务的抽象类,可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体,但是相对于线程实体是轻量级的。大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来,以某些使用限制为代价。

JMM

你对 Volatile 的理解

Volatile 是 Java 虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排

什么是JMM

: Java内存模型,不存在的东西,概念!约定!

关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁

线程 工作内存 、主内存

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类
型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量
    才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便
    随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机
    遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变
    量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,
    以便后续的write使用
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内
    存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须
    write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量
    实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,
    必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

17.Volatile

1、保证可见性
2、不保证原子性(指令重排)

如果不加 lock 和 synchronized ,怎么样保证原子性
使用原子类,解决 原子性问题
java.util.concurrent.atomic

指令重排

什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的:1234 但是可能执行的时候回变成 2134 1324
可不可能是 4123!

volatile可以避免指令重排:

内存屏障。CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)

小结:
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

19、深入理解CAS

什么是 CAS

大厂你必须要深入研究底层!有所突破! 修内功,操作系统,计算机网络原理

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就
一直循环!
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题

CAS的 ABA 问题

A线程 int a = 0;
A挂起
B线程 int a = 1;
B线程 int a = 0;
A线程唤醒
A使用a 时,a = 0;

中间a被修改过,而A线程不知道

Unsafe 类

20、原子引用

解决ABA 问题,引入原子引用! 对应的思想:乐观锁!

21、各种锁的理解

1、公平锁、非公平锁

公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:非常不公平,可以插队 (默认都是非公平)

2、可重入锁

可重入锁(递归锁)
Synchronized
lock锁

3、自旋锁

spinlock

/**
 * 自旋锁
 */
public class SpinlockDemo {
    // int   0
    // Thread  null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> mylock");
        // 自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
        }
    }
    // 解锁
    // 加锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();
        // 底层使用的自旋锁CAS
        SpinlockDemo lock = new SpinlockDemo();
        new Thread(()-> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()-> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

4、死锁
怎么排除死锁:
1、使用 jps -l 定位进程号
2、使用 jstack 进程号 找到死锁问题

工作中! 排查问题:
1、日志 9
2、堆栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值