Java 多线程详解

一、Java多线程

1. 进程与线程

1)串行与并行

  • 串行
    • 指多个任务执行时,各个任务按照顺序执行,一个任务完成之后才能进行下一个任务
  • 并行
    • 指多个任务执行时,可以同步进行执行,并行的前提是异步

2)并发与并行

  • 并发
    • 并发指的是执行多个任务时,只有一个CPU,通过CPU调度,将一个时间段分成多个时间片,在某一个时间片内只执行某一任务,但是多个任务之间切换的快,看起来就像是多个任务在同时执行,实际某一时间点上只执行单个任务
  • 并行
    • 并行指的是有多个CPU,多个任务由多个CPU同时处理,是真正的同时

3)程序与进程与线程

  • 程序
    • 程序指的是为了完成某一个需求而编写的代码的集合,是静态的,看得见的
  • 进程
    • 程序运行起来就是进程,是动态的,是系统资源分配的单位
  • 线程
    • 比进程的概念要小,通常一个进程地下可以有多个线程。一个进程中至少有一个线程。线程用来完成具体的细节,线程是CPU调度和执行的单位
  • 注意
    • 真正的多线程指的是有多个CPU即多核同时运行,模拟的多线程只能在同一个时间节点运行一个线程,不过切换速度快

4)线程状态

  • 初始
    • 创建一个线程对象时,该线程就进入了初始状态
  • 运行
    • 分为就绪和运行中两个状态,当线程对象调用start()方法时,进入就绪状态,当该线程获得CPU资源时,从就绪状态进入运行中状态
  • 阻塞
    • 给线程加上锁之后,该线程进入阻塞状态,同时锁内的资源只有等该线程使用完毕之后,其他线程才能访问
  • 等待
    • 线程进入等待状态,需要等其他线程做出特定的动作,通知或中断
  • 超时等待
    • 调用sleep()方法,在等待指定的时间后,线程自动回复到运行状态
  • 终止
    • 表示线程已经执行完毕,分为人为中断线程与线程自然结束

2. 线程对象

1)线程的三种定义方式

  • 实现Runnable接口

    • 自定义一个Runnable接口的非抽象子类,实现Runnable接口的run()方法,该方法用于定义该线程需要完成的动作。将该自定义类类的对象作为参数传递给Thread类的构造方法,调用Thread类的对象的start()方法,启动线程
    • 代码
    public class ThreadCreate implements Runnable {//使用Runnable接口创建线程
    
        public static void main(String[] args) {
    
            ThreadCreate threadCreate = new ThreadCreate();//创建自定义类的对象
            //将自定义类的对象作为参数传给Thread类的构造方法,得到Thread类的对象
            Thread thread = new Thread(threadCreate);
            //开启子线程
            thread.start();
        }
    
        @Override
        public void run() {//子线程中要运行的代码
            System.out.println("我是子线程");
        }
    }
  • 继承Thread类

    • Thread其实自己也实现了Runnable接口,但是Thread类的run()方法需要子类去重写,所以可以使用自定义的继承自Thread类的类创建线程
    • 代码
    public class ThreadCreate2 extends Thread {//使用Thread类的子类创建线程
    
        public static void main(String[] args) {
            ThreadCreate2 threadCreate2 = new ThreadCreate2();//创建子线程对象
            threadCreate2.start();//开启子线程
        }
    
        @Override
        public void run() {//子线程中要运行的代码
            System.out.println("我是子线程");
        }
    }
  • 实现Callable接口

    • 自定义Callable接口的实现类,使用该类的子类对象创建线程

