11.从零开始学习Java-高级多线程

高级多线程

转载请标明原文地址,感谢,后续会持续更新《从零开始学习》系列,Mysql和ssm框架。
希望能为世界带来些微价值,peace!

线程池

  • 现有问题:
    • 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
    • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
  • 线程池:
    • 线程容器,可设定线程分配的数量上限。
    • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁。

线程池原理:

将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。

在这里插入图片描述

获取线程池:

常用的线程池接口和类(所在包java.util.concurrent):

  • Executor:线程池的顶级接口。
  • ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。//void shutdown()启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。//boolean isTerminated()如果所有任务在关闭后完成,则返回true。
  • Executors工厂类:通过此类可以获得一个线程池。
  • 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线 程池中线程的数量。
  • 通过newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,无上限。
 // 线程池(引用) ---> Executors工具类(工厂类)
//引用指向实现
public class TestThreadPool {

    public static void main(String[] args) {// 主线程

        // 线程池(引用) ---> Executors工具类(工厂类)
        ExecutorService es = Executors.newFixedThreadPool(3);
		//接口引用指向实现类对象
        Runnable task = new MyTask();

        es.submit(task);

        es.submit(task);

        es.submit(task);
    }
}

class MyTask implements Runnable {//任务
    @Override
    public void run() {
        for (int i = 1; i <= 1000; i++) {
            System.out.println(Thread.currentThread().getName() + " MyTask:"
                    + i);
        }
    }
}

Callable接口

//Thread不支持Callable接口。

public interface Callable<V>{ //有返回值,有泛型,有异常的声明
    public V call() throws Exception; 
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。

Future接口

//同步为串行,异步为并发。

//如果Callable需要返回值,需要用到Future接口。

//线程池执行Callable任务,Callable返回值由Future接收,Future中的get方法得到异步返回值。

//异步接收ExecutorService.submit()所返回的状态结果。

  • 概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了 call()的返回值。
  • 方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。
  • 需求:使用两个线程,并发计算150、51100的和,再进行汇总统计。
  • 思考:实际应用中,如何接收call方法的返回值?
  • 思考:什么是异步?什么是同步?
    • 同步:形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。(单条执行路径)
    • 异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。(多条执行路径)
    • //Future的get方法,就是先异步再同步。主线程等待拿到其他线程的返回值才执行完。
        ExecutorService es = Executors.newFixedThreadPool(3);

        Callable<Integer> task1 = new MyTask1();//接口引用指向子类对象
        Callable<Integer> task2 = new MyTask2();

        Future<Integer> f1 = es.submit(task1);//异步接收ExecutorService.submit()所返回的状态结果,当中包含了 call()的返回值。
        Future<Integer> f2 = es.submit(task2);

        Integer result1 = f1.get();//无限期等待//V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。等待拿到返回值,再同步。
        Integer result2 = f2.get();//无限期等待
public class TestCallable {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("程序开始");

        ExecutorService es = Executors.newFixedThreadPool(3);

        Callable<Integer> task1 = new MyTask1();//接口引用指向子类对象
        Callable<Integer> task2 = new MyTask2();

        Future<Integer> f1 = es.submit(task1);//异步接收ExecutorService.submit()所返回的状态结果,当中包含了 call()的返回值。
        Future<Integer> f2 = es.submit(task2);

        Integer result1 = f1.get();//无限期等待//V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。等待拿到返回值,再同步。
        Integer result2 = f2.get();//无限期等待

        System.out.println(result1 + result2);

    }
}


class MyTask1 implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(5000);
        Integer sum = 0;
        for (int i = 1; i <= 50; i++) {
            sum += i;
        }
        return sum;
    }
}

class MyTask2 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(5000);
        Integer sum = 0;
        for (int i = 51; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

Lock接口

  • JDK5加入,与synchronized比较,显式定义,结构更灵活。
  • 提供更多实用性方法,比synchronized方法更多。
  • 常用方法:
    • void lock() //获取锁,如锁被占用,则等待。
    • boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)。
    • void unlock() //释放锁。//需要释放锁//如果有异常需要写在fianlly中,必须要执行。
