JUC (狂神说笔记二)

9、读写锁

ReadWriteLock:读可以被多线程同时读,写的时候只能有一个线程去写

在这里插入图片描述

测试代码:

package pers.mobian.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest01 {
    public static void main(String[] args) {
        MyCacheLock myCacheLock = new MyCacheLock();
        //写入
        for (int i = 1; i <= 5; i++) {
            int temp = i;
            new Thread(() -> {
                myCacheLock.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }
        //读取
        for (int i = 1; i <= 5; i++) {
            int temp = i;
            new Thread(() -> {
                myCacheLock.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

//自定义一个我们的缓存

class MyCacheLock {
    //自定义一个map集合
    private volatile Map<String, Object> map = new HashMap<>();

    //定义一个读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    //写入
    public void put(String key, Object value) {
        //在写入数据的时候,使用我们的写锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }

    }

    //读取
    public void get(String key) {
        //在读取数据的时候,使用我们的写锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始读取");
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

总结:我们本可以直接使用Lock锁进行锁的,但是为了更加小粒度,我们就使用读写锁。我们虽然在读和写的时候都添加了锁,但是只会在写入的时候显示出效果。

其他地方也会听到两个名词:独占锁(写锁)和共享锁(读锁)


10、阻塞队列

BlockingQueue

10.1、概述

写入时:如果队列满了,就会阻塞等待

读取时:如果队列是空的,就会阻塞等待生产

在这里插入图片描述

使用阻塞队列的场景:多线程并发处理、线程池!

补充:BlockingQueue(阻塞队列)、Deque(双端队列)、AbstractQueue(非阻塞队列)

相关接口以及类之间的继承关系:
在这里插入图片描述

10.2、学会使用API

学会使用队列的添加和移除操作

四组API:

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

第一种:抛出异常的方式

public static void test01() {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    queue.add("a");
    queue.add("b");
    queue.add("c");
    //IllegalStateException: Queue full
    //queue.add("d");

    //打印队首元素
    System.out.println(queue.element());

    queue.remove();
    queue.remove();
    queue.remove();
    //NoSuchElementException
    //queue.remove();

}

第二种方式:有返回值,不抛出异常

public static void test02(){
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    System.out.println(queue.offer("a"));
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    //当容量超过队列容量时,有返回值,不抛出异常
    //System.out.println(queue.offer("d"));


    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll());

    //当我们的队首没有元素的时候,返回null
    System.out.println(queue.peek());

    //当队列没有元素的时候,返回null
    System.out.println(queue.poll());
}

第三种方式:阻塞等待

public static void test03() throws InterruptedException {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    queue.put("a");
    queue.put("b");
    queue.put("c");
    //会一直阻塞,进入等待期
    //queue.put("d");


    queue.take();
    queue.take();
    queue.take();
    //也会一直阻塞,进入等待期
    //queue.take();     
}

第四种方式:超时等待

//第四种
public static void test04() throws InterruptedException {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    System.out.println(queue.offer("a",2, TimeUnit.SECONDS));
    System.out.println(queue.offer("b",2, TimeUnit.SECONDS));
    System.out.println(queue.offer("c",2, TimeUnit.SECONDS));
    //其他用法与第二种方式相同。等待两秒以后,再返回与第二种形式相同结果
    System.out.println(queue.offer("d",2, TimeUnit.SECONDS));

    System.out.println(queue.poll(2, TimeUnit.SECONDS));
    System.out.println(queue.poll(2, TimeUnit.SECONDS));
    System.out.println(queue.poll(2, TimeUnit.SECONDS));
    //等待两秒以后,再返回与第二种形式相同结果
    System.out.println(queue.poll(2, TimeUnit.SECONDS));
}

10.3、同步队列

SynchronousQueue(同步队列)与BlockingQueue(阻塞队列)不一样,同步队列不存储元素。

每当同步队列put一个值以后,必须先使用take取出来,否则不能再put进去值。

测试代码:

package pers.mobian.blockingqueue;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class Test02 {
    public static void main(String[] args) {

        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

        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();
            }
        }).start();
        
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("移除一个元素");
                synchronousQueue.take();

                TimeUnit.SECONDS.sleep(3);
                System.out.println("移除一个元素");
                synchronousQueue.take();
                
                TimeUnit.SECONDS.sleep(3);
                System.out.println("移除一个元素");
                synchronousQueue.take();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

总结:每次我们put一个值以后,就不能够再继续put值了,需要使用take,将数据取出,这样才能再次put值进去


11、线程池

线程池:3大方法、7大参数、4种拒绝策略

池化技术:事先准备好一些资源,有人要使用,就去拿,使用完毕之后再归还。

线程池的好处:

  1. 降低资源的消耗
  2. 提高相应的速度
  3. 方便管理

线程复用、可用控制最大并发数、管理线程

11.1、3大方法

package pers.mobian.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool3Method {
    public static void main(String[] args) {
        //1.创建一个只有一个线程的线程池
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();

        //2.创建一个可伸缩的线程池
        //ExecutorService threadPool = Executors.newCachedThreadPool();

        //3.创建一个指定最大数量的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try {
            for (int i = 0; i < 10; i++) {
                //使用线程池的方式,创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "--OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //使用线程池的方式,关闭线程池
            threadPool.shutdown();
        }
    }
}

11.2、7大参数

我们查看3大方法的底层源码:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

本质:三大方法的本质都是去实例化了一个ThreadPoolExecutor类

ThreadPoolExecutor的构造方法的方法体:

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;
}

场景:正常情况下,就只会开启两个Core线程,然后新开启的线程就在阻塞队列中。当阻塞队列满了以后,就会开启Max线程,来保证更多的线程能够执行。如果线程还在不断的增加,最终达到了线程的最大值,就会使用拒绝策略,不再接受对应的线程请求。当线程业务处理完毕以后,指定的时间以后,就可以关闭因业务量后来新开的线程,即窗口3、4、5。

在这里插入图片描述

根据图示,自定义线程池:

package pers.mobian.threadpool;

import java.util.concurrent.*;

public class ThreadPool7Nums {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,//核心的窗口数量
                5,//最大的窗口数量
                3,//等待一定事件后关闭最大窗口
                TimeUnit.SECONDS,//等待时间的单位
                new LinkedBlockingDeque<>(3),//创建一个消息队列
                Executors.defaultThreadFactory(),//线程工厂,不逊要动
                new ThreadPoolExecutor.AbortPolicy()//使用四种拒绝策略里面的哪一种
        );

        try {
            for (int i = 0; i < 9; i++) {
                //使用线程池的方式,创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "--OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //使用线程池的方式,关闭线程池
            threadPool.shutdown();
        }
    }
}

11.3、4种拒绝策略

RejectedExecutionHandler

在我们定义线程池的七个参数时,最后一个是使用何种拒绝策略

四种拒绝策略的具体实现类:

在这里插入图片描述

//多出来的线程,直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
    
//谁开启的这个线程,就让这个线程返回给谁执行。比如main线程开启的,那就返回给main线程执行    
new ThreadPoolExecutor.CallerRunsPolicy()
    
//如果队列线程数量满了以后,直接丢弃,不抛出异常
new ThreadPoolExecutor.DiscardPolicy()
    
//队列满了以后,尝试去和最早的线程竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy

11.4、定义最大线程数

ExecutorService threadPool = new ThreadPoolExecutor(
        2,
        5,//最大的线程数量
        3,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(3),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.CallerRunsPolicy()
);

定义最大线程数量的两种方法(用于调优)

1、CPU密集型

根据我们电脑的最大核数进行设置,以求达到平行的高效性。由于不同的电脑的核数不相同,所以我们不能写死,需要使用Java代码去动态的获取电脑核数的大小

Runtime.getRuntime().availableProcessors()

2、IO密集型

首先我们需要知晓我们程序中有几个十分消耗IO的线程。如果是n个,那么我们就需要大于n就行,一般使用2n个。


12、四大函数式接口

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:只有一个方法的接口

例如:Runnable接口底层和forEach方法底层

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
1234
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i));
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

四大函数式接口:Function、Predicate、Consumer、Supplier

12.1、Function

函数型接口

Function接口体:传入一个输入参数,返回一个输出参数。并且只要是函数式接口,就可以使用lambda表达式进行简化书写

//参数:T是传入的参数,R是返回的参数
public interface Function<T, R> {
    R apply(T t);
}

具体的代码实现:

package pers.mobian.function;

import java.util.function.Function;

public class Test01 {
    public static void main(String[] args) {
        
//        常规的方法体
//        Function function = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return "==>"+s;
//            }
//        };
        //使用lambda表达式简化开发
        Function function = (s)->{return "==>"+s;};

        System.out.println(((Function<String, String>) function).apply("2"));
    }
}

12.2、Predicate

断定型接口

Predicate接口体:传入一个参数,根据对应的逻辑返回相应的boolean值

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

具体的代码实现:

package pers.mobian.function;

import java.util.function.Predicate;

public class PredicateTest01 {
    public static void main(String[] args) {

//        Predicate predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//               return s.equals("s");
//            }
//        };
        
        //使用lambda表达式进行简化书写
        Predicate predicate = (s)->{return s.equals("s");};

        System.out.println(predicate.test("s"));
    }
}

12.3、Consumer

消费型接口

Consumer接口体:只有输入没有返回值

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

具体的代码实现:

package pers.mobian.function;

import java.util.function.Consumer;

public class ConsumerTest01 {
    public static void main(String[] args) {
//        Consumer consumer =   new Consumer<String>(){
//            @Override
//            public void accept(String str) {
//                System.out.println("直接打印语句");
//            }
//        };

        //使用lambda表达式简化书写
        Consumer consumer = (a)->{
            System.out.println("直接打印语句");
        };

        consumer.accept("s");
    }
}

12.4、Supplier

供给型接口

Supplier接口体:不需要传入参数,直接

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

具体的代码实现:

package pers.mobian.function;

import java.util.function.Supplier;

public class SupplierTest01 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "OK";
//            }
//        };

        //使用lambda表达式简化开发
        Supplier supplier = ()->{return "OK";};

        System.out.println(supplier.get());
    }
}

13、Stream流式计算

对于大量的数据(大数据 = 存储 + 计算),我们可以使用集合或者MySQL进行存储与计算。但是我们应该明确一个点,他们的本职工作应该是用来存储数据。

那么此时,我们的计算就应该交给流

我们就可以使用 java.util.stream来进行计算。

结合lambda表达式、链式编程、函数式接口、Stream流式计算的小案例

题目要求:

  1. 使用Stream进行筛选计算
  2. 输出的id必须为偶数
  3. 年龄必须大于23岁
  4. 用户名转为大写字母
  5. 用户名字字母倒着排序
  6. 只能输出一个用户

测试代码:

//使用lombok插件
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private int id;
    private String name;
    private int age;
}
123456789
package pers.mobian.stream;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamTest01 {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 22);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(6, "e", 25);

        //使用List集合,存储五个用户
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //System.out.println(list);

        //使用Stream流进行计算
        list.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);
    }
}

