高级多线程笔记

高级多线程

线程池概念

问题:

线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。

频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。

线程池:

线程容器,可设定线程分配的数量上限。将预先创建的线程对象存入池中,并重用线程池中的线程对象。避免频繁的创建和销毁。

在这里插入图片描述

创建线程池

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

Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。
Executors工具类:通过此类可以获得一个线程池。
通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限

Executor:线程池的根接口,execute()
ExecutorService:包含管理线程池的一些方法,submit shutdown
ThreadPoolExecutor
ScheduledThreadPoolExecutor
Excutors:创建像城池工具类
(1)创建固定线程个数线程池
(2)创建缓存线程池,由任务的多少决定
(3)创建单线程池
(4)创建调度线程池 调度:周期,定时执行

package Thread3;

/**Executor:线程池的根接口,execute()
ExecutorService:包含管理线程池的一些方法,submit shutdown
ThreadPoolExecutor
ScheduledThreadPoolExecutor
Excutors:创建像城池工具类
 (1)创建固定线程个数线程池
 (2)创建缓存线程池,由任务的多少决定
 (3)创建单线程池
 (4)创建调度线程池 调度:周期,定时执行
*/

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

public class Demo01 {
    public static void main(String[] args) {
        //1创建固定线程个数的线程池
        //ExecutorService es = Executors.newFixedThreadPool(4);
        //1.2创建缓存线程池,线程个数由任务个数决定
        ExecutorService es = Executors.newCachedThreadPool();
        //2创建任务
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            @Override
            public void run() {
                while(true){
                    if (ticket<=0){
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                    ticket--;
                }
            }
        };
        //3提交任务
        for (int i = 0;i<4;i++){
            es.submit(runnable);
        }
        //4关闭线程
        es.shutdown();//等待所有任务执行完毕然后关闭线程池
    }
}

Callable接口

public interface Vallable{

public V call() throws Exception;

}

JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。

Callable具有泛型返回值、可以声明异常。

演示Callable接口的使用
Callable和Runnable接口的区别
(1)Callable接口中call方法有返回值,Runnable接口中run方法没有返回值

(2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常

package Thread3;

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

public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //功能需求:使用Callable实现1-100和
        Callable<Integer> callable = new Callable<Integer>(){

            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum = 0;
                for (int i = 0;i<=100;i++){
                    sum = sum + i;
                    Thread.sleep(100);
                }
                return sum;
            }
        };
        //2把Callable对象转成可执行任务
        FutureTask<Integer> task = new FutureTask<>(callable);
        //3创建线程
        Thread thread = new Thread(task);
        //4创建线程
        thread.start();
        //5获取结果(等待call执行完毕,才会返回)
        Integer sum = task.get();
        System.out.println("结果是:"+sum);
    }
}

Callable结合线程池使用

package Thread3;
//使用线程池计算1-100的和

import java.util.concurrent.*;

public class Demo03 {
    public static void main(String[] args) throws Exception {
        //1创建线程池
        ExecutorService es = Executors.newFixedThreadPool(1);
        //2提交任务 Future:表示将要执行完成任务的结果
        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum = 0;
                for (int i = 0;i<=100;i++){
                    sum+=i;
                }
                return sum;
            }
        });
        //3获取任务结果
        System.out.println(future.get());
        //4关闭线程池
        es.shutdown();
    }
}

Future接口

Future:表示将要完成任务的结果。

需求:使用两个线程,并发计算150,51100的和,在进行汇总统计

Future接口的使用

package Thread3;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

//需求:使用两个线程,并发计算1~50,51~100的和,在进行汇总统计
public class Demo04 {
    public static void main(String[] args) throws Exception{
        //1创建线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //2提交任务
        Future<Integer>future1=es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 1;i <= 50;i++){
                    sum+=i;
                }
                System.out.println("1-50计算完毕");
                return sum;
            }
        });
        Future<Integer>future2=es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 51; i <= 100; i++) {
                    sum += i;
                }
                System.out.println("51-100计算完毕");
                return sum;
            }
        });
        //3获取结果
        int sum = future1.get()+future2.get();
        System.out.println("结果是:"+sum);
        //4关闭线程池
        es.shutdown();
    }
}

Future接口

表示ExecutorService.submit()所返回的状态结果,就是call()的返回值

方法: V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)

思考:什么是异步?什么是同步?

线程的同步

同步:

在这里插入图片描述

异步:

在这里插入图片描述

Lock接口

JDK5加入,与synchronized比较,显示定义,结构更灵活。

提供更多实用性方法,功能更强大、性能更优越。

常用方法:

void lock() //获取锁,如锁被占用,则等待。

