多线程
多线程
以下部分某些内容来自: 如何正确地停止一个线程?
0、简介
问:什么是进程?什么是线程?
答:进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。
注:一个程序至少有一个进程,一个进程至少有一个线程.
1、特性
-
原子性:是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值为1,线程B给他赋值为-1。那么不管这两个线程以何种方式。何种步调工作,i的值要么是1,要么是-1.线程A和线程B之间是没有干扰的。这就是原子性的一个特点,不可被中断。
-
可见性:是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行来说,可见性问题是不存在的。
-
有序性:在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
2、实现线程的方式
2、1通过继承Thread类的方法创建
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread:=>进来了");
super.run();
}
}
class Main{
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
2、2 通过实现Runable接口的方法创建
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread:=>进来了");
}
}
class Main02{
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
优点:
- Runnable方式可以避免Thread方式由于java单继承特性带来的缺陷
- Runnable的代码可以被多个线程(Thread实例)共享,从而使用多个线程处理同一资源的情况
缺点:出现线程进行一半,而被别的线程抢占cpu的调度,可加入lock锁,或者synchronize关键字来同步方法
2、3 通过Callable和FutureTask创建线程
- 创建Callable接口的实现类 ,并实现Call方法
- 创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动线程
- 调用FutureTask对象的get()来获取子线程执行结束的返回值
public class MyThread03 implements Callable {
int i=0;
@Override
public Object call() throws Exception {
for (i = 0; i <50; i++) {
System.out.println("MyThread03:=>i:"+i);
}
return i;
}
}
class Main03{
public static void main(String[] args) throws Exception {
System.out.println("主线程进入了");
MyThread03 myThread03 = new MyThread03();
FutureTask<Integer> ft = new FutureTask<>(myThread03);
for (int i = 0; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + ":=>i:" + i);
if (i == 30) {
//FutureTask对象作为Thread对象的target创建新的线程
Thread thread = new Thread(ft);
//线程进入到就绪状态
thread.start();
}
}
try {
//取得新创建的新线程中的call()方法返回的结果
Object num = ft.get();
System.out.println("num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程退出了");
}
}
Callable与Runnable的区别
- Callable规定的方法是call(),而Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
- call()方法可抛出异常,而run()方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等
- 待计算的完成,并检索计算的结果.通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果
2、4 通过线程池创建线程
class MyThread04 implements Runnable {
@Override
public void run(){
System.out.println("MyThread04:=>" + Thread.currentThread().getName() + " ");
}
}
class Main04{
//线程池数量
private static int POOL_NUM = 10;
public static void main(String[] args) throws InterruptedException {
// 创造一个规定线程最大数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i = 0; i<POOL_NUM; i++)
{
MyThread04 thread = new MyThread04();
Thread.sleep(1000);
//线程池启动线程
executorService.execute(thread);
}
//关闭线程池
executorService.shutdown();
}
}
Executors类:提供了一系列工厂方法用于创建线程池
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建 一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
2、5 辅助类:CountDownLatch
/**在必须执行一定任务的时候,再使用
*
* 够使一个或多个线程等待其他线程完成各自的工作后再执行,CountDownLatch是JDK 5+里面闭锁的一个实现。
*
* 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,
* 在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,
* 那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,
* 它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
与CountDownLatch第一次交互是主线程等待其它的线程,主线程必须在启动其它线程后立即调用await方法,
这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他的N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务,
这种机制就是通过countDown()方法来完成的。每调用一次这个方法,在构造函数中初始化的count值就减1,
所以当N个线程都调用了这个方法count的值等于0,然后主线程就能通过await方法,恢复自己的任务。
*/
@SuppressWarnings("all")
public class CountDownLatchDemo {
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch=new CountDownLatch(2);//两个工人的协作
Worker worker1=new Worker("zhang san", 5000, latch);
Worker worker2=new Worker("li si", 8000, latch);
worker1.start();//
worker2.start();//
latch.await();//等待所有工人完成工作
System.out.println("all work done at "+sdf.format(new Date()));
}
static class Worker extends Thread{
String workerName;
int workTime;
CountDownLatch latch;
public Worker(String workerName ,int workTime ,CountDownLatch latch){
this.workerName=workerName;
this.workTime=workTime;
this.latch=latch;
}
@Override
public void run(){
System.out.println("Worker "+workerName+" do work begin at "+sdf.format(new Date()));
doWork();//工作了
System.out.println("Worker "+workerName+" do work complete at "+sdf.format(new Date()));
latch.countDown();//工人完成工作,计数器减一
}
private void doWork(){
try {
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了 。(运行主线程开启子线程的时候,子线程还没有结束的时候,主线程可以一直等待,直到初始化的现成的计数器count为0,主线程就可以不用等待继续执行了)
2、6 辅助类: CyclicBarrier
/**
* CyclicBarrier可以使一定数量的线程在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,
* 这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,
* 那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
*
* CyclicBarrier支持在线程执行前首先执行一个Runnable Command。
如果某个线程出了问题(如InterruptedException),那么将会影响其他线程。
CyclicBarrier使用ReentranLock进行加锁,使用Condition的await、signal及signalAll方法进行线程间通信。
*
* 默认等待到一定数量,才打破栅栏
*/
class Client {
public static void main(String[] args) throws Exception{
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,new TourGuideTask());
Executor executor = Executors.newFixedThreadPool(3);
//登哥最大牌,到的最晚
executor.execute(new TravelTask(cyclicBarrier,"哈登",5));
executor.execute(new TravelTask(cyclicBarrier,"保罗",3));
executor.execute(new TravelTask(cyclicBarrier,"戈登",1));
}
}
/**
* 旅行线程
*/
class TravelTask implements Runnable{
private CyclicBarrier cyclicBarrier;
private String name;
private int arriveTime;//赶到的时间
public TravelTask(CyclicBarrier cyclicBarrier,String name,int arriveTime){
this.cyclicBarrier = cyclicBarrier;
this.name = name;
this.arriveTime = arriveTime;
}
@Override
public void run() {
try {
//模拟达到需要花的时间
Thread.sleep(arriveTime * 1000);
System.out.println(name +"到达集合点");
cyclicBarrier.await();
System.out.println(name +"开始旅行啦~~");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
/**
* 导游线程,都到达目的地时,发放护照和签证
*/
class TourGuideTask implements Runnable{
@Override
public void run() {
System.out.println("****导游分发护照签证****");
try {
//模拟发护照签证需要2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
回环栅栏:让一组线程等待至某个状态之后再全部同时执行。
叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
叫做栅栏,大概是描述所有线程被栅栏挡住了,当都达到时,一起跳过栅栏执行,也算形象。我们可以把这个状态就叫做barrier。
2、7 辅助类: Semaphore
Semaphore是信号量,用于管理一组资源。其内部是基于AQS的共享模式,AQS的状态表示许可证的数量,在许可证数量不够时,线程将会被挂起;而一旦有一个线程释放一个资源,那么就有可能重新唤醒等待队列中的线程继续执行。
/**
* 信号量
* 计数信号量。从概念上讲,一个信号量维护一组允许。每个 acquire()块如果必要的许可证前,是可用的,然后把它。每个 release()添加许可,
* 潜在收购方释放阻塞。然而,不使用实际允许的对象; Semaphore只是计数的数量和相应的行为。
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();//获取或消耗一个资源(可中断)
System.out.println(Thread.currentThread().getName()+"我抢到车位了");
Thread.sleep(1000);
semaphore.release();
System.out.println(Thread.currentThread().getName()+"我离开车位了");
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
3、线程的生命周期
3、1 线程的运行
1 在线程start()后不是立即运行,需要等待CPU的调度
2 线程的六个状态:Thread.State()从这个进入查看源码
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
4、java线程的分类
线程的类别 | 作用 |
---|---|
用户线程 | 运行在前台,执行具体的任务,程序的主线程,连接网络的子线程都是用户线程 |
守护线程 | 运行在后台,为用户线程服务 一旦所有的用户线程结束运行,守护线程会随着jvm一起结束工作 应用领域:数据库连接池的监测线程, jvm虚拟机启动的监测线程 最常见的守护线程:垃圾回收线程 |
4、1 设置守护线程
通过调用Thread类的setDaemon(true)方法来设置当前线程为守护线程
4、2 注意事项
-
setDaemon(true)必须在start ( )方法之前调用,否则会抛出IllegalThreadStateException异常
-
在守护线程中产生的新线程也是守护线程
-
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
4、3 当主线程阻塞,守护线程会关闭
/**
* 测试:守护线程一直在后台向某文件写入东西,主线程读取后终止,守护线程会因为主线程结束而跟随jvm一起结束
*/
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("进入了守护线程");
try {
WriteToFile();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("退出了守护线程");
}
//一个向某文件写入的方法
private void WriteToFile() throws Exception {
FileOutputStream out = new FileOutputStream(new File("D:/jdk/java/多线程/TempFile.txt"),true);
int count=0;
while(count<999){
count++;
out.write(("\r\n 美好世界"+count).getBytes());
System.out.println("守护线程 写入"+count+"次了");
Thread.sleep(1000);
}
}
}
class Main01{
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程启动");
//开启一个线程
Thread thread = new Thread(new MyThread());
//将该线程设置为守护线程
thread.setDaemon(true);
//启动守护线程
thread.start();
Scanner sc = new Scanner(System.in);
//制造阻塞
sc.next();
System.out.println("主线程结束");
}
}
4、4 Jstack
Jstack | |
---|---|
作用 | 生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息) |
目的 | 帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等 |
5、正常没有创建线程都会有俩个进程存在,一个main线程,一个垃圾回收的GC线程
//使用匿名内部类的方法来创建线程,可以减少创建一个类
new Thread(new Runnable() {
@Override
public void run() {
}
},"A").start();
6、停止某个线程
停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被废弃的方法。
在java中有以下几种方法可以终止正在运行的线程:
-
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
-
使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
-
使用interrupt()方法中断线程。
4.使用return停止线程将方法interrupt()与return结合使用也能实现停止线程的效果
注:
- interrupt()方法的使用效果并不像for+break语句那样,马上就停止循环。调用interrupt方法其本身并不是一个强制打断线程的方法,其仅仅会修改线程的interrupt标志位,然后让线程自行去读标志位,自行判断是否需要中断
- stop()方法以及作废,因为如果强制让线程停止有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题。
- 当线程处于写文件的状态时,调用 interrupt() 不会中断线程。
- 在设计里,如果线程处于休眠状态,那一旦其被调用interrupt()方法,则就没有必要继续休眠下去了,直接抛出异常InterruptedException,让被打断线程去做收尾操作,及时释放线程资源。
6、1 成功停止线程
/**
* 用return和interrupt()停止线程
* //检查是否需要停止
* if(isInterrupted())
* //停止,退出
* return;
*/
public class MyThread01 {
}
//抑制检查 参数all 所有类型
@SuppressWarnings("all")
class Main02{
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run() {
for (int i = 0; i <100000 ; i++) {
if(isInterrupted()) {
return;
}
System.out.println("MyThread01:=> i:"+i);
}
}
};
thread01.start();
Thread thread02 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread01.interrupt();
}
};
thread02.start();
}
}
6、2 失败停止线程
/**
* 用return和interrupt()停止线程
*
* 即使thread02有 thread01.interrupt();,但也不会停止
* 因为interrupt()其本身并不是一个强制打断线程的方法,
* 其仅仅会修改线程的interrupt标志位,然后让线程自行去读标志位,
* 自行判断是否需要中断。
*/
public class MyThread02 {
}
//抑制检查 参数all 所有类型
@SuppressWarnings("all")
class Main01{
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run() {
for (int i = 0; i <100000 ; i++) {
System.out.println("MyThread01:=> i:"+i);
}
}
};
thread01.start();
Thread thread02 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread01.interrupt();
}
};
thread02.start();
}
}
7、中断某个线程
初介绍:线程中断是一种软性的停止线程的机制,他并不会立即终止线程否则就是stop,而是通知被中断线程“希望”该线程尽快停止,
@SuppressWarnings("all")
public class MyThread01 implements Runnable{
@Override
public void run() {
for (int i = 0; i <100000 ; i++) {
if(Thread.interrupted()) { //判断线程是否需要中断
return;
}
System.out.println("MyThread01:=> i:"+i);
}
}
}
@SuppressWarnings("all")
class Main02{
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run() {
for (int i = 0; i <100000 ; i++) {
if(isInterrupted()) { //判断是否已经中断
return;
}
System.out.println("MyThread01:=> i:"+i);
}
}
};
thread01.start();
Thread thread02 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread01.interrupt();
}
};
thread02.start();
}
}
与interrupt线程中断的相关方法有三个:
- interrupt 实例方法,用于设置线程的中断标志位
- isinterrupted 实例方法,用于判断线程是否被中断
- interropted,静态方法,判断线程是否被中断并清除中断标志位
注:Thread.Interrupt()方法只是设置相关线程的中断标志位,通知目标线程中断,即该方法只是修改标志位并没有对真正中断该线程。所以需要在目标类中手动添加中断处理逻辑,在发现了中断信号执行相关逻辑.
8、thread的notify、join、yield方法
8、1 notify
唤醒wait队列中的第一个线程,与此对应的notifyAll唤醒wait队列中的所有线程。
8、2 Join
线程插队,在线程1中执行Thread2.join线程1会在线程2结束后才执行。
Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
t1.join();
join的意思是使得放弃当前线程的执行,并返回对应的线程,例如上面代码的意思就是:程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕.所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
个人理解:join用来抢夺执行到了这个方法的cpu的执行权,使被抢夺的线程处于冻结状态,在抢夺到cpu执行权的线程运行完run后,被抢夺线程恢复到运行状态,就相当于线程A抢夺了Main(前台)线程的CPU执行权,使齐main线程等待线程A执行完,才能获取cpu执行权,与其启动的线程b不受影响。
8、3 yield
线程会让出资源,但是并不会让出锁,若果是加锁的代码块,会在该线程完全结束后其他线程才能访问。
9、线程的异常处理
9、1 理念:
“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。
9、2 未捕获的异常去向
一个异常被抛出后,如果没有被捕获处理,则会一直向上抛。异常一旦被Thread.run() 抛出后,就不能在程序中对异常进行捕获,最终只能由JVM捕获。
9、3 异常处理方法
在Java中有一个异常处理器uncaughtExceptionHandler的接口,并且每一个线性对象Thread都有一个属性:uncaughtExceptionHandler。如果一个线程内部产生了异常,并且没有try,catch来处理,最终会被uncaughtExceptionHandler这个对象捕获。如果uncaughtExceptionHandler这个对象为null的话,这个异常就无法被捕获。
处理方法:
实现UncaughtExceptionHandler接口
public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到异常:"+e);
//TODO处理异常的逻辑
}
}
设置线程对象的uncaughtExceptionHandler
public static void main(String[] args){
Thread thread = new Thread();
MyUncaughtExceptionHandler exceptionHandler = new MyUncaughtExceptionHandler();
thread.setUncaughtExceptionHandler(exceptionHandler);
}
例子
public class Main01 {
public static void main(String[] args) {
Thread thread01 = new Thread(new MyThread01());
MyUncaughtExceptionHandler exceptionHandler = new MyUncaughtExceptionHandler();
thread01.setUncaughtExceptionHandler(exceptionHandler);
thread01.start();
}
}
class MyThread01 implements Runnable{
@Override
public void run() {
int i=1/0;
}
}
10、死锁的解决方案
10、1 什么是死锁?
死锁是由于两个或以上的线程互相持有对方需要的资源,导致这些线程处于等待状态,无法执行。
10、2 产生死锁的四个必要条件
1.互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。
2.请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。
3.不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。
4.循环等待:发生死锁时,线程进入死循环,永久阻塞。
10、3 产生死锁的原因
在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁。例如:事务A 获取了行 1 的共享锁。事务 B 获取了行 2 的共享锁。
排他锁,等待事务 B 完成并释放其对行 2 持有的共享锁之前被阻塞。
排他锁,等待事务 A 完成并释放其对行 1 持有的共享锁之前被阻塞。
事务 B 完成之后事务 A 才能完成,但是事务 B 由事务 A 阻塞。该条件也称为循环依赖关系:事务 A 依赖于事务 B,事务 B 通过对事务 A 的依赖关系关闭循环。除非某个外部进程断开死锁,否则死锁中的两个事务都将无限期等待下去。
10、4 死锁的解决方案
1.抢占资源,从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
2.终止(或撤销)进程,终止(或撤销)系统中的一个或多个死锁进程,直至打破循环环路,使系统从死锁状态解脱出来.
3.通过协议来预防或避免死锁,确保系统不会进入死锁状态。 可以允许系统进入死锁状态,
4.可以允许系统进入死锁状态,然后检测它,并加以恢复。
5.可以忽视这个问题,认为死锁不可能在系统内发生。
11、注意点
sleep()不会释放对象锁,但是会让出cpu的资源
sleep()和wait()方法的最大区别是:
- sleep()睡眠时,保持对象锁,仍然占有该锁;
- 而wait()睡眠时,释放对象锁。
使用锁的注意点:锁的对象不能为空,锁的作用域不宜过大,避免死锁
12、Synchronize关键字
12、1 Synchronize性质:
可重入,不可中断
12、2 synchronize作用:
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。(能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。)
12、4 Synchronize缺陷:
效率低:锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程
不够灵活(读写锁更灵活):加锁和释放锁的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。读操作不加锁,写操作加锁
无法知道是否成功获得到锁
12、5 不使用synchronize无法保证数据准确
/**
* 不使用synchronize锁来进行累加
*
* 结果:DisappearRequest01:=>i:108413
* i++ 实际不存在原子性,内部有三个操作,
* 1:读取数值
* 2:修改数值
* 3:将数值返回保存到内存处
* 因为thread1还没将变化后的值写入内存中,thread2就将值读取了,所以相当于运行了两次9+1。
*/
public class DisappearRequest01 implements Runnable {
static int i=0;
@Override
public void run() {
for (int j = 0; j <100000 ; j++) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread01 = new Thread(new DisappearRequest01());
Thread thread02 = new Thread(new DisappearRequest01());
thread01.start();
thread02.start();
// /join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
// 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
thread01.join();
thread02.join();
System.out.println("DisappearRequest01:=>i:"+i);
}
}
12、6 八锁情况(自定义的)
* 1 标准访问。俩个同步代码块,一部手机,俩个用户,先发短信还是邮件 -------邮件
* 2 暂停3秒再发邮件,1部手机,俩个用户,先发短信还是邮件 -------邮件
* 3 一个普通方法,一个同步代码块,1部手机,俩个用户,先发普通方法还是邮件 -------普通方法
* 4 2部手机,先发短信还是邮件 -------短信
* 5 俩个静态代码块,一部手机,俩个用户,先发短信还是邮件 -------邮件
* 6 俩个静态代码块,2部手机,俩个用户,先发短信还是邮件 -------邮件
* 7 一个静态代码块,一个同步代码块,1部手机,俩个用户,先发普通方法还是邮件 -------短信
* 8 一个静态代码块,一个同步代码块,2部手机,俩个用户,先发普通方法还是邮件 -------短信
详情
synchronize锁的是对象,创建出来的对象 eg:phone1,phone2
static synchronize 锁的是创建这个对象的模板,为整个Class类 eg:Phone
同步方法和普通方法,等于俩道门,俩把锁,不影响
12、7 对象锁与类锁
12、7、1 方法锁
概念:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己制定锁对象)
使用方法:
- 1同步方法块(手动指定锁对象)
- 2普通方法锁(Synchronized修饰普通方法,所对象默认为this)
12、7、2 类锁
概念:类锁实际上是class对象的锁,class对象只有一个,但是类对象可以有多个
本质:所谓的类型,不过是class对象的锁而已
用法和效果:类锁只能在同一时刻被一个对象拥有。
使用方法:
- 1、synchronized加在static方法上
- 2、synchronized(*.class)代码块
12、7、3 总结
1、一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待
2、每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是.class以及synchronized修 饰的是static方法的时候,所有对象共用同一把类锁*
3、无论是方法正常执行完毕或者方法抛出异常,都会释放锁
12、8 当线程抛出异常,默认锁会被释放
程序在执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
比如,在一个web app处理的过程中,有多个servlet线程同时访问一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会就如同步代码区,有可能会访问到异常产生的数据,因此要非常小心的处理同步业务逻辑中的异常
12、9 synchronize 锁的性质
可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁 好处:避免死锁,提升封装性
不可中断:一旦这个锁被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁,如果别人永远不释放锁,那么我只能永远的等待
12、10 synchronize 可重入的原理
加锁计数器:
1、JVM负责跟踪对象被加锁的次数;
2、有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。
3、任务结束离开,则会执行monitorexit,计数递减,直到完全释放
12、10 synchronize 可见性原理
JAVA内存模型
线程A与线程B通信详解:线程A从主内存获取变量,进行修改后然后在存入主内存中,线程b从主内存中获取变量,从而获取线程a修改的数据
12、11 synchronize 总结
JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。
13、 读写锁 也称(独占锁和共享锁)
独占锁(也称为 写锁 )
共享锁(也称为 读锁 )
/**
* 先创建一个缓存线程
1我进来写了 3我进来写了 3我进来写完了 2我进来写了 4我进来写了 5我进来写了
1我进来写完了 5我进来写完了 4我进来写完了 2我进来写完了
普通操作无法保证原子性
必须更改为读写锁 也就是俗称的 独占锁(写锁) 共享锁(读锁)
1我进来写 1我进来写完了 5我进来写了 5我进来写完了 2我进来写了 2我进来写完了
保证原子性,进来写完出去后才能别人进来写
*/
public class MyReaderWriteLock {
// static MyCache myCache=new MyCache();//正常的缓存
static MyReaderWriteLockCache myCache=new MyReaderWriteLockCache();//加了读写锁的缓存
public static void main(String[] args) {
//写操作
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.write(temp+"",temp+"");
},String.valueOf(temp)).start();
}
//读操作
for (int i = 1; i <=5; i++) {
final int temp=i;
new Thread(()->{
myCache.reader(temp+"");
},String.valueOf(temp)).start();
}
}
}
class MyCache{
HashMap hashMap=new HashMap<>();
//读的方法
public void reader(String key){
hashMap.get(key);
}
//写的方法
public void write(String key ,String value){
hashMap.put(key,value);
}
}
@SuppressWarnings("All")
class MyReaderWriteLockCache{
private HashMap hashMap=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//读的方法
public void reader(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"我进来读了");
hashMap.get(key);
System.out.println(Thread.currentThread().getName()+"我进来读完了");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
//写的方法
public void write(String key ,String value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"我进来写了");
hashMap.put(key,value);
System.out.println(Thread.currentThread().getName()+"我进来写完了");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
}
14、阻塞队列
BlockQueue (家族关系图如下)
四组Api使用:
方式 | 抛出异常 | 不抛出异常,返回有无异常 true或者flase | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(,time,TimeUtil.) |
删除 | remove | poll | take | poll(time,TimeUtil.) |
检测队列首部元素 | element | peek | - | - |
@SuppressWarnings("ALL")
public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
BlockingQueueDemo blockingQueueDemo = new BlockingQueueDemo();
//抛异常
// blockingQueueDemo.test1();
//不抛异常
// blockingQueueDemo.test2();
//一直阻塞
// blockingQueueDemo.test3();
//超时阻塞
// blockingQueueDemo.test4();
}
/**
* 当队列已经满了,在添加元素,会抛出异常 java.lang.IllegalStateException: Queue full
* 当队列已经空了,在剔除元素,会抛出异常 java.util.NoSuchElementException
* @throws Exception
*/
public void test1() throws Exception {
//规定了对列的大小为6
ArrayBlockingQueue blockQueue= new ArrayBlockingQueue(6);
//往队列塞入6个值,返回true
System.out.println("添加:"+blockQueue.add("今"));
System.out.println("添加:"+blockQueue.add("天"));
System.out.println("添加:"+blockQueue.add("天"));
System.out.println("添加:"+blockQueue.add("气"));
System.out.println("添加:"+blockQueue.add("好"));
System.out.println("添加:"+blockQueue.add("好"));
System.out.println("队首的元素:"+blockQueue.element());
//当队列已经满了,在添加元素,会抛出异常 java.lang.IllegalStateException: Queue full
// System.out.println("添加:"+blockQueue.add("啊"));
System.out.println("剔除:"+blockQueue.remove());
System.out.println("剔除:"+blockQueue.remove());
System.out.println("剔除:"+blockQueue.remove());
System.out.println("剔除:"+blockQueue.remove());
System.out.println("剔除:"+blockQueue.remove());
System.out.println("剔除:"+blockQueue.remove());
//当队列已经空了,在剔除元素,会抛出异常 java.util.NoSuchElementException
// System.out.println("剔除:"+blockQueue.remove());
}
/**
* 当队列已经满了,在添加元素,不会抛出异常 返回false
* 当队列已经空了,在剔除元素,不会抛出异常 返回null
*/
public void test2(){
//规定了对列的大小为6
ArrayBlockingQueue blockQueue= new ArrayBlockingQueue(6);
//往队列塞入6个值,返回true
System.out.println("添加:"+blockQueue.offer("今"));
System.out.println("添加:"+blockQueue.offer("天"));
System.out.println("添加:"+blockQueue.offer("天"));
System.out.println("添加:"+blockQueue.offer("气"));
System.out.println("添加:"+blockQueue.offer("好"));
System.out.println("添加:"+blockQueue.offer("好"));
System.out.println("对首的元素:"+blockQueue.peek());
//当队列已经满了,在添加元素,不会抛出异常 返回false
System.out.println("添加:"+blockQueue.offer("啊"));
System.out.println("剔除:"+blockQueue.poll());
System.out.println("剔除:"+blockQueue.poll());
System.out.println("剔除:"+blockQueue.poll());
System.out.println("剔除:"+blockQueue.poll());
System.out.println("剔除:"+blockQueue.poll());
System.out.println("剔除:"+blockQueue.poll());
//当队列已经空了,在剔除元素,不会抛出异常 返回null
System.out.println("剔除:"+blockQueue.poll());
}
/**
* 当队列已经满了,在添加元素,不会抛出异常 会一直阻塞等待
* 当队列已经空了,在剔除元素,不会抛出异常 会一直阻塞等待
*/
public void test3() throws InterruptedException {
//规定了对列的大小为6
ArrayBlockingQueue blockQueue= new ArrayBlockingQueue(6);
//往队列塞入6个值,返回true
blockQueue.put("今");
blockQueue.put("天");
blockQueue.put("天");
blockQueue.put("气");
blockQueue.put("好");
blockQueue.put("好");
//当队列已经满了,在添加元素,不会抛出异常 会一直阻塞等待
blockQueue.put("啊");
System.out.println("剔除:"+blockQueue.take());
System.out.println("剔除:"+blockQueue.take());
System.out.println("剔除:"+blockQueue.take());
System.out.println("剔除:"+blockQueue.take());
System.out.println("剔除:"+blockQueue.take());
System.out.println("剔除:"+blockQueue.take());
//当队列已经空了,在剔除元素,不会抛出异常 会一直阻塞等待
blockQueue.take();
}
/**
* 当队列已经满了,在添加元素,不会抛出异常 会等待一段时间
* 当队列已经空了,在剔除元素,不会抛出异常 会等待一段时间
*/
public void test4() throws InterruptedException {
//规定了对列的大小为6
ArrayBlockingQueue blockQueue= new ArrayBlockingQueue(6);
//往队列塞入6个值,返回true
System.out.println("添加:"+blockQueue.offer("今",2, TimeUnit.SECONDS));
System.out.println("添加:"+blockQueue.offer("天",2,TimeUnit.SECONDS));
System.out.println("添加:"+blockQueue.offer("天",2,TimeUnit.SECONDS));
System.out.println("添加:"+blockQueue.offer("气",2,TimeUnit.SECONDS));
System.out.println("添加:"+blockQueue.offer("好",2,TimeUnit.SECONDS));
System.out.println("添加:"+blockQueue.offer("好",2,TimeUnit.SECONDS));
//当队列已经满了,在添加元素,不会抛出异常 会超时阻塞等待2秒,如果2秒内还未取入东西
System.out.println("添加:"+blockQueue.offer("啊",2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
//当队列已经空了,在剔除元素,不会抛出异常 会超时阻塞等待2秒,如果2秒内还未存入东西
System.out.println("剔除:"+blockQueue.poll(2,TimeUnit.SECONDS));
}
}
SynchronousQueue同步队列
/**
* 同步队列,一种特殊的阻塞队列
* 他的容量为1,当存了一个值后就没法在存第二个值,必须取出才能存
*/
public class SynchronousQueueDemo {
static BlockingQueue<String> synchronousQueue=new SynchronousQueue();
public static void main(String[] args) {
new Thread(()->{
try {
System.out.println("存入:A");
synchronousQueue.put("A");
System.out.println("存入:B");
synchronousQueue.put("B");
System.out.println("存入:C");
synchronousQueue.put("C");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("取出:"+synchronousQueue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("取出:"+synchronousQueue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("取出:"+synchronousQueue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
15、Executors 类 创建线程池
15、0 线程池的好处
1:降低资源的损坏
2:提高响应速度
3:方便管理
线程复用,可以控制最大并发数,管理线程
15、1 Executors 三大创建线程池的方法
/**
* Executors创建线程池例子
*/
public class MyThreadPollDemo {
public static void main(String[] args) {
//创建有缓存功能的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//创建一个可以自定义容量的线程池
// ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建一个容量为一的线程池
// ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <=10; i++) {
final int temp =i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"=="+temp);
});
}
//关闭线程池
executorService.shutdown();
}
}
15、2 Executors 创建线程池的七大参数
三大创建线程池的方法内原理
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时没有线程调用后存活的最大时间
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue){ //阻塞队列
ThreadFactory threadFactory, //线程工厂:创造线程,一般不动
RejectedExecutionHandler handler) { //拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
核心线程池大小可以从 0-Integer.MAX_VALUE(21亿) 因为可以存在21亿,所以会产生oom异常,所以不推荐我们使用Executors创建线程池,而是手动ThreadPoolExecutor来创建线程池
15、3 Executor 四种拒绝策略
/**
拒绝策略
new ThreadPoolExecutor.AbortPolicy() //拒绝策略 队列满了,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略 从哪来回哪去 交给main线程做
new ThreadPoolExecutor.DiscardPolicy() //拒绝策略 队列满了,直接丢弃任务,并不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() //拒绝策略 队列满了,尝试去和最早的竞争,并不抛出异常
*/
public class MyThreadPollDemo {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2, 3,
3, TimeUnit.SECONDS,
new LinkedBlockingQueue(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy() //拒绝策略 队列满了,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略 从哪来回哪去 交给main线程做
// new ThreadPoolExecutor.DiscardPolicy() //拒绝策略 队列满了,直接丢弃任务,并不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() //拒绝策略 队列满了,尝试去和最早的竞争,并不抛出异常
);
for (int i = 1; i <=7; i++) {
final int temp=i;
myThreadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+":=>"+temp);
});
}
//关闭线程池连接
myThreadPool.shutdown();
}
}
15、4 线程池的最大的大小如何设置
最大线程池的定义 (调优)
1:CPU 密集型 几核 就是最大线程池数,可以保证线程池的效率最高
2:IO 密集型 大于判断你程序中十分耗io的线程
/**
获取当前运行环境的cpu核数
Runtime.getRuntime().availableProcessors()
最大线程池的定义
1:CPU 密集型 几核 就是最大线程池数,可以保证线程池的效率最高
2:IO 密集型 大于判断你程序中十分耗io的线程
*/
public class MyThreadPollDemo {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(), //获取当前运行环境的cpu核数
3, TimeUnit.SECONDS,
new LinkedBlockingQueue(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
for (int i = 1; i <=7; i++) {
final int temp=i;
myThreadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+":=>"+temp);
});
}
//关闭线程池连接
myThreadPool.shutdown();
}
}
16、四大函数式接口
底层:@FunctionalInterface
简介编程模式,在新版本的框架底层大量使用
foreach(消费者类的函数式接口)
Funcution 接口 :函数式接口,传入一个,传出一个
/**
* Function 函数式接口:传入一个参数,返回一个参数
* 使用Lambda简化
*/
@SuppressWarnings("all")
public class FunctionDemo {
public static void main(String[] args) {
//创建匿名内部类
// Function<String, String> myFunction = new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s+s+"";
// }
// };
//使用Lambda简化
Function<String, String> myFunction=(str)->{return str+str+"";};
System.out.println(myFunction.apply("Abcd"));
}
}
Predicate 接口 :断言式接口,传入一个参数,返回一个boolean值[外链图片转存失败,源站可能有防盗链机制,
/**
* 断言式函数:传入一个参数,返回一个boolean值
*/
public class PredicateDemo {
public static void main(String[] args) {
//判断字符是否为空,如果为空返回false
// Predicate<String> myPredicate=new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return !s.isEmpty();
// }
// };
//使用Lambda简化
Predicate<String> myPredicate=(str)->{return !str.isEmpty();};
System.out.println(myPredicate.test("cese"));
}
}
Consumer 消费型函数接口:只传入,无传出
/**
* 消费型函数接口:只传入,不返回
*/
public class ConsumerDemo {
public static void main(String[] args) {
// Consumer<String> myConsumer=new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println("传入字符串的长度"+s.length());
// }
// };
Consumer<String> myConsumer=(str)->{System.out.println("传入字符串的长度"+str.length());};
myConsumer.accept("huangxuanru");
}
}
Supplier 供给型函数接口,不传入,只传出
/**
* 供给型函数接口: 没有参数,只有返回值
*/
public class SupplierDemo {
public static void main(String[] args) {
// Supplier<String> mySupplier = new Supplier<String>() {
// @Override
// public String get() {
// return "ok";
// }
// };
Supplier<String> mySupplier=()->{ return "ok";};
System.out.println(mySupplier.get());
}
}
17、 Stream流式计算
什么是流式计算:
大数据:存储+计算
存储:=》集合,mysql 本质就是存储数据
计算:=》交给流来操作
/**
* *题目要求:一分钟内完成此题,只能用一行代码实现!
* *现在有5个用户!筛选:
* *1、ID必须是偶数.
* *2、年龄必须大于23岁
* *3、用户名转为大写字母
* *4、用户名字母倒着排序
* *5、只输出一个用户!
* users.stream() 将链表转换为流
* .filter(user ->{return user.getId()%2==0;}) 过滤ID必须是偶数.
* .filter(user ->{return user.getAge()>23;}) 过滤年龄必须大于23岁
* .map(user ->{return user.getName().toUpperCase();}) 给定函数应用到该流元素的结果--用户名转为大写字母
* .sorted((u1,u2)->{return u2.compareTo(u1);}) 排序--用户名字母倒着排序
* .limit(1) 输出的数量---只输出一个用户!
*/
public class TestDemo {
public static void main(String[] args) {
User user1=new User(1,"a",22);
User user2=new User(2,"b",23);
User user3=new User(3,"c",24);
User user4=new User(4,"d",25);
User user5=new User(5,"e",26);
User user6=new User(6,"f",27);
//存储数据
List<User> users = Arrays.asList(user1, user2, user3, user4, user5, user6);
//将list转变为流
users.stream()
.filter(user ->{return user.getId()%2==0;})
.filter(user ->{return user.getAge()>23;})
.map(user ->{return user.getName().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
18、ForkJoin
在大数据量下使用,分而计之
内部实际是,将一个任务分成俩个任务同时进行,队列为双端队列
/**
* 使用Forkjoin 来计算,分而合之计算
*/
public class ForkjoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long sum;
private Long temp=10000L; //临界值
public ForkjoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end-start)<temp){
for (Long i = start; i <=end ; i++) {
sum+=i;
}
return sum;
}else{
Long middle=(start-end)/2;
ForkjoinDemo task1 = new ForkjoinDemo(0L, middle);
task1.fork();
ForkjoinDemo task2 = new ForkjoinDemo(middle+1, end);
task2.fork();
return task1.join()+task2.join();
}
}
}
class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkjoinDemo forkjoinDemo = new ForkjoinDemo(0L, 1000000000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(forkjoinDemo);
Long aLong = submit.get();
System.out.println(aLong);
}
}
18、1 测试ForkJoin与for循环与Stream流式计算的效率
/**
* 比较计算的方式和时间差
* 3(for循环) 6(ForkJoin) 9(Stream)
*/
public class Test {
private static long sum=0;
public static void main(String[] args) throws Exception {
Long startTime= System.currentTimeMillis();
// test1(); //耗时:5924ms
// test2(); //耗时:1552ms
// test3(); //耗时:152ms
Long endTime = System.currentTimeMillis();
System.out.println("得数:"+sum+" 耗时:"+(endTime-startTime)+"ms");
}
static public void test1(){
for (Long i = 0L; i <=10_0000_0000L ; i++) {
sum+=i;
}
}
static public void test2() throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkjoinDemo(0L,1000000000L));
sum = submit.get();
}
static public void test3(){
sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
}
}
于此得知 效率:Stram>forkJoin>for
19、异步回调
/**
* 异步回调 CompletableFuture
* 1 异步执行
* 2 成功回调
* 3 失败回调
*/
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// //创建一个无返回类型的异步回调任务
VoidCompletableFuture();
//创建一个有返回类型的异步回调任务
IntegerCompletableFuture();
}
@SuppressWarnings("all")
private static void IntegerCompletableFuture() throws InterruptedException, ExecutionException {
//创建一个有返回类型的异步回调任务
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"我进来了");
int i=1/0; //故意出錯
return 1024;
});
System.out.println("我是main 0 0 ");
completableFuture.whenComplete((t,v)->{ //当计算的时候
System.out.println(t+" t"); //成功完成结果存在t
System.out.println(v+" v"); //出异常会把异常信息存在v
}).exceptionally((e)->{ //异常情况下
//打印错误信息
System.out.println(e.getMessage());
return 500;
}).get();
System.out.println(completableFuture.get());
}
@SuppressWarnings("all")
private static void VoidCompletableFuture() throws InterruptedException, ExecutionException {
//创建一个无返回类型的异步回调任务
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
//强行睡3秒钟
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"");
});
System.out.println("我是main 0 0进来的");
//阻塞等待异步回调的结果
completableFuture.get();
System.out.println("我是main 0 0出来的 我被阻塞了");
}
}
2 JMM
java内存模型https://www.processon.com/diagraming/5ea1ce125653bb6efc67dc34
Lock:加锁,作用于主内存的变量,作用是把一个共享变量标识为一个线程独占状态。
UnLock:解锁,作用于主内存变量,作用是把一个处于锁定状态的变量释放,然后可以被其他线程锁定。
read:读取,作用于主内存中的变量,作用是从主内存中读取出后面load操作要用到的变量。
load:载入,作用于主内存中的变量,把刚才read的值放入工作内存的副本中。
use:使用,作用于工作内存中的变量,当线程执行某个字节码指令需要用到相应的变量时,把工作内存中的变量副本传给执行引擎。
assign:赋值,作用于工作内存中的变量,把一个从执行引擎接收的值,复制给工作内存中的变量。
store:存储,作用于工作内存中的变量,把工作内存中的变量送到主内存,给后续的write使用。
write:写入,作用于主内存的变量,把store的工作内存中的变量值,写入主内存中。
同时,JMM还对这8种操作制定了一些规则,限制指令的执行:
1、read和load,store和write必须顺序执行,就是说必须是先read然后load,并且这两对中的每一对中的两个不能单独出现,就是说不能只出现read,也不能只出现load
2、不允许一个线程丢弃最近的assign操作,就是说工作内存中的变量改变后,必须同步到主内存
3、不允许一个线程没有发生任何的assign操作,就把工作内存中的变量同步到主内存
4、一个新的变量必须诞生于主内存,不允许工作内存使用一个没有初始化的变量,即use和stroe前只执行了assign和load操作
5、一个变量同一时刻,只允许一个线程对其lock,并且该线程可以对该变量加锁多次,释放锁需要执行相同次数的unlock,lock和unlock要成对出现
6、一个变量没有lock,不能unlock,并且一个线程不能unlock被其他线程锁住的变量,执行unlock前,必须把工作内存中的变量同步到主内存中
7、一个线程执行lock操作,需要清空工作内存,并且需要使用该变量之前,要重新执行load和assign操作
1、volatile — 一个轻量级的线程同步机制。
保证了变量在线程之间的可见性。
当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。但是volatile并不保证变量更新的原子性,在一些场景下,用volatile修饰的变量仍然不是线程安全。
禁止指令重排
加入volatile关键字后会出现内存屏障,是一种cpu指令
作用:保证特定操作执行顺序
保证了某些变量的内存可见性(利用了这个特性实现了可见性)
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
内存屏障可以被分为以下几种类型
**LoadLoad屏障:**对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
**StoreStore屏障:**对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
**LoadStore屏障:**对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
**StoreLoad屏障:**对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
详细了解可看https://www.jianshu.com/p/2ab5e3d7e510
不保证原子性
因为number++ 不是一步指令,内部其实有三步指令,1:获取值,2:值+1 3:返回值回去
不保证原子性那如何解决原子性问题,使用juc下的原子类 这些类的底层都直接和操作系统挂钩 Unsafe类是一个很特殊的存在
/**
* 1998
2000
volatile不保证原子性
因为number++并非原子操作
使用原子类AtomicInteger来保证原子性
*/
public class VolatileDemo {
private static volatile int num=0;
private static volatile AtomicInteger atomicInteger=new AtomicInteger();
private static void add(){
atomicInteger.getAndIncrement();//getAndIncrement 自+1 方法 CAS
num++; //在volatile不保证原子性
}
public static void main(String[] args) {
// VolatileDemo volatileDemo = new VolatileDemo();
for (int i = 1; i <=20; i++) {
new Thread(() -> {
for (int j = 1; j <=100; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //让main线程等待。直到其他的线程跑完再继续下去
Thread.yield();
}
System.out.println(num);
System.out.println(atomicInteger);
}
}
2、CAS
乐观锁与悲观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
锁存在的问题:
Java在JDK1.5之前都是靠 synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。
悲观锁机制存在以下问题:
-
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
一个线程持有锁会导致其它所有需要此锁的线程挂起。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。
乐观锁:
乐观锁( Optimistic Locking )其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
CAS -----乐观锁的一种实现方式
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。
缺点:
- 出现ABA问题:
- 循环时间长开销大:
自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
- 只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
CAS与Synchronized的使用情景:
1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
atomicInteger.getAndIncrement();为啥会安全自增1
CAS原理:CAS通过调用JNI的代码实现的。而compareAndSwapInt()就是借助C来调用CPU底层指令实现的。
//下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
解决CAS出现的aba问题,添加标识符版本号
/**
* 解决CAS出现的aba问题
添加版本号
* 使用AtomicStampedReference:
* 一个 AtomicStampedReference保持随着整数“邮票”一个对象的引用,可以自动更新。
实现说明:此实现通过创建表示“框”[参考,整数]对的内部对象来保持标记的引用。
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
//创建AtomicStampedReference(初始值,版本号)
AtomicStampedReference<Integer> myAtomicReference = new AtomicStampedReference<>(3,1);
new Thread(()->{
int stamp = myAtomicReference.getStamp();
System.out.println("a1=:当前的版本号"+stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myAtomicReference.compareAndSet(3,4,stamp, stamp+1);
System.out.println("a2=:当前的版本号"+myAtomicReference.getStamp());
myAtomicReference.compareAndSet(4,3,myAtomicReference.getStamp(), myAtomicReference.getStamp()+1);
System.out.println("a3=:当前的版本号"+myAtomicReference.getStamp());
},"A").start();
new Thread(()->{
int stamp = myAtomicReference.getStamp();
System.out.println("b1=:当前的版本号"+stamp);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//因为版本不对,代表中途被修改了,所以下面操作不执行
myAtomicReference.compareAndSet(3,6,stamp, stamp+1);
System.out.println("b2=:当前的版本号"+myAtomicReference.getStamp());
},"B").start();
}
}
自定义自旋锁
/**
* 自定义一个自旋锁
*/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void MyLock(){
System.out.println("SpinLockDemo:=>"+Thread.currentThread().getName()+"进来加锁了");
while(!atomicReference.compareAndSet(null,Thread.currentThread())){
System.out.println(Thread.currentThread().getName()+"在自旋");
}
System.out.println("SpinLockDemo:=>"+Thread.currentThread().getName()+"加锁成功");
}
public void MyUnLock(){
System.out.println("SpinLockDemo:=>"+Thread.currentThread().getName()+"进来解锁了");
atomicReference.compareAndSet(Thread.currentThread(),null);
System.out.println("SpinLockDemo:=>"+Thread.currentThread().getName()+"解锁成功");
}
}
测试类
/**
测试类
SpinLockDemo:=>T1进来加锁了
SpinLockDemo:=>T2进来加锁了
T2在自旋 ...
SpinLockDemo:=>T1加锁成功
T2在自旋 ...
SpinLockDemo:=>T1进来解锁了
T2在自旋 ...
SpinLockDemo:=>T1解锁成功
SpinLockDemo:=>T2加锁成功
SpinLockDemo:=>T2进来解锁了
SpinLockDemo:=>T2解锁成功
因为t1在睡眠,t2无法加锁,一直在自旋
*/
public class TestSpinLock {
public static void main(String[] args) {
SpinLockDemo mySpinLock = new SpinLockDemo();
new Thread(() -> {
mySpinLock.MyLock();
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"在睡觉");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
mySpinLock.MyUnLock();
}
},"T1").start();
new Thread(() -> {
mySpinLock.MyLock();
try {
TimeUnit.SECONDS.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
mySpinLock.MyUnLock();
}
},"T2").start();
}
}
3 锁的总结
公平锁和非公平锁
公平锁:不允许插队
非公平锁:允许插队
默认一般情况:非公平锁
/** 公平锁
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
-----------------------------------------------------------------------------------------
/** 非公平锁
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁 (递归锁)
详情看上方
读写锁(共享锁/独占锁)
ReadWriteLock 详情看上方
自旋锁
do{
}while()
or
while(){
}
详情看上方
死锁
@Override
public void run() {
synchronized (A){
System.out.println("我是"+A+"我要"+B);
synchronized (B){
System.out.println("我是"+B+"我要"+A);
}
}
}
详情看上方
练习代码:http://git.wzubi.com/gulugulu/fist_case