14、ForkJoin

分支合并,分而治之

ForkJoin出现在JDK1.7中,为了并行的执行任务,提高效率而生的框架。只有在处理大量的数据的时候才能显现它的作用。

大数据下的Map Reduce的执行原理,是将大任务拆分为小任务,如图:

在这里插入图片描述

ForkJoin

特点:工作窃取

我们维护的都是一个双端队列。当B线程执行完毕以后,它会去帮助A线程执行,由于是双端队列的缘故,所以两边都能够开始执行。工作窃取,也就是偷取别人的工作,让总体的工作时间更短。

但是双端队列有一个问题,如果B线程执行完了以后去帮助A线程执行,容易出现抢占资源的问题。

在这里插入图片描述

使用方式:继承RecursiveTask(本质是ForkJoinTask),重写里面的抽象方法compute即可

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    private static final long serialVersionUID = 5232453952276485270L;

    V result;
    //继承该类
    protected abstract V compute();

    public final V getRawResult() {...}

    protected final void setRawResult(V value) {...}

    protected final boolean exec() {...}
}

计算大量数字的一个小案例:比较普通for、ForkJoin、Stream三种不同的方式的计算速度

package pers.mobian.forkjoin;

import java.util.concurrent.RecursiveTask;

//编写一个计算方法
public class ForkJoinDemo extends RecursiveTask<Long> {