//synchronized
synchronized(this){
}
//开启锁   
locker.lock();
    //xxx
locker.unlock();
public class TestSynchronized {
    public static void main(String[] args) throws Exception {

        Account acc = new Account("1001", "123456", 2000D);

        Husband h = new Husband(acc);
        Wife w = new Wife(acc);

        // 三个线程
        Thread t1 = new Thread(h);
        Thread t2 = new Thread(w);

        t1.start();
        t2.start();

    }
}

class Husband implements Runnable {
    Account acc;

    public Husband(Account acc) {
        this.acc = acc;
    }

    @Override
    public void run() {
        this.acc.withdrawal("1001", "123456", 1200D);
    }

}

class Wife implements Runnable {
    Account acc;

    public Wife(Account acc) {
        this.acc = acc;
    }

    @Override
    public void run() {
        this.acc.withdrawal("1001", "123456", 1200D);
    }
}

class Account {
    //锁对象
    Lock locker = new ReentrantLock();

    String cardNo;
    String password;
    double balance;

    public Account(String cardNo, String password, double balance) {
        super();
        this.cardNo = cardNo;
        this.password = password;
        this.balance = balance;
    }

    public void withdrawal(String no, String pwd, double money) {
        //开启锁   synchronized(this){
        locker.lock();
        try{
            System.out.println("请稍后。。。");
            if (this.cardNo.equals(no) && this.password.equals(pwd)) {
                System.out.println("验证成功,请稍后。。");
                if (money < balance) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    balance -= money;
                    System.out.println("取款成功,当前余额为:" + balance);
                } else {
                    System.out.println("卡内余额不足!");
                }
            } else {
                System.out.println("卡号或密码错误!");
            }
        }finally{
            //释放锁  }
            locker.unlock();
        }
    }
}

重入锁

在这里插入图片描述

ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。//互斥:但凡有一个线程得到了锁,其他的线程都需要等待。//也需要获取锁,释放锁。

class MyList{
    private Lock locker = new ReentranLock();//创建锁对象
    //xxx
    public void add(){
        locker.lock();//显示开启锁
        locker.unlock();//显示释放锁//考虑可能出现异常,释放锁放到finally中
    }
}

读写锁

  • ReentrantReadWriteLock:
    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
    • 支持多次分配读锁,使多个读操作可以并发执行。
  • 互斥规则://我理解为内部互斥或不互斥,是一套锁。
    • 写-写:互斥,阻塞。
    • 读-写:互斥,读阻塞写、写阻塞读。
    • 读-读:不互斥、不阻塞。
    • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
ReentrantReadWriteLock
public class TestReentrantReadWriteLock {

    public static void main(String[] args) {
        final Student s = new Student();
        Callable<Object> writeTask = new Callable<Object>(){
            @Override
            public Object call() throws Exception {
                s.setValue(111);
                return null;
            }
        };

        Callable<Object> readTask = new Callable<Object>(){
            @Override
            public Object call() throws Exception {
                s.getValue();
                return null;
            }
        };


        ExecutorService es = Executors.newFixedThreadPool(20);

        //开始时间
        long start = System.currentTimeMillis();

        for (int i = 0; i < 2; i++) {
            es.submit(writeTask);
        }

        for (int i = 0; i < 18; i++) {
            es.submit(readTask);
        }


        //停止线程池(不在接受新的任务,将现有任务全部执行完毕)
        es.shutdown();
        while(true){
            System.out.println("结束了吗?");
            if(es.isTerminated()){
                break;
            }
        }

        //.....
        System.out.println(System.currentTimeMillis() - start);

    }

}

class Student{

    //ReentrantLock rLock = new ReentrantLock();
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock rl = rwl.readLock();
    ReentrantReadWriteLock.WriteLock wl = rwl.writeLock();

    int value;

    //读
    public int getValue() {

        rl.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return this.value;

        } finally{
            rl.unlock();
        }
    }

    //写
    public void setValue(int value) {
        wl.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.value = value;
        } finally{
            wl.unlock();
        }
    }
}

