Java学习之走进多线程

本文深入探讨Java的多线程编程,从进程与线程的区别开始,详细讲解线程的创建、状态、同步和安全问题,以及线程池、Lock接口和线程安全集合的使用,帮助开发者掌握Java并发编程的核心概念和技术。
摘要由CSDN通过智能技术生成

进程和线程

1.什么进程

  • 正在运行的程序,是系统进行资源分配的基本单位
  • 目前操作系统都是支持多线程,可以同时执行多个进程,通过进程id(pid)区分
  • 单核CPU在同一个时刻,只能运行一个进程;宏观并行,微观串行

2.什么线程

  • 线程,又称为轻量级进程,进程中的一条执行路径,也是CPU的基本调度单位,一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程

3.进程和线程的区别

  • 1.进程是操作系统资源分配的而基本单位,而线程是cpu的基本调度单位
  • 2.一个程序运行后至少有一个进程
  • 3.一个进程可以包含多个线程,但是至少需要有个线程,否则这个进程是没有意义的
  • 4.进程间不能共享数据段地址,但同进程的线程之间可以

线程的组成和特点

1.线程的组成

  • 任何一个线程都具有基本的组成部分:
    • cpu时间片:操作系统(OS)会为每个线程分配执行时间
    • 运行数据
      • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
      • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
    • 线程的逻辑代码

2.线程的特点

  • 1.线程抢占式执行
    • 效率高
    • 可防止单一线程长时间独占cpu
  • 2.在单核cpu中,宏观同时执行,微观串行就是按顺序执行

线程的创建

1.继承Tread类(重写run方法)


/**
 * 线程类
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println("子线程-------"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread = new MyThread();
        //启动线程,不能调用run方法
        myThread.start();

        //主线程执行
        for (int i = 0; i <100 ; i++) {
            System.out.println("主线程=========="+i);
        }
    }
}

在这里插入图片描述

1.1获取和修改线程名称

  • 获取线程id和名称
    • 1.在Thread的子类中调用this.getId()或this.getName()
    • 2.使用Thread.currentThread().getId()和Thread.currentThread().getName()
  • 修改线程名称
    • 1.调用线程对象的setName()方法
    • 2.使用线程子类的构造方法赋值
  • 第一种方式获取id和名称,局限性:要继承Thread类
/**
 * 线程类
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
        }
    }
}

  • 第二种方式获取id和名称
/**
 * 线程类
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
//            第一种方法获取id和名称
//            System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
//            第二种方法获取id和名称
            System.out.println("线程id:"+Thread.currentThread().getId()+"线程名字:0"+Thread.currentThread().getName()+"子线程-----------"+i);
        }
    }
}

  • 修改线程名称
/**
 * 线程类
 */
public class MyThread extends Thread{
    public MyThread(){
    }
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
//            第一种方法获取id和名称
//            System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
//            第二种方法获取id和名称
            System.out.println("线程id:"+Thread.currentThread().getId()+"线程名字:0"+Thread.currentThread().getName()+"子线程-----------"+i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread = new MyThread("我的子线程1");
        //启动线程,不能调用run方法
        myThread.start();
        MyThread myThread1 = new MyThread();
        myThread1.setName("我的子线程2");
        myThread1.start();
        //主线程执行
        for (int i = 0; i <1000 ; i++) {
            System.out.println("主线程=========="+i);
        }
    }
}

在这里插入图片描述

  • 四个窗口卖票案例
public class TestWin {
    public static void main(String[] args) {
        TicketWin w1 = new TicketWin("窗口1");
        w1.start();
        TicketWin w2 = new TicketWin("窗口2");
        w2.start();
        TicketWin w3 = new TicketWin("窗口3");
        w3.start();
        TicketWin w4 = new TicketWin("窗口4");
        w4.start();
    }
}
public class TicketWin extends Thread{
    public TicketWin(){
    }
    public TicketWin(String name){
        super(name);
    }
    private int ticket=100;
    @Override
    public void run() {
        while (true) {
            if (ticket <= 0) {
                break;
            }
            System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
            ticket--;
        }
    }
}

在这里插入图片描述

2.实现Runnable接口

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+".........."+i);
        }
    }
}
public class TestRunnable {
    public static void main(String[] args) {
        //1.创建MyRunnable对象,表示线程要执行的功能
        MyRunnable myRunnable = new MyRunnable();
        //2.创建线程对象
        Thread thread = new Thread(myRunnable, "我的线程1");
        //3.启动
        thread.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("main.........."+i);
        }
    }
}

在这里插入图片描述

  • 使用匿名内部类创建