boolean tryLock()//尝试获取锁(成功返回true。失败返回false,不阻塞)

void unlock()//释放锁

重入锁

在这里插入图片描述

package Thread3;
//ReentrantLock的使用

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyList {
    private Lock lock = new ReentrantLock();
    private String[] str = {"A","B","","",""};
    private int count = 2;

    public void add (String value){//传参,传入value
        lock.lock();
        try {
            str[count] = value;//count为下标2,传入value
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName()+"添加了"+value);
        }finally{
            lock.unlock();
        }
    }
    public String[] getStr(){
        return str;
    }
}
package Thread3;

import java.util.Arrays;

public class TestMyList {
    public static void main(String[] args) throws Exception{
        MyList list = new MyList();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                list.add("hello");
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                list.add("world");
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(list.getStr()));
    }
}

卖票

package Thread3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable{
    private int ticket = 10000;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                 if(ticket<=0){
                     break;
                 }
                System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                 ticket--;
            }finally{
                lock.unlock();
            }
        }
    }
}
package Thread3;

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

public class TestTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        ExecutorService es = Executors.newFixedThreadPool(4);
        for (int i = 0; i<4;i++){
            es.submit(ticket);
        }
        es.shutdown();
    }
}

读写锁

ReentrantReadWriteLock:

一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。。支持多次分配读锁,使

多个读操作可以并发执行。

互斥规则:

写-写:互斥,阻塞。

读-写:互斥,读阻塞写、写阻塞读。

读-读:不互斥、不阻塞。

在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

读写锁使用

package Thread3;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;

public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock rr1 = new ReentrantReadWriteLock();
    //读取读锁
    private ReadLock readLock = rr1.readLock();
    //获取写锁
    private ReadLock writeLock = rr1.readLock();
    private String value;
    //写入
    public void setValue(String value){
        writeLock.lock();
        try{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("写入:"+value);
            this.value = value;
        }finally{
            writeLock.unlock();
        }
    }
    //读取
    public String getValue(){
        //使用读锁上锁
        readLock.lock();
        try{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取:"+this.value);
            return this.value;
        }finally{
            readLock.unlock();
        }
    }

}
package Thread3;

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

public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteDemo readWriteDemo = new ReadWriteDemo();
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(20);
        Runnable write = new Runnable() {
            @Override
            public void run() {
                readWriteDemo.setValue("张三:"+new Random().nextInt(100));
            }
        };
        Runnable read = new Runnable() {
            @Override
            public void run() {
                readWriteDemo.getValue();
            }
        };

        long start = System.currentTimeMillis();
        for (int i = 0;i < 18;i++){
            es.submit(read);
        }
        //分配两个写的任务
        for (int i = 0; i<2;i++){
            es.submit(write);
        }
        es.shutdown();
        while(!es.isTerminated()){
            //空转
        }
        long end = System.currentTimeMillis();
        System.out.println("用时:"+(end-start));
    }
}

常见线程安全集合

在这里插入图片描述

问题演示并用Collections方法解决

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实现。

问题:

Exception in thread “Thread-6” Exception in thread “Thread-3” java.util.ConcurrentModificationException

package T3;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Demo01 {
    public static void main(String[] args) {
        //1创建arrylist
        //ArrayList<String> arrayList = new ArrayList<>();
        //1.1使用Collevtions中的线程安全方法转成线程安全集合
        //List<String> synList = Collections.synchronizedList(arrayList);
        //1.2使用CopyOnWriteArrayList
        CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
        //2创建线程
        for (int i = 0; i< 20 ; i++){
            int temp = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0;j<10;j++){
                        arrayList.add(Thread.currentThread().getName()+"==="+temp+"==="+j);
                        System.out.println(arrayList.toString());
                    }
                }
            }).start();
        }
    }
}

CopyOnWriteArrayList

线程安全的ArrayList,加强版的读写分离。

写有锁,读无锁,读写之间不阻塞,优于读写锁。

写入时,先copy一个容器副本、再添加新元素,最后替换引用使用方式与ArrayList无异。

package T3;
//使用多线程操作CopyOnWriteArrayList

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo02 {
    public static void main(String[] args) {
        //1创建集合
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        //2使用多线程集合
        ExecutorService es = Executors.newFixedThreadPool(5);
        //3提交任务
        for (int i = 0;i<5;i++){
            es.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0;j<10;j++){
                        list.add(Thread.currentThread().getName()+"..."+new Random().nextInt(1000));
                    }
                }
            });
        }
        //4关闭线程池
        es.shutdown();
        while(!es.isTerminated()){}
        //打印结果
        System.out.println("元素个数:"+list.size());
        for (String string : list){
            System.out.println(string);
        }
    }
}

