多线程-进阶2

 博主主页: 码农派大星.

    数据结构专栏:Java数据结构

 数据库专栏:MySQL数据库

JavaEE专栏:JavaEE

关注博主带你了解更多数据结构知识

1.CAS

1.1CAS全称:Compare and swap

比较内存和cpu寄存器中的内容,如果发现相同,就进行交换(交换的是内存和另一个寄存器的内容)

一个内存的数据 和 两个寄存器中的数据进行操作(寄存器1和寄存器2)

此处的"交换"实际上就是赋值

比较内存和寄存器1中的值,是否相等,如果不等,就无事发生,如果相等,就交换内存和寄存器2的值(此处只关心内存交换后的内容,不关心寄存器2交换后的内容)

因此CAS就能编写多线程代码,"无锁化编程"

CAS具体使用场景

1)基于CAS实现原子类 都是线程安全

import java.util.concurrent.atomic.AtomicInteger;
public class Main5 {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
             count.getAndIncrement();//count++
//           count.incrementAndGet();//++count
//           count.getAndDecrement();//count--
//           count.decrementAndGet();//--count
     //      count.getAndAdd(10);//count+= 10
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count.getAndIncrement();//count++
              /*  count.incrementAndGet();//++count
                count.getAndDecrement();//count--
                count.decrementAndGet();//--count*/
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t1.join();
        System.out.println(count.get());
    }
}

2.Callable接口

Callable 接口也是创建线程的一种方式。相当于把线性封装了一个返回值。

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

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 1000; i++) {
                    sum += i;

                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get());


    }
}

3.创建线程的方式:

1.直接继承Thead类

2.使用Runnable

3.使用Callable

4.使用lambda

5.使用线程池

4.ReentrantLock

4.1synchroniazed与ReentrantLock区别

1)大多是情况下使用synchronized,属于关键字(底层是通过JVM的c++代码实现的)

ReentrantLock则是标准库提供的类,通过Java代码实现的

2)synchronized通过代码块控制加锁解锁,ReentrantLock通过调用lock unlock方式来完成 unlock可能会遗漏

3)ReentrantLock 提供了tryLock这样的加锁风格

前面介绍的加锁,都是发现锁被别人占用了,就阻塞等待

tryLock在加锁失败的时候,不会阻塞,而是返回,通过返回值来反馈是加锁成功还是失败

4)1Reentrantlock提供了公平锁的实现

默认是非公平的,可以在构造方法中,传入参数,设定成公平的

5)Reentrantlock还提供了功能更强的功能"等待通知机制"

基于Condition类,能力要比wait notify更强一些.

import java.util.concurrent.locks.ReentrantLock;

public class Main2 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock locker = new ReentrantLock();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                locker.lock();
                count++;
                locker.unlock();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                locker.lock();
                count++;
                locker.unlock();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }



}

5.信号量Semaphore

import java.util.concurrent.Semaphore;

public class Main3 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore =new Semaphore(3);//可硬资源数,计数器初始值

        semaphore.acquire();
        System.out.println("申请一个资源");
        semaphore.acquire();
        System.out.println("申请一个资源");
        semaphore.acquire();
        System.out.println("申请一个资源");
        semaphore.release();
        System.out.println("释放一个资源");
        semaphore.acquire();
        System.out.println("申请一个资源");


    }


}

6.CountDownLatch

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

public class Main4 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //构造方法的数字,就是拆分出来的任务个数
        CountDownLatch countDownLatch =new CountDownLatch(20);

        for(int i = 0 ; i < 20;i++){
            int id = i;
            executorService.submit(()->{
                System.out.println("下载任务"+id+"开始执行");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("下载任务"+id+"结束任务");
                //完毕!!
                countDownLatch.countDown();
            });
        }

            //当countDownLatch收到20个"完成",所有的任务就完成了
 //await= allwait
            countDownLatch.await();

    }



}

借助CountDownLatch就能衡量出当前任务是否整体执行结束

7.线程安全的集合类

解决方案:

1)自己加锁

2)如果要使用ArraryList/LinkedList这样的结构

标准库中,提供了一个带锁的List

还可以使用CopyOnWrite集合类

写时拷贝的缺点:

1)无法应对,多个线程同时修改的情况

2)如果涉及到数据量很大,拷贝起来非常慢 

3)想多线程环境下使用队列

BlockingQueue

4)多线程环境下使用哈希表

Hashtable虽然是可选项

ConcurrentHashMap[推荐]

此处这个数据结构,相比于HashMa和Hashtable 来说,改进力度非常大的

1)优化了锁的粒度 

Hashtable的加锁,就是直接给put get 的方法加上synchronized,就是给this加锁

整个哈希表对象就是一把锁,任何一个针对这个哈希表的操作,都会触发锁竞争

ConcurrentHashMap则是给每个哈希表中的"链表" 进行加锁.(不是一把锁,而是多把锁)

上述设定方式,是可以保证线程安全的!!

其次可大大降低锁冲突的概率,只有同时进行两次修改,恰好在修改在同一链表元素上的时候,才会

触发锁竞争

2)ConcurrentHashMap引入了CAS原子操作.针对像修改了size这样的操作,直接借助CAS完成,才不会加锁

3)针对读操作,做了特殊处理 .上述的加锁,只是针对写操作,加锁

对于读操作,通过volatile以及一些精巧的代码实现,确保读操作,不会读到"修改一半的数据"

4)针对hash表的扩容,进行了特殊的优化

普通hash表的扩容,需要创建新的hash表,把元素搬过去

这一列操作,很有可能就在一次put中就完成了就会使这次put开销非常大,耗时非常长

ConcurrentHashMap进行了"化整为零" 不会在一次操作中,进行搬运所有数据而是一次搬运一部分,伺候每次操作,都会触发,一部分key的搬运,最终把所有的key都搬运完成

当新旧同时存在的时候

1)插入操作,直接插入到新空间中

2)查询/修改/删除/都需要同时查询旧的空间和新空间

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值