public class TestRunnable {
    public static void main(String[] args) {
//        //1.创建MyRunnable对象,表示线程要执行的功能
//        MyRunnable myRunnable = new MyRunnable();
//        //2.创建线程对象
//        Thread thread = new Thread(myRunnable, "我的线程1");
//        //3.启动
//        thread.start();
//
//        for (int i = 0; i < 50; i++) {
//            System.out.println("main.........."+i);
//        }
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <100 ; i++) {
                    System.out.println(Thread.currentThread().getName()+".........."+i);
                }
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        thread.start();
        for (int i = 0; i < 50; i++) {
            System.out.println("main.........."+i);
        }
    }
}

3.Callable接口

  • jdk5.0加入,与Runnable接口相似,实现之后代表一个线程任务
  • Callable具有泛型返回值,要声明异常

public class CallableTest {
    public static void main(String[] args) throws Exception {
        //创建Callable对象
        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+=i;
                    Thread.sleep(100);
                }
                return sum;
            }
        };
        //把Callable对象转换成可执行对象
        FutureTask<Integer> task=new FutureTask<>(callable);
        //创建线程
        Thread thread=new Thread(task);
        //启动线程
        thread.start();
        //获取结果(等待call方法执行完毕才返回)
        Integer sum = task.get();
        System.out.println("结果是"+sum);
    }
}

在这里插入图片描述

线程的状态

-
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.阻塞常用方法

  • 休眠
    • public static void sleep(long millis)
    • 当前线程主动休眠 millis毫秒
public class SleepThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"......."+i);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class TestSleep {
    public static void main(String[] args) {
        SleepThread s1 = new SleepThread();
        SleepThread s2 = new SleepThread();
        s1.start();
        s2.start();
    }
}

在这里插入图片描述

  • 放弃
    • public static void yield()
    • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