运行结果:互斥锁运行时间20034毫秒、读写锁运行时间3004毫秒。(2次写各占1秒、18次读共占1秒)。

创建方法:

旧:(线程)

如何创建一个任务对象?

Runnable task = new Runnable(){

	public void run(){}

};

如何运行任务?

Thread t = new Thread(task);

t.start();

如何保证线程安全?

synchroniced(this){}

public synchroniced void method(){}

新:(线程池)

如何创建一个任务对象?

Callable<String> task = new Callable<String>(){

	public String call() throws Exception{}

};

如何运行任务?

ExecutorService es = Executors.newFixedThreadPool(2);

Future<String> f  =  es.submit(task);//(新增)接受异步的返回值

String result = f.get();//以等待的形式接收泛型结果

如何保证线程安全?

Lock locker = new ReentrantLock();

locker.lock();

locker.unlock();

ReadWriteLock rwl = new ReentrantReadWriteLock();

ReadLock rl = rwl.readLock();

WriteLock wl = rwl.writeLock();

rl.lock();	rl.lock();

wl.lock();	wl.unlock();

线程安全的集合

Collection体系集合、以及线程安全集合。
在这里插入图片描述

注:绿色代表新增知识,下划线代表线程安全集合

Collections中的工具方法

//用的synchronized性能没有那么高效

  • Collections工具类中提供了多个可以获得线程安全集合的方法。
    • public static Collection synchronizedCollection(Collection c)
    • public static List synchronizedList(List list)
    • public static Set synchronizedSet(Set s)
    • public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
    • public static SortedSet synchronizedSortedSet(SortedSet s)
    • public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
  • JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。//内有$synchonizedCollection内部类实现

//由$synchonizedCollection内部类实现,内部类中有final实例变量mutex,先把对象传给mutex,(代理模式)然后mutex获得synchonized锁实现。

//源码实现:
final Collection<E> c;  // Backing Collection
final Object mutex;
//例如add方法
public boolean add(E e) {
      synchronized (mutex) {return c.add(e);}
   }
public class TestCollectionsForSyn {

    public static void main(String[] args) {
        //父类接口指向子类对象
        List<String> list = new ArrayList<String>();//0x11223344   ArrayList

        List<String> safeList = Collections.synchronizedList(list);//0x33445566    Collections$SynchronizedCollection

        safeList.add("A");

        safeList.add("A");

        safeList.add("A");

        safeList.add("A");
    }
}

class SafeCollection<E>{

    private Collection c = null;

    final Object o = new Object();

    public SafeCollection(Collection c){
        this.c = c;
    }

    public void add(E e){
        synchronized(o){
            c.add(e);
        }
    }
}

CopyOnWriteArrayList

//遍历操作(读)的数量大大超过可变操作(写)时。

//比vector高效,vector用的是synchonized锁,全都互斥。

//只有写~写操作才有互斥。

  • 线程安全的ArrayList,加强版读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
  • 使用方式与ArrayList无异。

//对底层数组进行一次新的复制,再添加新元素,最后替换引用。

//源码实现
public boolean add(E e) {
        final ReentrantLock lock = this.lock;//上锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);//复制一份数组,多一个数组元素,用来add
            newElements[len] = e;
            setArray(newElements);//写有锁,读无锁,读写之间不阻塞,优于读写锁。
            return true;
        } finally {
            lock.unlock();
        }
    }

CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现。
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。//new长度加1的新数组,和旧元素比较。
  • 如存在元素,则不添加(扔掉副本)。

//jdk1.6版本先创建一个newElements数组,元素个数len + 1,和原数组比较,如果原数组包括元素,则扔掉副本。如果不包括旧元素,就进行替换引用。

//jdk1.8,先使用indexOf判断如果原数组包括元素,则扔掉副本。如果不包含旧元素,返回-1,执行addIfAbsent(e, snapshot)。

//源码实现:
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
	private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

ConcurrentHashMap

//不为Map加锁,为Segment分段加锁。

