JUC探索

线程的状态

New New状态是线程实例化后还没有执行start()方法的状态。

new Thread().getState();

RUNNABLE 线程进入运行的状态。

Thread t1 = new Thread(); t1.start(); 

TIMED_WAITING 有时间限制的等待。

Thread.sleep(XXX)

WAITING 线程执行了 lock.wait() 后的状态。永久等待,直到被另外一个线程lock.notify()

lock.wait() 

BLOCKED 当一个线程在等待同步锁时,线程状态为BLOCKED
TERMINATED 当任务执行完毕后的状态

Callable接口与Runnable接口的区别

public interface Callable {
    V call() throws Exception;
}

public interface Runnable {
    public abstract void run();
}

(1)Callable接口的方法是call(),Runnable接口的方法是run()
(2)Callable的任务执行后可返回值,而Runnable的任务执行后没有返回值
(3)Callable的任务执行中可以抛异常,而Runnable的任务不能抛出异常

异步调用Future模式

java.util.concurrent.FutureTask 类实现了 Runnable接口,Future接口,RunnableFuture接口。Future表示异步计算的结果。FutureTask类在计算完成后,可以通过get()方法获取其结果。若构造函数中接收的任务尚未完成,调用get()方法,将会出现线程阻塞,直到任务完成。
FutureTask必须要先执行run()方法,得出结果后,再调用get()方法可返回结果

    public static void main(String[] args) throws ExecutionException, 
                                                       InterruptedException {
        Callable c = ()->{
            System.out.println("睡两秒");
            Thread.sleep(2000);
            return "as";
        };
        FutureTask task = new FutureTask<>(c);
        //必须要先执行run才有结果,否则get()将一直等待
        task.run();
        System.out.println(task.get());
    }

FutureTask包装的是Callable或者Runnable,用的是构造函数接收的。因为FutureTask实现了Runnable接口,所以可以提交给ThreadPoolExecutor的execute(Runnable)执行。


    public static void main(String[] args) throws InterruptedException, 
                                                       ExecutionException {
        Callable task = () -> {
            //模拟计算时间
            Thread.sleep(5000);
            return "Callable接口实现的返回结果";
        };
        //创建FutureTask来包装Callable接口实现
        FutureTask futureTask = new FutureTask<>(task);
        //创建线程池准备执行任务
        ExecutorService service = Executors.newFixedThreadPool(1);
        //执行任务,线程池将会分配一个线程去执行指定的任务
        service.execute(futureTask);
        //主线程执行其它任务
        Thread.sleep(2000);
        System.out.println("主线程执行其它任务花费了2秒");
        //主线程需要子线程任务的结果
        String result = futureTask.get();
        System.out.println("FutureTask任务的执行结果是:"+result);
        //关闭线程池
        service.shutdown();
image.png

线程池ThreadPoolExecutor:

创建多个线程,会消耗许多内存,也非常耗时。
线程池:创建线程变成了从线程池获取空闲的线程,关闭线程变成了向池子中归还线程。
降低内存资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。在线程池中的线程都是已经被创建好的,我们的任务直接获取一个空闲的线程就能够被执行了。


image.png

ThreadPoolExecutor 的 execute(Runnable)方法 与父类ExecutorService的submit方法的区别
ThreadPoolExecutor # execute(..) 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
ExecutorService # submit(..) 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。


image.png

线程计数器CountDownLatch

线程计数器CyclicBarrier

构造函数接收计数器的值。
被计数的任务线程执行完毕后,记得调用一次 latch.countDown()方法,提示CountDownLatch对象,当前线程已经执行完毕。
在需要同步的地方,使用 latch.await()方法进行阻塞。再次恢复任务执行,需要CountDownLatch的计数器为0。
缺点:CountDownLatch这个类的缺点就很明显,如果子线程耗时过多,那么主线程也会一直等待,程序执行效率大大降低。
开始执行任务前,等待 N 个前置线程完成各自的准备任务:例如应用程序启动类要确保在处理用户请求前,所有 N 个外部系统已经启动和运行了。
实际用法:用于等待所有多线程执行的定时任务执行完毕后,打印日志耗时

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    private static final int THREAD_COUNT_NUM = 7;
    private static CountDownLatch latch = new CountDownLatch(THREAD_COUNT_NUM);
 
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < THREAD_COUNT_NUM; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "执行");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //每个线程执行完毕,都把计数器减1
                latch.countDown();
            }, "Thread" + i).start();
        }
 
        //CountDownLatch.await()阻塞主函数。
        //那么,主函数需要等到 latch.countDown()被调用七次后,方可恢复执行
        latch.await();
        System.out.println("主函数阻塞结束");
        /**
         * Thread1执行
           Thread3执行
           Thread5执行
           Thread0执行
           Thread2执行
           Thread4执行
           Thread6执行
           主函数阻塞结束
         */
 
    }
}

无锁机制CAS (compareAndSwap)

加锁是一种悲观的策略,它总是认为每次访问共享资源的时候,总会发生冲突,所以宁愿牺牲性能(时间)来保证数据安全
无锁是一种乐观的策略,无锁的策略使用一种叫做比较交换的技术(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。
无锁操作即CAS操作天生免疫死锁。
JUC包中有一个atomic包,它们是原子操作类

public final int incrementAndGet(){
        for(;;){
            int current = get();
            int next = current + 1;
            if(compareAndSet(current,next)){
                return next;
            }
        }
    }

ThreadLocal

每一个线程都可以独立地维护自己的副本


image.png

锁性能优化

一、减少线程持有锁的时间,例如可以把同步方法修改为同步代码块
二、锁分段:将一个对象的数据分割成多个部分,并为每个部分分配一把锁。ConcurrentHashMap
三、锁分离:读写锁ReentranceReadWriteLock,读读互不影响

你可能感兴趣的:(JUC探索)