自学内容网 自学内容网

JUC的常见类

1.callable接口

callable是一个interface,相当于把线程封装了一个“返回值”,方便程序员借助多线程的方式计算结果。callable接口和Runnable接口是并列关系,但是Runnable返回值是void重写的是run方法更注重执行的过程而不是结果,而callable重写的是call方法,call是有返回值的返回值的类型是泛型参数。

1.2 callable接口的使用

callable代码实现:

import java.util.Timer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Demo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //此处callable只是定义了一个“带有返回值的”的任务
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                  int result = 0;
                for (int i = 1; i <= 100; i++) {
                    result += i;
                }
                return result;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        //get操作就是获取到 FutureTask 的返回值,这个返回值就来自于Callable的call方法
        // get可能会阻塞,如果当前线程执行完毕,get拿到返回值
        // 如果当前线程没有执行完毕 get就会一直阻塞
        System.out.println(futureTask.get());
    }
}

在这里我们可以看到,这里的callable是要放在FutureTask里面才能被线程获取到和Runnable是不一样的。其实Thread本身不提供获取结果的方法,就需要凭借FutureTsak来拿到结果。这里不提供获取结果的方法主要是为了解偶合,这样使Thread和任务这个概念分离开来不关心任务是啥样的是否有返回值。

Runnable的实现:

 public class Demo4 {
    //静态方法主线程和他线程可以共用
    public static int total = 0;
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            //Runnable是一个接口,它定义了一个契约,即实现它的类必须提供一个run方法
            @Override
            public void run() {
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum += i;
                }
                total = sum;
            }
        };
        Thread t1 = new Thread(runnable);
        t1.start();
        t1.join();
        System.out.println("total:" + total);
    }
}

在这里我们可以总结一下创建线程的写法:

1继承Thread(定义单独的类、使用匿名内部类)

2.实现Runnable(定义单独的类、使用匿名内部类)

3.lambda

4.实现Callable(定义单独的类、实现内部类)

5.线程池ThreadFactory

2.ReentrantLock

可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果保证线程安全。

2.1ReentrantLock的使用

用法如下:

  • lock():加锁,如果获取不到锁就死等
  • trylock(超时时间):加锁,如果获取不到锁·等待一定时间之后放弃加锁
  • unlock:解锁
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Demo2 {
    private static int count = 0;
    public static void main(String[] args) {
        ReentrantLock locker = new ReentrantLock();
       Thread t1 = new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   locker.lock();
                   try {
                       count++;
                   }finally {
                       locker.unlock();
                   }
               }
           }
       });
       Thread t2 = new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   locker.lock();
                   try {
                       count++;
                   }finally {
                       locker.unlock();
                   }

               }
           }
       });
       t1.start();
       t2.start();
       try{
           t1.join();
           t2.join();
       } catch (InterruptedException e) {
           throw new RuntimeException(e);
       }
        System.out.println(count);
    }
}

但是和synchronized相比synchronized效率更高。

2.2 synchronized和ReentrantLock之间的区别(重点)

1.synchronized是关键字(内部实现是JVM通过C++实现的),ReentrantLock标准库的类(java)

2.synchronized通过代码块控制加锁解锁不需要手动释放锁,ReentrantLock需要lock、unlock方法,需要注意unlock不被调用的问题建议搭配Finally使用

3.ReentrantLock除了提供lock、unlock之外,还提供了一个方法tryLock。这个方法不会阻塞还可以设置超时时间,加锁成功返回true,加锁失败返回false,调用者判定返回值决定接下来咋做。这个tryLock相当于追女神追不到就换一个,而普通的synchronized就是如果追不带女神就一直等,但是加了之后就不需要考虑解锁。

4.ReentrantLock提供了公平锁的实现,默认是非公平的。按照如下设置是公平锁

5.ReentrantLock搭配等待通知机制是Condition类,相比wait notify来说功能更强大一些。

 3.原子类

原子类内部用的是CAS实现,所以性能要比加锁实现i++高很多,原子类有以下几个

1.AtomicBoolean

2.AtomicInteger

3.AtomicIntegerArray

4.AtomicLong(数据统计,服务器收到多少请求)

5.AtomicReference

6.AtomicStampedReference

4.信号量Semaphore

信号量用来表示可用资源的个数,本质上就是一个计数器。能够协调多个进程之间的资源分配,也能协调多个线程之间的资源分配。信号量表示可用资源的个数,申请一个资源计数器就会-1,释放一个资源计数器就会+1,计数器为0,继续申请就会阻塞等待。其中申请(acquire方法)资源(p操作),release方法表示释放资源(V操作)。

代码实现:

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(4);
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
    }
}

 如果再进行一次P操作那么代码就会阻塞,开始的时候定义为4表示4个可用资源.打印的仍然是四次。

信号量还有一个特殊的情况,那就是初始值为1的时候。取值要么是1要么是0称为二元信号量,这样的信号量等价于锁。只有第一个线程才能进行P操作第二个线程要想进行P操作就需要等待,如果是普通N的信号量,就可以限制同时有多少个线程来执行某个逻辑。

关于信号量锁的实现:

import java.util.concurrent.Semaphore;
public class Demo4 {
    private static int count = 0;
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    try {
                        semaphore.acquire();
                        count++;
                        semaphore.release();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    try {
                        semaphore.acquire();
                        count++;
                        semaphore.release();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count);
    }
}


5.CountDownLatch

我们在使用多线程,经常把一个大的任务拆分成多个子任务,使用多个线程执行这些子任务从而提高程序的效率。如何衡量这些任务是否都完成了呢?这就需要使用到CountDownLatch 同时等待N个任务执行结束。

5.1CountDownLatch的使用

1.实例化时构造方法指定参数,描述拆成多少个任务传递参数就为多少就,这里假设为10

2.每个任务执完毕之后,都调用一次countDown方法。当调用次数为10说明任务全部完成

3.主线程调用await方法,阻塞等待所有任务执行完毕,await就会返回

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

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        //现在把整个任务拆成10个部分,每个部分视为一个子任务 
        // 可以把这10个任务丢到线程池中,让线程池执行
        // 当然也可以安排10个独立的线程执行
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 10; i++) {
            int id = i;
            executorService.submit(()->{
                System.out.println("子任务开始执行:" +id);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("子任务结束执行:"+id);
                countDownLatch.countDown();
            });
        }
        //这个方法阻塞等待所有的任务结束
        countDownLatch.await();
        System.out.println("所有任务执行完毕");
    }
}

结果: 线程池包含前台线程所以没有 return 0 ,要想return 0 在代码最后加上

executorService.shutdown();

  


原文地址:https://blog.csdn.net/2301_80785428/article/details/143726127

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!