public class YieldThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <100; i++) {
            System.out.println(Thread.currentThread().getName()+"......."+i);
            yield();
        }
    }
}
public class TestYield {
    public static void main(String[] args) {
        YieldThread thread1 = new YieldThread();
        YieldThread thread2 = new YieldThread();
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

  • 加入
    • public final void join()
    • 允许其他线程加入到当前线程中
    • 加入当前线程main,并阻塞当前线程,直到加入线程执行完毕
public class JoinThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 50 ; i++) {
            System.out.println(Thread.currentThread().getName()+".........."+i);
            try {
                sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class TestJoin {
    public static void main(String[] args) {
        JoinThread j1 = new JoinThread();
        j1.start();
        try {
            j1.join();//加入当前线程main,并阻塞当前线程,直到加入线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"........"+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

  • 优先级
    • 线程对象。setPriority()
    • 线程优先级为1-10,默认为5,优先级越高,表示获得cpu机会越多
public class TestPriority {
    public static void main(String[] args) {
        PriorityThread p1 = new PriorityThread();
        PriorityThread p2 = new PriorityThread();
        PriorityThread p3 = new PriorityThread();
        p1.setPriority(1);
        p1.start();
        p2.start();
        p3.start();
    }
}
public class PriorityThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <50 ; i++) {
            System.out.println(Thread.currentThread().getName()+"==========="+i);
        }
    }
}

在这里插入图片描述

  • 守护线程
    • 线程对象.setDaemon(true);设置为守护线程
    • 线程有两类:用户线程(前台线程),守护线程(后台线程)
    • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
    • 垃圾回收器线程属于守护线程
public class TestDeamon {
    public static void main(String[] args) {
        DeamonThread d1 = new DeamonThread();
        d1.setDaemon(true);
        d1.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println("主线程----------"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DeamonThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <50 ; i++) {
            System.out.println(currentThread().getName()+"-----------"+i);
            try {
                sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

线程的安全

在这里插入图片描述

  • 多线程安全问题
    • 当多线程并发访问临界资源时,如果破坏了原子操作,可能会造成数据不一致
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
    • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可被打乱或缺省
public class ThreadSafe {
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
   
            String[] strings = new String[5];
            Runnable runnableA = new Runnable() {
                @Override
                public void run() {
                    strings[index] = "hello";
                    index++;

                }
            };
            Runnable runnableB = new Runnable() {
                @Override
                public void run() {
                    strings[index] = "world";
                    index++;

                }
            };
            Thread a = new Thread(runnableA, "A");
            Thread b = new Thread(runnableB, "B");
            a.start();
            b.start();
            a.join();
            b.join();
            System.out.println(Arrays.toString(strings));
        }
}

1.同步代码块

  • sychronized(临界资源对象){//对临界资源对象加锁}
    //代码(原子操作)
    }
    注:每个对象都有一个互斥锁标记,用来分配给线程的
    只有拥有对象互斥锁标记的线程,才进入对该对象加锁的同步代码块
    线程退出同步代码块时,会释放相应的互斥锁标记
public class ThreadSafe {
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {

            String[] s = new String[5];

            Runnable runnableA = new Runnable() {
                @Override
                public void run() {
                    synchronized (s){
                        s[index] = "hello";
                        index++;
                    }
                }
            };
            Runnable runnableB = new Runnable() {
                @Override
                public void run() {
                    synchronized (s){
                        s[index] = "world";
                        index++;
                    }

                }
            };
            Thread a = new Thread(runnableA, "A");
            Thread b = new Thread(runnableB, "B");
            a.start();
            b.start();
            a.join();
            b.join();
            System.out.println(Arrays.toString(s));
        }
}

在这里插入图片描述

2.同步方法

synchronized返回值类型 方法名称(参数列表){//对当前对象(this)加锁
//代码(原子操作)
}

public class ThreadSafe {
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {

            String[] s = new String[5];

         /*   Runnable runnableA = new Runnable() {
                @Override
                public void run() {
                    synchronized (s){
                        s[index] = "hello";
                        index++;
                    }
                }
            };*/
        Runnable runnableA = new Runnable() {
            @Override
            public synchronized void run() {//锁 this 
                
                    s[index] = "hello";
                    index++;
                }
        };
            Runnable runnableB = new Runnable() {
                @Override
                public synchronized static void run() {//静态方法 锁Ticket.class
                  
                        s[index] = "world";
                        index++;

                }
            };
            Thread a = new Thread(runnableA, "A");
            Thread b = new Thread(runnableB, "B");
            a.start();
            b.start();
            a.join();
            b.join();
            System.out.println(Arrays.toString(s));
        }
}

在这里插入图片描述

3.同步规则

  • 注意:
    • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁的标记
    • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用
  • 已知jdk中线程安全的类
    • StringBuffer
    • Vector
    • Hashtable
    • 以上类中公开方法,均为synchronized修饰的同步方法

4.经典问题

  • 死锁
    • 当第一个线程拥有A对象标记,并等待B对象锁标记,同时第二个线程拥有B对象锁的标记,并等待A对象标记时,产生死锁
    • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能会产生死锁

5.线程通信

  • 等待
    • public final void wait()
    • public final void wait(long timeout)
    • 必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记,同时此线程阻塞在o的等待队列中,释放锁,进入等待队列
  • 通知
    • public final void notify()
    • public final void notifyAll()

public class BankCard {
    //余额
    private double money;
    //标记
    private boolean flag=false;//true 代表有钱可以取钱,false没钱可以存钱
    //存钱
    public synchronized void save(double m){//this
        if (flag){
            try {
                this.wait();//进入等待队列,释放锁和cpu
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money+=m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+" 金额是"+money);
        //修改标记
        flag=true;
        //唤醒取钱线程
        this.notify();
    }
    //取钱
    public synchronized void take(double m){
        if (!flag){
            try {
                this.wait();//进入等待队列,释放锁和cpu
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money-=m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+"  金额是"+money);
        //修改标记
        flag=false;
        //唤醒存钱线程
        this.notify();
    }

}

public class SubMoney implements Runnable{
    private BankCard bankCard;

    public SubMoney(BankCard bankCard) {
        this.bankCard = bankCard;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bankCard.take(1000);
        }
    }
}

public class AddMoney implements Runnable{
    private BankCard bankCard;

    public AddMoney(BankCard bandCard){
        this.bankCard=bandCard;
    }
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            bankCard.save(1000);
        }
    }
}

在这里插入图片描述

线程池

1.线程池的概念

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

2.线程池原理

在这里插入图片描述

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

3.创建线程池

  • 常用的线程池接口和类(所在包java.util.concurrent)
    • Executor:线程池的顶级接口
    • ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
    • Executors工厂类:通过此类可以获得一个线程池
    • 通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量
    • 通过newCachedThreadPool()获取动态数量的线程池,如不够则创建新的,没有上限
/**
 * 线程池创建
 * Executor 线程池的根接口 executor()
 * ExecutorService 包含管理线程池的一些方法
 *      ThreadPoolExecutor
 *          ScheduledThreadPoolExecutor
 * Executors 创建线程的工具类
 *      1)创建固定线程的个数的线程池
 *      2)创建缓存线程池,由任务的多少所决定
 *      3)创建单线程池
 *      4)创建调度线程池 调度:周期,定时执行
 */
public class Demo01 {
    public static void main(String[] args) {
        //1)创建固定线程的个数的线程池
        ExecutorService es = Executors.newFixedThreadPool(4);
        //1.1动态创建线程池.由任务个数所决定
        Executors.newCachedThreadPool();
              //1.2创建单线程线程池
        Executors.newSingleThreadExecutor();
        //1.3创建调度线程池
        Executors.newScheduledThreadPool();
        //2)创建任务
        Runnable runnable = new Runnable() {
            private Integer 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();
    }
}


在这里插入图片描述

3.1Callable结合线程池使用


public class Demo02 {
    public static void main(String[] args) throws Exception{
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(1);
        //提交任务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;
                    Thread.sleep(10);
                }
                return sum;
            }
        });
        //获取任务结果
        System.out.println(future.get());
        //关闭线程池
        es.shutdown();

    }
}

在这里插入图片描述

4.Future接口