    private long start;
    private long end;

    //临界值
    private long temp = 1000000;
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //继承类中的抽象方法,我们在里面编写我们的计算方法体
    @Override
    protected Long compute() {

        if ((end - start) < temp) {
            long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            //方法的返回值需要与继承类的类型相同
            return sum;
        } else {
            long middle = (start + end) / 2;

            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);

            //把一个任务拆分为两个小任务,并且将两个任务压入队列中
            task1.fork();
            task2.fork();

            //返回计算以后的结果
            return task1.join() + task2.join();
        }
    }
}



package pers.mobian.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;


public class ForkJoinTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test01();
        test02();
        test03();
    }

    //使用最基本的循环遍历计算
    public static void test01() {
        long start = System.currentTimeMillis();
        long sum = 0;
        for (long i = 0; i <= 10_0000_0000L; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();

        System.out.println("花费的时间是:" + (end - start) + "结果是" + sum);
    }

    //使用ForkJoin进行计算
    public static void test02() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        //创建一个ForkJoinPool池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
        //将自己的任务,提交至ForkJoinPool池中
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        long sum = submit.get();
        long end = System.currentTimeMillis();

        System.out.println("花费的时间是:" + (end - start) + "结果是" + sum);
    }

    //使用Java8的新特性,进行流式计算
    public static void test03() {
        long start = System.currentTimeMillis();
        //直接使用Stream流式计算结果
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();

        System.out.println("花费的时间是:" + (end - start) + " 结果是" + sum);
    }
}