2)线程的命名

  • 如果想要区分不同的线程,那么就需要对象的名称加以区分,就要对线程加以命名,一般线程的命名建议在创建线程对象时或在启动前进行,不建议对已经启动的线程进行命名,也不建议对不同的线程设置相同的名字

  • 实现Runnable接口的线程命名

    • setName()方法
    • Thread类的构造方法
    • 代码
    public class ThreadCreate implements Runnable {//使用Runnable接口创建线程,并改名
    
        public static void main(String[] args) {
    
            ThreadCreate threadCreate = new ThreadCreate();//创建自定义类的对象
            //创建子线程对象1,在构造方法中对子线程1进行命名
            Thread thread1 = new Thread(threadCreate, "子线程1");
            //开启子线程1
            thread1.start();
    
            //创建子线程对象2
            Thread thread2 = new Thread(threadCreate);
            //使用setName()方法对子线程2进行命名
            thread2.setName("子线程2");
            //开启子线程1
            thread2.start();
        }
    
        @Override
        public void run() {//子线程中要运行的代码,作用是打印当前线程的名称
            System.out.println("我是:" + Thread.currentThread().getName());
        }
    }
  • Thread类子类的线程的重命名

    • 使用setName()方法
    • 代码
    public class ThreadCreate2 extends Thread {//使用Thread类的子类创建线程,并改名
    
        public ThreadCreate2(String name) {//该构造方法用来对子线程进行命名,内部调用了setName()方法
            this.setName(name);
        }
    
        public static void main(String[] args) {
            ThreadCreate2 threadCreate2 = new ThreadCreate2("子线程");//创建子线程对象并命名
            threadCreate2.start();//开启子线程
        }
    
        @Override
        public void run() {//子线程中要运行的代码
            System.out.println("我是" + Thread.currentThread().getName());
        }
    }

3)线程优先级

  • 优先级的范围是1~10,默认的优先级是5,最高是10,最低是1
  • 线程的优先级并不代表真正的执行顺序,只是一种概率,优先级高的线程优先执行的概率比优先级低的线程高
  • setPriorty()
    • 用来设置线程的优先级
  • getPriorty()
    • 获取线程的优先级
  • 代码示例
public class ThreadPriority implements Runnable {//线程优先级的设置与获取

    @Override
    public void run() {//输出打印线程基本信息
        System.out.println("线程名称:" +
                Thread.currentThread().getName() +
                "\t线程优先级:" +
                Thread.currentThread().getPriority());
    }

    public static void main(String[] args) {

        ThreadPriority threadPriority = new ThreadPriority();
        Thread thread1 = new Thread(threadPriority, "线程1");//创建线程2,重命名并设置优先级
        thread1.setPriority(1);

        Thread thread2 = new Thread(threadPriority, "线程2");//创建线程2,重命名并设置优先级
        thread2.setPriority(10);

        thread1.start();//启动线程
        thread2.start();
    }
}

4)线程休眠与线程礼让

  • Thread.sleep()线程休眠方法
    • 使得当前的线程在指定的时间段内暂停执行
  • Thread.yield()线程礼让方法
    • 已经获取到CPU资源的线程将当次CPU资源放弃,然后系统重新调度CPU资源
    • 当次放弃CPU资源的线程在下一次分配资源时可能还会获得
  • 代码示例
public class ThreadYield implements Runnable {//线程休眠与线程礼让示例

    @Override
    public void run() {
        try {
            Thread.sleep(3000);//线程休眠3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.yield();//线程礼让
        System.out.println("线程礼让与线程休眠示例");
    }

    public static void main(String[] args) {
        ThreadYield threadYield = new ThreadYield();
        Thread thread = new Thread(threadYield);
        thread.start();
    }
}

5)线程联合

  • join()线程联合/插队方法
    • 使得调用join的线程优先执行,优先插队执行
    • 哪个线程要插队,哪个线程就调用
    • 等调用的线程执行完了,当前线程才有可能执行
  • 代码示例
public class ThreadJoin implements Runnable {//线程联合示例