  • Future:表示将要完成任务的结果
  • 表示ExecutorService.submit()所返回的状态结果,就是call()的返回值
  • 方法:V.get()以阻塞形式等待Future中的异步处理结果也就是call()的返回值
  • 需求:使用两个线程,并发计算1-50,51-100的和,在进行汇总统计
public class Demo03 {
    public static void main(String[] args) throws Exception{
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //提交任务
        Future<Integer> future1 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum=0;
                for (int i = 1; i <=50 ; i++) {
                    sum+=i;
                }
                return sum;
            }
        });
        //获取任务结果
        Integer sum1 = future1.get();
        //提交任务
        Future<Integer> future2 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum=0;
                for (int i = 51; i <=100 ; i++) {
                    sum+=i;
                }
                return sum;
            }
        });
        //获取任务结果
        Integer sum2 = future2.get();
        int sum = sum1 + sum2;
        System.out.println("两个线程的总和是"+sum);
        //关闭线程池
        es.shutdown();
    }
}

在这里插入图片描述

线程的同步和异步

1.线程的同步

  • 同步:
    • 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能够继续
      在这里插入图片描述

2.线程的异步

  • 异步:
    • 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知后立刻返回。二者竞争时间片,并发执行
      在这里插入图片描述

Lock接口

  • jdk5,与synchronized比较,显示定义,结构灵活
  • 提供更多实用性方法,功能强大,性能更加优越
  • 常用方法:
    • void lock() //获取锁,如锁被占用,则等待
    • boolean tryLock() //尝试获取锁(成功则返回true,否则false,不阻塞)
    • void unlock()//释放锁

1.重入锁

  • ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
public class TestMylist {
    public static void main(String[] args) throws Exception {
        MyList myList=new MyList();
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                myList.add("hello");
            }
        };
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                myList.add("world");
            }
        };
        Thread t1= new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(myList.getStr()));
    }
}

public class MyList {
    //创建锁
    private Lock lock=new ReentrantLock();
    private String[] str={"A","B","","",""};
    private int count=2;
    public void add(String value){
        lock.lock();
        try {
            str[count]=value;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
        } finally {
            lock.unlock();
        }
    }

    public String[] getStr(){
        return str;
    }
}

在这里插入图片描述

2.读写锁

  • ReentrantReadWriteLock
    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁
    • 支持多次分配读锁,使多个读操作可以并发执行
  • 互斥规则
    • 写-写 :互斥,阻塞
    • 读-写:互斥,读阻塞写,写阻塞读
    • 读-读:不互斥,不阻塞
    • 在读操作远远高于写操作的环境中,可保障线程安全的情况下,提高虚拟性效率
public class TestReadWrite {
    public static void main(String[] args) {
        ReadWriteDemo readWriteDemo = new ReadWriteDemo();
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(20);

        Long start=System.currentTimeMillis();

        for (int i = 0; i <2 ; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    readWriteDemo.setValue("张三:"+new Random().nextInt(100));
                }
            });
        }

        for (int i = 0; i <18 ; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    readWriteDemo.getValue();
                }
            });
        }


        //关闭线程池
        es.shutdown();
        while (!es.isTerminated()){

        }
        long end=System.currentTimeMillis();
        System.out.println("用时:"+(end-start));

    }
}
public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock rr1=new ReentrantReadWriteLock();
    //获取读锁
    private ReentrantReadWriteLock.ReadLock readLock=rr1.readLock();
    //获取写锁
    private ReentrantReadWriteLock.WriteLock writeLock= rr1.writeLock();
    private String value;

    public String getValue() {
        readLock.lock();
        try{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取"+value);
            return value;
        }finally {
            readLock.unlock();
        }

    }

    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();
        }

    }
}

在这里插入图片描述

线程安全的集合

  • Collection体系集合中除Vector以外的线程安全集合
    在这里插入图片描述
  • Map安全集合体系在这里插入图片描述