总结:理论上是普通的for最慢,然后是ForkJoin中间,Stream最块。但是在我实际的测试中,ForkJoin才是最慢的,网上也看了很多别人的分析,但是没有找到答案。有知道的你们,也可以给我说说,是什么改了???


15、异步回调

我们常听说的异步是Ajax,其实在Java里面也有异步回调的说法,甚至还有专门的处理方式。

Java通过Future接口来实现,对将来的某个事件的结果进行建模

具体的实现代码:

没有返回值的runAsync异步回调

package pers.mobian.future;

import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class FutureTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值的runAsync异步回调
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"==>OK");
            }
        });
        System.out.println("123456");
        completableFuture.get();
    }
}

有返回值(t, u)的supplyAsync异步回调

package pers.mobian.future;

import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class FutureTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //有返回值(t, u)的supplyAsync异步回调
        //返回值是错误的信息
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            //添加一个异常语句
            int i = 10/0;
            System.out.println(Thread.currentThread().getName() + "==>OK");
            return 1024;
        });
        
        //获取异步回调对象的返回值(t, u),并打印。如果成功执行,就直接打印,如果执行出错,就执行对应的异常处理方法
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t);
            //u为错误的信息
            System.out.println("u=>" + u);
        }).exceptionally((e) -> {
            //输出错误信息:java.lang.ArithmeticException: / by zero
            System.out.println(e.getMessage());
            return 333;
        }).get());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
jstack生成的Thread Dump日志.docx 系统线程状态 (Native Thread Status) 系统线程有如下状态: deadlock 死锁线程,一般指多个线程调用期间进入了相互资源占用,导致一直等待无法释放的情况。 runnable 一般指该线程正在执行状态中,该线程占用了资源,正在处理某个操作,如通过SQL语句查询数据库、对某个文件进行入等。 blocked 线程正处于阻塞状态,指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。 waiting on condition 线程正处于等待资源或等待某个条件的发生,具体的原因需要结合下面堆栈信息进行分析。 (1)如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某种资源且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取,或者正在等待其他线程的执行等。 (2)如果发现有大量的线程都正处于这种状态,并且堆栈信息中得知正等待网络读,这是因为网络阻塞导致线程无法执行,很有可能是一个网络瓶颈的征兆: 网络非常繁忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读; 网络可能是空闲的,但由于路由或防火墙等原因,导致包无法正常到达; 所以一定要结合系统的一些性能观察工具进行综合分析,比如netstat统计单位时间的发送包的数量,看是否很明显超过了所在网络带宽的限制;观察CPU的利用率,看系统态的CPU时间是否明显大于用户态的CPU时间。这些都指向由于网络带宽所限导致的网络瓶颈。 (3)还有一种常见的情况是该线程在 sleep,等待 sleep 的时间到了,将被唤醒。 waiting for monitor entry 或 in Object.wait() Moniter 是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁,每个对象都有,也仅有一个 Monitor。 从上图可以看出,每个Monitor在某个时刻只能被一个线程拥有,该线程就是 "Active Thread",而其他线程都是 "Waiting Thread",分别在两个队列 "Entry Set"和"Waint Set"里面等待。其中在 "Entry Set" 中等待的线程状态是 waiting for monitor entry,在 "Wait Set" 中等待的线程状态是 in Object.wait()。 (1)"Entry Set"里面的线程。 我们称被 synchronized 保护起来的代码段为临界区,对应的代码如下: synchronized(obj){} 当一个线程申请进入临界区时,它就进入了 "Entry Set" 队列中,这时候有两种可能性: 该Monitor不被其他线程拥有,"Entry Set"里面也没有其他等待的线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区里面的代码;此时在Thread Dump中显示线程处于 "Runnable" 状态。 该Monitor被其他线程拥有,本线程在 "Entry Set" 队列中等待。此时在Thread Dump中显示线程处于 "waiting for monity entry" 状态。 临界区的设置是为了保证其内部的代码执行的原子性和完整性,但因为临界区在任何时间只允许线程串行通过,这和我们使用多线程的初衷是相反的。如果在多线程程序中大量使用synchronized,或者不适当的使用它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在Thread Dump中发现这个情况,应该审视源码并对其进行改进。 (2)"Wait Set"里面的线程 当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(通常是被synchronized的对象)的wait()方法,放弃Monitor,进入 "Wait Set"队列。只有当别的线程在该对象上调用了 notify()或者notifyAll()方法,"Wait Set"队列中的线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。"Wait Set"中的线程在Thread Dump中显示的状态为 in Object.wait()。通常来, 通常来,当CPU很忙的时候关注 Runnable 状态的线程,反之则关注 waiting for monitor entry 状态的线程。 JVM线程运行状态 (JVM Thread Status)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值