CopyOnWriteArraySet

线程安全的Set,底层使用CopyOnWriteArrayList实现。

唯一不同在于,使用addIfAbsent()添加元素,会遍历数组,

如存在元素,则不添加(扔掉副本)。

package T3;


import java.util.concurrent.CopyOnWriteArraySet;

public class Demo03 {
    public static void main(String[] args) {
        //1创建集合
        CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();//有序
        //2添加元素
        set.add("pingguo");
        set.add("huawei");
        set.add("xiaomi");
        set.add("lianxiang");
        set.add("pingguo");
        //3打印
        System.out.println("元素个数:"+set.size());
        System.out.println(set.toString());
    }
}

Queue接口(队列)

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

常用方法:

抛出异常:

boolean add(E e)//顺序添加一个元素(到达上限后,再添加则会抛出异常)

E removeK)//获得第一个元素并移除(如果队列没有元素时,则抛异常)

Eelement()//获得第一个元素但不移除(如果队列没有元素时,则抛异常)

返回特殊值:推荐使用

boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会返回false)

E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null)

E peek()//获得第一个元素但不移除(如果队列没有元素时,则返回null)

package T3;

import java.util.LinkedList;
import java.util.Queue;

public class Demo04 {
    public static void main(String[] args) {
        //1创建队列
        Queue<String> queue = new LinkedList<>();//(线程不安全)
        //2入队
        queue.offer("苹果");
        queue.offer("橘子");
        queue.offer("葡萄");
        queue.offer("西瓜");
        queue.offer("榴莲");
        //3出队
        System.out.println(queue.peek());
        System.out.println("===========");
        int size = queue.size();
        for (int i =0;i<size;i++){
            System.out.println(queue.poll());
        }
        System.out.println("出队完毕:"+queue.size());

    }
}

ConcurrentLinkedQueue

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

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

V:要更新的变量、E:预期值、N:新值。

只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。

package T3;

import java.util.concurrent.ConcurrentLinkedQueue;

public class Demo05 {
    public static void main(String[] args) throws Exception{
        //1创建安全队列
        ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
        //2入队操作
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1;i<=5;i++){
                    queue.offer(i);
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 6;i<=10;i++){
                    queue.offer(i);
                }
            }
        });
        //3启动线程
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("=====出队=====");

        //4出队操作
        int size = queue.size();
        for (int i = 0;i<size;i++){
            System.out.println(queue.poll());
        }

    }
}

BlockingQueue接口(阻塞队列)

Queue的子接口,阻塞的队列

Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。

方法:
void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。

E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。

可用于解决生产生、消费者问题。

实现类:

ArrayBlockingQueue:
数组结构实现,有界队列。(手工固定上限)

LinkedB1ockingQueue:
链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)

BlockingQueue的使用

package T3;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo06 {
    public static void main(String[] args) throws Exception{
        //创建一个有界数列,添加数据
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
        //添加元素
        queue.put("aaa");
        queue.put("bbb");
        queue.put("ccc");
        queue.put("ddd");
        queue.put("eee");
        //删除元素
        queue.take();
        System.out.println("已经添加5个元素");
        queue.put("xyz");
        System.out.println("已经添加了6个元素");
        System.out.println(queue.toString());
        

    }
}

生产者消费者案例

package T3;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo07 {
    public static void main(String[] args) {
        //1创建队列
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(6);
        //2创建两个线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<30;i++){
                    try {
                        queue.put(i);
                        System.out.println(Thread.currentThread().getName()+"生产了"+i+"号面包");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        },"晨晨");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<30;i++){
                    try {
                        Integer num = queue.take();
                        System.out.println(Thread.currentThread().getName()+"消费了"+i+"号面包");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"冰冰");
        //启动线程
        t1.start();
        t2.start();
    }
}

ConcurrentHashMap

初始容量默认为16段(Segment),使用分段锁设计。

不对整个Map加锁,而是为每个Segment加锁。

当多个对象存入同一个Segment时,才需要互斥。

最理想状态为16个对象分别存入16个Segment,并行数量16使用方式与HashMap无异。

ConcurrentHashMap的使用

package T3;

import java.util.concurrent.ConcurrentHashMap;

public class Demo08 {
    public static void main(String[] args) {
        //1创建集合
        ConcurrentHashMap<String,String> hashMap = new ConcurrentHashMap<String,String>();
        //2使用多线程添加数据
        for (int i=0;i<5;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int k = 0;k<10;k++){
                        hashMap.put(Thread.currentThread().getName()+"--"+k,k+"");
                        System.out.println(hashMap);
                    }
                }
            }).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值