1.CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版的读写分离
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁
  • 写入时,先copy一份容器副本,再添加元素,最后替换引用
  • 使用方式与ArrayList无异
public class Demo01 {
    public static void main(String[] args) {
        //创建CopyOnWriteArrayList对象
        CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(5);
        //提交任务
        for (int i = 0; i <5; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        strings.add(Thread.currentThread().getName()+"======"+new Random().nextInt(1000));
                    }

                }
            });
        }
        //关闭线程池
        es.shutdown();
        while (!es.isTerminated()){}
        System.out.println("元素个数为:"+strings.size());
        for (String string : strings) {
            System.out.println(string);
        }
    }
}

在这里插入图片描述

2.CopyWriteArraySet

  • 线程安全的set,底层使用CopyWriteArrayList实现,有序
  • 唯一不同之处,add添加元素是调用addIfabsent()添加元素,会遍历数组
  • 如存在元素,则不添加(扔掉副本)
public class Demo02 {
    public static void main(String[] args) {
        //创建CopyWriteArraySet对象
        CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet();
        set.add("北京");
        set.add("上海");
        set.add("广州");
        set.add("北京");//添加不了
        System.out.println(set.toString());
    }
}

在这里插入图片描述

3.Queue接口(队列)

  • Collection的子接口,表示队列FIFO,先进先出
  • 常用方法:
    • 抛出异常:
      • boolean add(E e)//顺序添加一个元素(达到上限后,再添加则会抛出异常)
      • E remove() //获得第一个元素并移除(如果队列没有元素,则抛出异常)
      • E element()//获得第一个元素但不移除(如果队列没有元素,则抛出异常)
    • 返回特殊值:推荐使用
      • boolean offer(E e)//顺序添加一个元素(达到上限后,再添加则会返回false)
      • E poll() //获得第一个元素并移除(如果队列没有元素,则返回null)
      • E peek()//获得第一个元素但不移除(如果队列没有元素,则返回null)
/**
 * Queue接口使用,LinkedList线程不安全
 */
public class Demo03 {
    public static void main(String[] args) {
        //创建队列
        Queue<String> queue=new LinkedList<>();
        //入队
        queue.offer("苹果");
        queue.offer("香蕉");
        queue.offer("梨子");
        queue.offer("葡萄");
        queue.offer("苹果");
        System.out.println(queue.peek());
        System.out.println(queue.size());
        //出队
        for (int i = 0; i < 5; i++) {
            queue.poll();
        }
        System.out.println(queue.size());
    }
}

在这里插入图片描述

3.1ConcurrentLinkedQueue

  • Queue的实现类,线程安全,可高效读写的队列,高并发下最好的队列
  • 无锁,CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
  • V:要更新的变量,E:预期值,N:新值
  • 只有V==E时才V=N;否则表示已更新过,取消当前的操作
public class Demo04 {
    public static void main(String[] args) throws Exception{
        //创建安全队列
        ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
        //入队操作
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    queue.offer(i);
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 5; i < 10; i++) {
                    queue.offer(i);
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

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

在这里插入图片描述

3.2BlockingQueque接口(阻塞队列)

  • Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
  • 方法
    • void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待
    • E take()//获取并移除此队列头部元素,如果没有可用元素,则等待
3.2.1ArrayBlockingQueue
  • 数组结构实现,有界队列(手工固定上限)
public class Demo05 {
    public static void main(String[] args) throws Exception{
        //创建ArrayBlockingQueue
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
        //添加元素
        queue.put("aaa");
        queue.put("bbb");
        queue.put("ccc");
        queue.put("ddd");
        queue.put("eee");
        System.out.println(queue.size());
        //删除元素
        queue.take();
        queue.put("fff");
        System.out.println(queue.size());
    }
}

在这里插入图片描述

3.2.2LinkedBlockingQueue
  • 链表结构实现,有界队列(默认上限Integer.Max_VALUE)

4.ConcurrentHashMap

  • jdk1.7
    • 初始容量默认为16段(Segment),使用分段式锁设计
    • 不对整个Map加锁,而是为每个Segment加锁
    • 当多个对象存入同一个Segment时,才需互斥
    • 最理想状态为16对象分别存入16g个Segement,并行数量16
    • 使用方式与HashMap无异
  • jdk1.8
    • 使用CAS无锁算法
public class Demo06 {
    public static void main(String[] args) {
        //创建集合
        ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
        //使用多线程添加数据
        for (int i = 0; i <5 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        hashMap.put(Thread.currentThread().getName()+"--"+j,j+"");
                        System.out.println(hashMap);
                    }
                }
            }).start();
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值