//理想化最大并发16

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。
//源码实现
//Segment
    static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }
//Entry<K,V>
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
//
    public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) { 
        //xxx
    }

使用方法:

public class TestCopyOnWriteArrayList {

    public static void main(String[] args) {

        List<String> list = new CopyOnWriteArrayList<String>();//接口引用指向实现类对象,更容易更换实现
        //list.add("a");
        //add get set remove size toArray
        Set<String> set = new CopyOnWriteArraySet<String>();

        Map<String,String> map = new ConcurrentHashMap<String,String>();

//		map.put(key, value);

    }
}

Queue接口(队列)

  • Collection的子接口,表示队列FIFO(First In First Out)

  • 常用方法:

    • 抛出异常://Collection的方法
      • boolean add(E e) //顺序添加一个元素(到达上限后,再添加则会抛出异常)
      • E remove() //获得第一个元素并移除(如果队列没有元素时,则抛异常)
      • E element() //获得第一个元素但不移除(如果队列没有元素时,则抛异常)
    • 返回特殊值:推荐使用//当成队列来用
      • boolean offer(E e) //顺序添加一个元素 (到达上限后,再添加则会返回false)
      • E poll() //获得第一个元素并移除 (如果队列没有元素时,则返回null,成功则返回移除成功的对象)
      • E peek() //获得第一个元素但不移除 (如果队列没有元素时,则返回null)
    public class TestQueue {
        public static void main(String[] args){
            LinkedList queue = new LinkedList();//链表
            queue.offer("A");
            queue.offer("B");
            queue.offer("C");
    
            queue.add(0,"D");
            queue.add(1,"C");
            queue.add(2,"E");
            System.out.println(queue.poll());
            System.out.println(queue.poll());
            System.out.println(queue.poll());
        }
    }
    测试输出:
    D
    C
    E
    
public class TestQueue {
    public static void main(String[] args){
        Queue queue = new ConcurrentLinkedQueue();//队列
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}
测试输出:
A
B
C

ConcurrentLinkedQueue

//无锁。

//CAS比较交换算法:还有内存空间的交换也使用这一方法

  • 线程安全、可高效读写的队列,高并发下性能最好的队列。

  • 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)

  • V:要更新的变量、E:预期值、N:新值。//避免其他线程先进行操作。

    • //实现逻辑
      if(E==预期值){
          V.value=N;
      }else{
          //不更新
      }
      
  • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。

public class TestQueue {
    public static void main(String[] args){
        Queue queue1 = new ConcurrentLinkedQueue();
        queue1.offer("A");
        queue1.offer("B");
        queue1.offer("C");
        System.out.println(queue1.poll());
        System.out.println(queue1.poll());
        System.out.println(queue1.poll());
    }

BlockingQueue接口(阻塞队列)

在这里插入图片描述

  • Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
  • 方法:/解决/生产者、消费者问题
    • void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。
    • E take() //获取并移除此队列头部元素,如果没有可用元素,则等待。
  • 可用于解决生产生、消费者问题。

//还有一对超时方法。

阻塞队列(解决生产者、消费者)
  • ArrayBlockingQueue:
    • 数组结构实现,有界队列。(手工固定上限)
  • LinkedBlockingQueue:
    • 链表结构实现,无界队列。(默认上限Integer.MAX_VALUE)//int的最大整数取值。
public class xx {
    public static void main(String[] args){
        BlockingQueue<String> abq = new ArrayBlockingQueue<>(10);
        BlockingQueue<String> lbq = new LinkedBlockingQueue<>();
    }
}

总结

  • ExecutorService线程池接口、Executors工厂。
  • Callable线程任务、Future异步返回值。
  • Lock、ReentrantLock重入锁、ReentrantReadWriteLock读写锁。
  • CopyOnWriteArrayList线程安全的ArrayList。
  • CopyOnWriteArraySet线程安全的Set。
  • ConcurrentHashMap线程安全的HashMap。
  • ConcurrentLinkedQueue线程安全的Queue。
  • ArrayBlockingQueue线程安全的阻塞Queue。(生产者、消费者)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值