多线程(4)

常量池:

字符串常量池:常量池就是jvm用来存储字符串常量的地方,可以用来减少重复的字符串对象,节省内存。

运行时常量池:是类加载后,存储在方法区的各种常量,一般有final常量,类名,方法名

线程池:

就是提前创建好一批线程放在池子中,用的时候拿出来,用完了再放回去,避免频繁的创建或者销毁线程

线程池的好处,避免重复创建线程,避免创建太多的线程导致系统卡死,控制线程数量,生命周期,来不及的任务可以放在队列中进行排队

为什么我们认为创建线程的开销比直接从线程池中取线程的开销大呢?

创建线程并不是简单的new Thread() ,背后有大量的内核操作

当你new一个线程,thread.start()启动后,jvm和操作系统要做一堆事,jvm要为线程分配内存,也就是线程栈,每一个线程都有一个线程栈 ,而且创建线程一定要系统调用,操作系统要从用户态切换到内核的巨大开销,创建线程就相当于你有一份活需要干,找一个员工,为员工准备工位,电脑,资料,而从线程池中拿线程就相当于,员工有了,电脑和资料也准备好了,直接把工作给员工就行了。

线程池的属性

工厂模式

就是不需要自己new对象,将new对象的过程统一交给工厂进行管理

工厂模式的核心就是通过静态方法,把构造对象new的过程,各种属性的初始化,统一进行了封装,提供不同的静态方法,对应各种不同情况

class Point{

}
//使用方法工厂
class PointFactory{
    public static Point setPointByLR(int l,int r){
        Point point=new Point();
        return point;
    }
    public static Point setPointBySE(int s,int e){
        Point point=new Point();
        return point;
    }
}
public class Demo29 {
    public static void main(String[] args) {
        Point point=PointFactory.setPointByLR(100,200);
        Point point1=PointFactory.setPointBySE(200,300);
    }
}

线程池中的参数就是一种典型的工厂模式

线程池中的拒绝策略

详细讲解一下

线程池已经没有能力处理新的任务,就会拒绝策略

触发的条件一般是:当线程池最大线程数满了,工作队列(阻塞队列)也满了,此时再提交任务就会触发。

JDK内置的4种拒绝策略

AbortPolicy(默认):直接会抛出异常。

CallerRunsPolicy:让提交任务的线程自己执行任务,会是同步执行,当线程池繁忙,会让main线程处理任务,但是会导致main线程阻塞。

DiscardPolicy:直接抛弃任务,不处理异常

DiscardOldestPolicy:丢弃最早的任务,然后把当前的任务加入队列

线程接收任务的流程:

Executors

是java提供的一个专门创建线程池的工厂类,用来快速的创建线程池

但是生产环境中不推荐使用 Executors 创建线程池,通常推荐 ThreadPoolExecutor + 自定义参数。

主要原因有两点:1.队列无限制,底层使用了 LinkedBlockingQueue() 无界队列

2.线程数无限制,最大的线程数是Integer.MAX_VALUE

public class Demo30 {
    public static void main(String[] args) {
        //        创建一个无数量限制的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();//没有数量限制
//        for (int i = 0; i < 100; i++) {
//            int m=i;
//            executorService.submit(()->{
//                System.out.println(" nihao ...."+m+Thread.currentThread().getName());
//            });
//        }

        ExecutorService executorService1 = Executors.newFixedThreadPool(4);
//        使用这大小为4的线程池
        for (int i = 0; i < 100; i++) {
            int m=i;
            executorService1.submit(()->{
                System.out.println(" nihao ...."+m+Thread.currentThread().getName());
            });
        }

    }
}

模拟实现一个固定个数的线程池

//实现一个固定个数的线程池
class MyThreadPool{
    private static BlockingQueue<Runnable> queue=null;

//    创建线程池中线程的个数
    public MyThreadPool(int n){
//        任务队列的容量
        queue=new ArrayBlockingQueue<>(1000);

//        创建n个线程
        for (int i = 0; i < n; i++) {
            Thread thread=new Thread(()->{
                try {
//                    线程进行完成任务
                    while (true){
                        Runnable task=queue.take();//获得任务
                        task.run();//执行任务
//                        queue.take().run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

//    将任务存放到队列中
    public void submit(Runnable task) throws InterruptedException {
        queue.put(task);
    }
}

public class Demo31 {
    public static void main(String[] args) throws InterruptedException {
//        创建一个大小为10的线程池
        MyThreadPool myThreadPool=new MyThreadPool(10);
        for (int i = 0; i < 100; i++) {
            int id=i;
            myThreadPool.submit(()->{
                System.out.println(".."+id+Thread.currentThread().getName());
            });
        }
    }
}

定时器的使用

相当于闹钟,当时间一到,执行一些逻辑

还是实现了Runnable类,重写了run方法

public class Demo32 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timer=3000");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timer=2000");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timer=1000");
            }
        },1000);
    }
}