    @Override
    public void run() {//打印子线程名称
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadJoin threadJoin = new ThreadJoin();//创建子线程
        Thread thread = new Thread(threadJoin, "线程");
        thread.start();//启动子线程
        
        try {
            thread.join();//让子线程先插队
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印主线程名称
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

6)线程停止

  • 线程停止(结束)
    • 结束掉当前线程,结束以后当前线程不再运行
    • 有三种结束方法,但两种已经过时,现在时候剩下的一种
    • 使用退出标识,让线程正常的退出,即让run方法正常运行完
  • 代码示例
public class ThreadStop implements Runnable {//线程停止示例
    private static boolean flag = true;//子线程的停止标识

    @Override
    public void run() {
        while (flag) {//当调用stop()方法时子线程结束
            System.out.println("线程名称:" + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        //创建并启动子线程
        ThreadStop threadStop = new ThreadStop();
        Thread thread = new Thread(threadStop, "子线程");
        thread.start();

        int i = 0;
        while (i++ < 10) {
            System.out.println("线程名称:" + Thread.currentThread().getName());
        }
        ThreadStop.stop();//调用stop()方法结束子线程
    }

    private static void stop() {//改变停止标识,调用此方法结束子线程
        flag = false;
    }
}

7)守护线程

  • Java线程分为两种,用户线程与守护线程
  • 用户线程完成一些具体的业务需求,可以理解为工作线程
  • 守护线程负责完成后台的一些系统性服务,比如垃圾回收线程
  • 用户线程是一定会运行完的

8)使用多线程模拟龟兔赛跑

public class TurtleRabbitRace implements Runnable {//使用Java多线程模拟龟兔赛跑比赛
    private String winner = null;

    public static void main(String[] args) {
        //创建并运行线程
        TurtleRabbitRace race = new TurtleRabbitRace();
        Thread thread1 = new Thread(race, "兔子");
        Thread thread2 = new Thread(race, "乌龟");
        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        int i = 0;
        while (i++ < 100) {//循环跑100米
            if (getWinner()) {//如果已经有获胜者,另一个人就不再跑了,比赛结束
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");//打印每个选手的比赛进度
            if (i > 99) {//当有选手跑到100米时,诞生冠军,并且打印冠军的信息
                winner = Thread.currentThread().getName();
                System.out.println(winner + "是胜利者");
            }

            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {//如果比赛选手是兔子,让兔子每10米休息1毫秒
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 用来得到比赛是否结束
     */
    private boolean getWinner() {
        if (winner != null) {//如果有冠军返回true
            return true;
        } else {
            return false;//如果没有冠军返回false
        }
    }
}

3. 线程同步

1)线程冲突

  • 当不同的线程对公共资源中的同一数据进行操作时,就会可能发生数据的异常
  • 比如售票处有两个售票员,此时还有最后的几张票,售票员1刚将游客的门票钱收了,准备给游客取票时突然身体不舒服,去了卫生间。此时售票员2看到还有几张票,就将这几张票卖给另外几个游客了。一会售票员1回来一看票没了,此时票数就发生了冲突,多收钱了。
  • 代码示例
public class ThreadConflict implements Runnable {//线程冲突示例

    //票数,总共100张
    private static int ticket = 100;

    @Override
    public void run() {

        while (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + (--ticket));
        }
    }

    public static void main(String[] args) {
        //创建4个子进程,模拟4个售票员
        ThreadConflict conflict = new ThreadConflict();
        Thread thread1 = new Thread(conflict, "售票员1");
        Thread thread2 = new Thread(conflict, "售票员2");
        Thread thread3 = new Thread(conflict, "售票员3");
        Thread thread4 = new Thread(conflict, "售票员4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

2)同步语句与同步方法

  • 为了解决线程冲突问题,可以使用synchronized关键字,将可能冲突的数据锁起来,等待当前线程使用完锁起来的数据之后,其他线程才能使用
  • 同步语句
    • synchronized (“锁名称”) {可能会出现冲突的代码;}
  • 同步方法
    • 只是在普通方法的声明上加上synchronized关键字
    • 相比较于同步语句,同步方法可以实现代码的重用,在业务逻辑较复杂时,同步方法更方便
  • 代码示例
public class SynchronizedStatements implements Runnable {//同步语句解决线程冲突
    //票数
    private static int ticket = 100;

    @Override
    public void run() {

        while (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized ("") {
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + (--ticket));
            }
        }
    }

    public static void main(String[] args) {
        /**
         * 创建子线程对象
         */
        SynchronizedStatements conflict = new SynchronizedStatements();
        Thread thread1 = new Thread(conflict, "售票员1");
        Thread thread2 = new Thread(conflict, "售票员2");
        Thread thread3 = new Thread(conflict, "售票员3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

public class SynchronizedMethods implements Runnable {//同步方法解决线程冲突
    //票数
    private static int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();//调用同步方法
        }
    }

    /**
     * 同步方法,将可能会发生并发的代码锁起来
     */
    private synchronized void sellTicket() {
        if (ticket <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + (--ticket));
    }

    public static void main(String[] args) {
        /**
         * 创建子线程对象
         */
        SynchronizedMethods conflict = new SynchronizedMethods();
        Thread thread1 = new Thread(conflict, "售票员1");
        Thread thread2 = new Thread(conflict, "售票员2");
        Thread thread3 = new Thread(conflict, "售票员3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

4. 线程死锁

1)线程死锁

  • 当使用同步语句或同步方法解决线程冲突问题时,在数据之间,可能存在彼此依赖的关系,由于其中某一对互相锁住,导致彼此之间谁也没办法相互前进,出现互相卡着的情况
  • 两个线程之间相互持有对方所需要的锁,相互等待,永久处于堵塞状态的情况
  • 代码举例
public class ThreadDeadLock {//线程死锁示例

    public static void main(String[] args) {
        Thread thread1 = new Thread(new DeadLockA(), "线程1");
        Thread thread2 = new Thread(new DeadLockB(), "线程2");

        thread1.start();
        thread2.start();
    }
}

class DeadLockA implements Runnable {//A锁需要B锁来解锁
    @Override
    public void run() {
        synchronized ("A") {
            System.out.println("线程1获得了A锁");
            synchronized ("B") {
                System.out.println("线程1获得了A锁和B锁");
            }
        }
    }
}

class DeadLockB implements Runnable {//B锁需要A锁来解锁
    @Override
    public void run() {
        synchronized ("B") {
            System.out.println("线程2获得了B锁");
            synchronized ("A") {
                System.out.println("线程2获得了B锁和A锁");
            }
        }
    }
}

5. 线程协调

1)wait()方法和notify()方法

  • wait()方法
    • 该线程先让其他线程使用锁内的数据,自己先等一下
  • notify()或notifyAll()方法
    • 提醒等待的线程已经使用完毕该锁
  • 代码示例
public class ThreadCoordination {//线程协调示例

    public static void main(String[] args) {
        Thread thread1 = new Thread(new DeadLockAA(), "线程1");
        Thread thread2 = new Thread(new DeadLockBB(), "线程2");

        thread1.start();
        thread2.start();
    }
}

class DeadLockAA implements Runnable {
    @Override
    public void run() {
        synchronized ("A") {
            System.out.println("线程1获得了A锁");
            try {
                "A".wait();//让该线程的"A"锁等一下,让其他线程先使用
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized ("B") {
                System.out.println("线程1获得了A锁和B锁");
            }
        }
    }
}

class DeadLockBB implements Runnable {
    @Override
    public void run() {
        synchronized ("B") {
            System.out.println("线程2获得了B锁");
            synchronized ("A") {
                System.out.println("线程2获得了B锁和A锁");
                "A".notify();//提醒线程1线程2已经用完了"A"锁内的内容
            }
        }
    }
}

2)懒汉单例模式

  • 懒汉单例模式
    • 使用对象时再去查找这个对象,如果有就用,没有再创建该对象使用
    • 线程不安全,需要加同步锁,效率低
  • 饿汉单例模式
    • 加载类时就先创建好对象实例等待调用
    • 天生线程安全,类加载的时候初始化一次对象,效率较高
  • 代码示例
public class ThreadSingleton {//懒汉单例模式示例

    public static void main(String[] args) {

        //创建100个线程
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(new HelloRunnable(), "线程 " + i);
            thread.start();
        }
    }
}

class Lazybones {
    public Lazybones() {
        System.out.println("对象实例化了" + Thread.currentThread().getName());
    }

    private static Lazybones lazybones = null;

    public static Lazybones getLazybones() {
        //加锁是为了解决多线程不安全问题,只有当当前线程获得对象之后,下一线程才能获得对象
        synchronized ("") {
            if (lazybones == null) {
                lazybones = new Lazybones();
            }
        }
        return lazybones;
    }
}

class HelloRunnable implements Runnable {
    @Override
    public void run() {
        //调用方法得到懒汉单例模式类对象
        Lazybones.getLazybones();
    }
}

3)生产者消费者模式

  • 生产者
    • 负责数据生产的模块
  • 消费者
    • 负责数据处理的模块
  • 缓冲区
    • 生产者锁生产的数据不是直接提供给消费者的,而是先进入缓冲区中,由消费者从缓冲区中拿要处理的数据
  • 代码示例
public class Product {//产品类
    private String name = null;

    public Product(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Producer implements Runnable {//生产者

    //成员变量缓冲区
    private ProductPool productPool;

    //构造方法
    public Producer(ProductPool productPool) {
        this.productPool = productPool;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {

            i = i % 10;
            String name = "产品" + (i++ + 1);
            Product product = new Product(name);
            this.productPool.push(product);

            System.out.println(Thread.currentThread().getName() + "生产了------------->" + name);
        }
    }
}

public class Consumer implements Runnable {

    public ProductPool productPool;

    public Consumer(ProductPool productPool) {
        this.productPool = productPool;
    }

    @Override
    public void run() {
        int i = 3;
        while (i > 0) {
            i--;
            Product product = this.productPool.pop();
            System.out.println(Thread.currentThread().getName() + "消费了----->" + product.getName());
        }
    }
}

public class ProductPool {//产品池

    /**
     * 定义缓冲区的大小和缓冲区列表
     */
    public int Maxsize;
    public List<Product> list;

    /**
     * 构造方法
     */
    public ProductPool(int size, List<Product> list) {
        this.Maxsize = size;
        this.list = list;
    }

    /**
     * 放产品进缓冲区的方法
     */
    public synchronized void push(Product product) {
        if (this.list.size() >= Maxsize) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.list.add(product);
        this.notifyAll();
    }

    /**
     * 取产品出缓冲区的方法
     */
    public synchronized Product pop() {
        if (this.list.size() <= 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Product product = this.list.remove(0);
        this.notifyAll();
        return product;
    }
}

public class MainMethod {//主方法

    public static void main(String[] args) {
        /**
         * 实例化缓冲区
         */
        ProductPool productPool = new ProductPool(10, new LinkedList<>());

        /**
         * 创建生产者线程1
         */
        Producer producer = new Producer(productPool);
        Thread thread = new Thread(producer, "生产者1");
        thread.start();

        /**
         * 创建消费者线程1
         */
        Consumer consumer1 = new Consumer(productPool);
        Thread thread1 = new Thread(consumer1, "消费者1");
        thread1.start();

        /**
         * 创建消费者线程2
         */
        Consumer consumer2 = new Consumer(productPool);
        Thread thread2 = new Thread(consumer2, "消费者2");
        thread2.start();

        /**
         * 创建消费者线程3
         */
        Consumer consumer3 = new Consumer(productPool);
        Thread thread3 = new Thread(consumer3, "消费者3");
        thread3.start();
    }
}

6. 高级并发对象

1)线程定义:实现Callable接口

  • 实现Callable接口的优点
    • 解决无法获取子线程返回值的问题
    • 解决run()方法不可以抛出异常的问题
  • 代码示例
public class ThreadCallable implements Callable {//Callable接口的线程对象示例
    int i = 0;

    @Override
    public Object call() throws Exception {
        for (int i1 = 0; i1 < 10; i1++) {
            System.out.println(Thread.currentThread().getName() + ":" + i++);
        }
        return i;//获得子线程的返回值
    }

    public static void main(String[] args) {
        ThreadCallable threadCallable = new ThreadCallable();
        for (int i = 0; i < 10; i++) {//创建10个子线程
            FutureTask futureTask = new FutureTask(threadCallable);
            Thread thread = new Thread(futureTask, "子线程" + i);
            thread.start();
            try {//打印子线程的返回值
                System.out.println("子线程" + i + "返回值:" + futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

2)锁对象

  • 同步代码块与同步方法依赖于简单的可重入锁,这种锁易于使用,但是有很多限制
  • java.util.concurrent.locks包下有很多实用的锁方法
  • 代码示例
public class ThreadLock implements Runnable {
    //票数
    private static int ticket = 100;
    private ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (ticket > 0) {

            //上锁
            reentrantLock.lock();
            if (ticket <= 0) {
                reentrantLock.unlock();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + (--ticket));
            //解锁
            reentrantLock.unlock();
        }
    }
        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + (--ticket));
    }

    /**
     * 主线程
     */
    public static void main(String[] args) {
		//创建子线程对象并启用
        ThreadLock conflict = new ThreadLock();
        Thread thread1 = new Thread(conflict, "售票员1");
        Thread thread2 = new Thread(conflict, "售票员2");
        Thread thread3 = new Thread(conflict, "售票员3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

3)线程池

  • ThreadPool

    • 将经常要使用的线程保存在线程池中,以重复使用
  • 在Java中线程的创建与销毁需要频繁的系统调度,会耗费很大的资源,频繁的创建于销毁线程会影响系统性能

  • 使用线程池的好处

    • 降低系统资源的消耗
    • 提高任务的响应速度,执行任务时不必等待线程创建再执行
    • 提高线程的可管理性,进行统一的分配,调优,监控
  • 代码示例

public class ThreadPool implements Runnable {//线程池示例
    @Override
    public void run() {
 System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        //创建一个线程池服务
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //给池中添加线程
        ThreadPool threadPool = new ThreadPool();
        executorService.execute(new Thread(threadPool));
        executorService.execute(new Thread(threadPool));
        executorService.execute(new Thread(threadPool));
        executorService.execute(new Thread(threadPool));
        executorService.execute(new Thread(threadPool));
        executorService.execute(new Thread(threadPool));
        executorService.shutdown();//关闭线程池
    }
}

4)并发集合:BlockingQueue

  • 主要被设计用于生产者-消费者队列,有put()和take()两个方法,当集合为空时,从集合中取元素将进入等待状态,直到有元素才会唤醒;当集合满时,往集合中添加元素将进入等待状态,直到有空间才会唤醒。是线程安全的
  • 使用BlockingQueue集合实现生产者-消费者队列
public class Product {//产品类
    private String name;

    public Product(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Producer implements Runnable {//生产者或厨师

    private BlockingQueue<Product> blockingQueue;//成员变量并发集合,由构造方法提供初始化

    public Producer(BlockingQueue<Product> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            i = i % 10 + 1;
            try {
                Product product = new Product("产品" + i);
                blockingQueue.put(product);
                System.out.println(Thread.currentThread().getName() + "生产了----->" + product.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Consumer implements Runnable {//消费者类

    private BlockingQueue<Product> blockingQueue;//成员变量并发集合,由构造方法进行初始化

    public Consumer(BlockingQueue<Product> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {

        int i = 0;
        while (i++ < 3) {
            Product product;
            try {
                product = blockingQueue.take();
                System.out.println(Thread.currentThread().getName() + "消费了" + product.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class TestClass {//测试类

    public static void main(String[] args) {

        //创建有并发控制的并发集合,用来存储产品
        BlockingQueue<Product> blockingQueue = new ArrayBlockingQueue<>(10);

        //创建生产者线程
        Thread thread = new Thread(new Producer(blockingQueue), "生产者1");

        //创建消费者线程
        Consumer consumer = new Consumer(blockingQueue);
        Thread thread1 = new Thread(consumer, "消费者1");
        Thread thread2 = new Thread(consumer, "消费者2");
        Thread thread3 = new Thread(consumer, "消费者3");

        //运行线程
        thread.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

5)静态代理

  • 类比于结婚,你、婚庆公司、结婚这件事
    • 对于你,你要结婚,所以要准备结婚这件事
    • 对于婚庆公司,你找到他们,有偿让他们帮助你操办婚礼,所以他们也要准备结婚这件事,而且他们要准备的肯定比你要多
    • 所以,可以将这件事类比到Java中,结婚这件事可以抽象成一个接口,里面有结婚这个方法;你抽象成一个类、实现了结婚接口;婚庆公司也抽象成一个类,也实现了结婚接口,而且你要完成的事婚庆公司要帮你完成,婚庆公司的功能应该更多
  • 代码示例
public class MyStaticProxy {//静态代理示例

    public static void main(String[] args) {
        //创建Me对象,并将Me对象委托给WeddingCompany,调用WeddingCompany对象的结婚方法
        Me me = new Me();
        WeddingCompany weddingCompany = new WeddingCompany(me);
        weddingCompany.marryMethod();
    }
}

/**
 * 结婚接口
 */
interface Marry {
    void marryMethod();
}

/**
 * 要结婚的人
 */
class Me implements Marry {
    @Override
    public void marryMethod() {
        System.out.println("我要结婚了");
    }
}

/**
 * 婚庆公司
 */
class WeddingCompany implements Marry {

    private Marry marry;

    @Override
    public void marryMethod() {
        before();
        this.marry.marryMethod();
        after();
    }

    public WeddingCompany(Marry marry) {
        this.marry = marry;
    }

    private void before() {
        System.out.println("婚庆公司布置婚礼现场,准备流程");
    }

    private void after() {
        System.out.println("付钱回家,婚庆公司收拾结尾");
    }
}

6)Lambda表达式

  • 用来简便的书写接口或抽象类的实例,突出重写的方法体,而忽略不必要的格式
  • 优点
    • 允许将函数作为一个方法的参数
    • 使用Lambda表达式可以让代码变得更加简介
  • Lambda表达式演变示例
public class LambdaExpressions {

    /**
     * 2. 使用静态成员内部类的形式实现接口
     */
    static class Love2 implements InLove {
        @Override
        public void lambda() {
            System.out.println("2. 使用静态成员内部类的形式实现接口");
        }
    }

    public static void main(String[] args) {

        /**
         * 1. 使用外部类的方式实现接口
         */
        new Love1().lambda();

        /**
         *2. 使用静态成员内部类的形式实现接口
         */
        new Love2().lambda();

        /**
         * 3. 使用局部内部类的形式实现接口
         */
        class Love3 implements InLove {
            @Override
            public void lambda() {
                System.out.println("3. 使用局部内部类的形式实现接口");
            }
        }
        new Love3().lambda();

        /**
         * 4. 使用匿名内部类的形式实现接口
         */
        new InLove() {
            @Override
            public void lambda() {
                System.out.println("4. 使用匿名内部类的形式实现接口");
            }
        }.lambda();


        /**
         * 5. 使用Lambda表达式的形式实现接口
         */
        InLove love5 = () -> {
            System.out.println("5. 使用Lambda表达式的形式实现接口");
        };
        love5.lambda();
    }
}

/**
 * 接口
 */
interface InLove {
    public void lambda();
}

/**
 * 1. 使用外部类的形式实现接口
 */
class Love1 implements InLove {
    @Override
    public void lambda() {
        System.out.println("1. 使用外部类的形式实现接口");
    }
}

7)ArrayList集合与CopyOnWriteArray集合

  • ArrayList集合:线程不安全的

    • add()方法没有synchronized同步锁控制
    • 代码示例
    public class ArrayLists {//ArrayList的线程不安全
    
        public static void main(String[] args) {
            ArrayList<String> arrayList = new ArrayList<>();
    
            for (int i = 0; i < 100; i++) {
                /**
                 * 使用Lambda表达式将Runnable接口的子类实现传递给Thread类的构造方法
                 */
                new Thread(() -> {
                    /**
                     * 为了解决ArrayList集合的线程不安全问题,可以将插入元素的语句锁起来,这样子就不会有并发发生
                     */
                    synchronized ("") {
                        arrayList.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            /**
             * 因为主线程也是一个线程,可能主线程的集合长度都打印出来了,子线程的元素还没有添加完,
             * 所以先让主线程休眠1秒,等子线程的元素添加完之后再打印元素个数
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(arrayList.size());
            }
            for (String s : arrayList) {//打印集合中的元素
                System.out.println(s);
            }
        }
    }
  • CopyOnWriteArray集合:线程安全的

    • CopyOnWriteArray集合所有的操作都是对底层数组进行一次新的复制来实现
    • 代码示例
    public class CopyOnArrayLists {//多线程安全的集合
    
        public static void main(String[] args) {
    
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();//创建多线程安全的集合
    
            for (int i = 0; i < 100; i++) {//创建100个子线程,使用Lambda表达式的形式
                new Thread(() -> {//Lambda表达式,类似于匿名内部类,在参数为抽象类或接口时使用,不用创建接口或抽象类的子类对象
                    list.add(Thread.currentThread().getName());//将子线程的名称添加到集合中
                }).start();//开启线程
            }
    
            try {
                Thread.sleep(100);//让主线程睡眠100毫秒,先让其他的子线程执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(list.size());
            }
    
            for (String s : list) {//遍历打印集合的内容
                System.out.println(s);
            }
        }
    }

完…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值