使用多线程模拟实现一个定时器

创建一个MyTimerTask类表示一个任务

定时器中,能够管理多个任务,需要将任务管理起来->使用了优先级队列

创建了schedule,将任务添加到队列中

额外创建一个线程,负责执行队列中的任务


class MyTimerTask implements Comparable<MyTimerTask> {

    private Runnable task;

    private long time;

    public MyTimerTask(Runnable task,long time){
        this.time=time;
        this.task=task;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time-o.time);
    }
    public long getTime(){
        return time;
    }

    public void run(){
        task.run();
    }
}
//创建自己的计时器
class MyTimer{
    private PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
    private final Object locker=new Object();

    public void schedule(Runnable task,long time){
        synchronized (locker){
            //        设置定时任务
            MyTimerTask myTimerTask=new MyTimerTask(task,System.currentTimeMillis()+time);
//        将定时任务存储到优先级队列中
            queue.offer(myTimerTask);
            locker.notify();
        }
    }

    public MyTimer(){
        Thread t=new Thread(()->{
            try {
                while (true){
                    synchronized (locker){
                        while (queue.isEmpty()){
                            locker.wait();
                        }
//            从队列中取出任务
                        MyTimerTask task = queue.peek();
                        if(System.currentTimeMillis()<task.getTime()){
                            //如果当前的任务时间比系统的时间大,说明还未到执行时间
                            locker.wait(task.getTime()-System.currentTimeMillis());
                        }else{//执行的时间到了
                            task.run();//执行任务
                            queue.poll();//从队列中取出该任务
                        }
                    }
                }
            }catch (InterruptedException e){
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

}
public class Demo33 {
    public static void main(String[] args) {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        },1000);

    }
}

悲观锁vs乐观锁

悲观锁:我认为别人一定会修改,所以我每次操作前都会上锁

排他性强,安全性高,并发度低

乐观锁:我认为别人一般不会修改,所以不加锁,等提交的时候检查一下冲突就行了

并发度高,性能低,失败了需要重试,常用方式CAS

偏向锁vs轻量级锁vs重量级锁

偏向锁:适用于只有一个线程操作一个对象,减少CAS的开销,偏向锁的过程,进行synchronized,并不是真的加锁,只是进行标记一下,非常轻量,相比于加锁解锁的效率,效率会高很多。

轻量级锁:适用于多个线程交替执行但不真正的并发,使用CAS加锁,如果成功的话,则不阻塞,适用于短时间的锁竞争

重量级锁:线程会挂起阻塞,把cpu资源让出来,由操作系统切换到其他的线程进行运行,未来当锁释放的时候,操作系统再将其唤醒,性能的成本最高,只有CAS失败多次,竞争激烈时才升级到最高级

锁升级的全流程:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁,只能进行锁的升级,不能进行降级

挂起等待锁vs自旋锁

当线程发生阻塞时,直接挂起,释放cpu资源,由操作系统切换到其他的线程,等其它线程释放,再开始执行,适用于重量级锁

当线程发生阻塞,进行死等,不挂起,而实再cpu上循环等待,适用于轻量级锁的CAS失败后会自旋几次

普通的互斥锁vs读写锁

读写锁允许多个锁并发度但是写的时候独占

读-读并发,读-写互斥,写-写互斥

普通的互斥锁无论读写都互斥,导致再读多写少的场景下会严重的浪费吞吐量,读写锁通过允许并发度来提高并发量和吞吐量

java中的读写锁


//读写锁
public class Demo37 {
    private static Map<String,String> map=new HashMap<>();
    private static final ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    private static final Lock readLock=lock.readLock();
    private static final Lock writeLock=lock.writeLock();


//    读操作
    public String get(String key){
        readLock.lock();
        try {
            String value= map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取的value:"+value);
            return value;
        }finally {
            readLock.unlock();
        }
    }

//    写操作
    public String put(String key,String value){
        writeLock.lock();

        try {
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+value);
        }finally {
            writeLock.unlock();
        }
        return "添加元素成功";
    }

    public static void main(String[] args) {
        Demo37 demo37=new Demo37();
        Thread t=new Thread(()->{
            demo37.put("1232","223");
        });
        t.start();
        for (int i = 0; i < 5; i++) {
            Thread t2=new Thread(()->{
                demo37.get("1232");
            });
            t2.start();
        }
    }

}

可重入锁和不可重入锁

一个线程在已经获得锁的情况下,可以再次获得一把锁,不会发生死锁

核心要点:锁要记录当前是哪个线程拿到这把锁

使用计数器,记录当前的加锁了多少次,加锁+1,解锁-1

公平锁和非公平锁

锁的默认情况下,操作系统对其的调度是随机的,符合非公平锁的概念

想要实现非公平锁,需要记录一下各个线程获取锁的顺序,按照先来后到的顺便来执行。

锁粗化

线程多次synchronized,被jvm优化成一把更大的锁,jvm的优化行为,目的是减少频繁的加锁/解锁开销。

锁的粒度:锁范围的大小

锁粗化:把一些连续的细粒度的多次加锁,粗话成一个粗粒度的加锁

锁消除:编译器自动优化掉没必要的锁

CAS


CAS:是并发编程中实现无锁(乐观锁)的核心机制,就是判断值有没有变,再决定要不要更新的机制

CAS就是cpu上的一条指令

CAS的执行流程:

判断当前内存的值是否和A(寄存器中的值)相等,如果一致,就把它和B(寄存器中的另一个值)的值进行交换,如果不一致,则不进行更新

原子类就是基于CAS实现的

public class Demo38 {
    private static AtomicInteger count =new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i = 0; i < 10; i++) {
                count.getAndIncrement(); //每次增加一次
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 10; i++) {
                count.getAndIncrement(); //每次增加一次
            }
        });

        t.start();
        t2.start();
        t.join();
        t2.join();
        System.out.println(count.get());
    }
}

CAS的ABA问题

线程t1读取一个变量为A,t1像用CAS将这个变量更新为B,但是t1执行CAS之前,t2将变量从A改为B,再从B改为A,现在的值虽然还是A,栈内的顺序和数据已经发生了变化,t1的CAS会认为没有变化,直接将A更新为B

Callable接口

也是JUC中的一些组件,可以封装成一个被线程执行的任务,跟Runnable类似

FutureTask:它可以封装成一个Callable或者Runnable任务,并且可以异步执行任务并获取结果

public class Demo39 {
    public static void main(String[] args) {
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result=0;
                for (int i = 0; i < 100; i++) {
                    result+=i;

                }
                System.out.println(result);
                return result;
            }
        };

        FutureTask<Integer> task=new FutureTask<>(callable);
        Thread t=new Thread(task);
        t.start();
    }
}

Synchronized和ReentrantLock之间的区别

1.Synchronized是关键字(内部是jvm通过c++来实现的),ReentrantLock是java标准库的类

2.Synchronized是通过代码块控制加锁解锁,ReentrantLock通过lock和unlock来加锁解锁

3.ReentrantLock实现了公平锁

ReentrantLock reentrantLock=new ReentrantLock(true);//开启公平锁

4.ReentrantLock提供了tryLock方法,尝试获取锁,如果锁被占用无法获取则返回false,获得锁则返回true,还可以在tryLock中传参,色湖之超时时间,在这个时间范围内没获得锁,则返回false

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

公平锁的测试


public class Demo40 {
    private static int count = 0;

    public static void main(String[] args) {
        ReentrantLock locker = new ReentrantLock(true);

        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();
                    count++;
                    locker.unlock();
                }
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + count);
    }
}

信号量(Semaphore)

控制同时访问某个资源的线程数量

可以理解成一个计数器(描述资源的可用个数),获得一个资源要+1,释放一个资源要-1

Semaphore semaphore=new Semaphore(2);

每次只允许1个线程获得许可

public class Demo41 {
    private static int count =0;
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(1);
        Thread t1=new Thread(()->{
            for (int i = 0; i < 500; i++) {
                try {
                    semaphore.acquire();
                    count++;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread t2=new Thread(()->{
            for (int i = 0; i < 500; i++) {
                try {
                    semaphore.acquire();
                    count++;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

CountDownLatch

一个线程等待多个线程完成任务后再继续执行的工具

线程执行downLatch.await();阻塞等待,线程执行downLatch.countDown();计数器的数量-1,直到计数器的数量减为0,await()被唤醒继续执行

public class Demo42 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch downLatch=new CountDownLatch(10);//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);
                }
                downLatch.countDown();
                System.out.println("子任务执行完毕"+id);

            });
        }
        downLatch.await();
        System.out.println("所有的任务执行完毕");
    }
}

多线程环境使⽤ArrayList

ArrayList不是线程安全的

多线程替代ArrayList的方法

Collections.synchronizedList

List<String> list = Collections.synchronizedList(new ArrayList<>());

使用 CopyOnWriteArrayList(推荐的方法)

也是编程中的一种常见的思想方法

List<String> list = new CopyOnWriteArrayList<>();

写的时候拷贝整个数组,读的时候无锁

多线程环境下使用哈希表

HashMap是线程不安全的

Hashtable线程安全的,它在所有涉及到多线程操作的都加上了 synchronized 关键字来锁住整个 table,这就意味着所有的线程都在竞争一把锁,在多线程的环境下,它是安全的,但是无疑是效率低下的,

ConcurrentHashMap(推荐)

  • 高并发性能极好

  • 分段锁(JDK7)或 CAS + synchronized(JDK8)

  • 读操作几乎无锁

  • 写操作局部锁,不阻塞整个表

建议看:一文彻底弄懂ConcurrentHashMap-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值