初识多线程

多线程

进程&线程

什么是进程

进程:进行(执行)中的应用程序,我们称之为进程。进程属于CPU分配资源的最小单位。

目前操作系统都是支持多进程的,可以同时支持多个进程,通过进程ID区分。

单核CPU在同一时刻,只能运行一个进程;宏观并行丶微观串行

什么是线程

线程:线程属于CPU执行调度的最小单位。又称轻量级进程(Light Weight Process)。进程中的一条执行路径,也是CPU的基本调度单位,一个进程有一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

Java虚拟机是一个进程,当中包含主线程(main),可通过代码创建多个独立线程,与main并发执行。

进程和线程的区别

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

线程的组成

任何一个线程都具有基本的组成部分:

  • ​ CPU时间片:操作系统(OS)会为每个线程分配执行时间。

运行数据:

  • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
  • 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈

线程特点

  1. 线程抢占式执行
  • 效率高
  • 可防止单一线程长时间独占CPU
  1. 在单核CPU中,宏观上同时执行,微观上顺序执行。

并发和并行

并发:同时发生,轮流交替来执行。

并行:真正意义上的同时执行。


创建线程

继承Thread类
/***
 * 继承Thread类
 * this.getId() 获取当前线程的id
 * this.getName() 获取当前线程的名字
 * 注意:只能在继承Thread类时才能使用
 * Thread.currentThread().getId() 获取当前线程的id
 * Thread.currentThread().getName() 获取当前线程的名字
 * start() 开启线程
 *           ░     ░ ░      ░
 */
class MyThreadTest extends Thread{
    @Override
    public void run() {
        for(int i =0;i<100;i++){
            System.out.println("线程id:" + this.getId() +"--------" + "线程名字:" + this.getName() + "子线程:----" + i);
        }
    }
}
public class MyThread {
    public static void main(String[] args) {
        MyThreadTest th1 = new MyThreadTest();
        // 开始线程
        th1.start();
        // 主线程main执行
        for(int i = 0;i<50;i++){
            System.out.println("线程id:" + Thread.currentThread().getId() +"--------" + "线程名字:" + Thread.currentThread().getName());
        }
    }
}
/*
* 修改线程名字
* 方式1 调用setName()直接修改线程名字
* 方式2 构造方法修改线程名字
* */
// 方式1
class MyThreadTest extends Thread{
    @Override
    public void run() {
        for(int i =0;i<100;i++){
            System.out.println("线程id:" + this.getId() +"--------" + "线程名字:" + this.getName() + "子线程:----" + i);
        }
    }
}
public class MyThread {
    public static void main(String[] args) {
        MyThreadTest th1 = new MyThreadTest();
        // 方式一 修改线程名字
        th1.setName("我是一个子线程");
        // 开始线程
        th1.start();
    }
}
// =========================================
// 方式2
class MyThreadTest extends Thread{
  // 方式二
    public MyThreadTest(){}
    // 在构造方法中传递给Thread类一个name
    public MyThreadTest(String name){super(name);}
    @Override
    public void run() {
        for(int i =0;i<100;i++){
            System.out.println("线程id:" + this.getId() +"--------" + "线程名字:" + this.getName() + "子线程:----" + i);
        }
    }
}
public class MyThread {
    public static void main(String[] args) {
        MyThreadTest th1 = new MyThreadTest("我是一个子线程");
        // 开始线程
        th1.start();
    }
}
public class TicketWin extends Thread{
    private int ticket = 100;
    public TicketWin(){}
    public TicketWin(String name){super(name);}
    @Override
    public void run() {
        while(true){
            if(ticket <= 0 ){
                break;
            }else{
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票" ) ;
                ticket--;
            }
        }
    }

    public static void main(String[] args) {
        TicketWin t1 = new TicketWin("子线程1");
        TicketWin t2 = new TicketWin("子线程2");
        TicketWin t3 = new TicketWin("子线程3");
        TicketWin t4 = new TicketWin("子线程4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

实现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 static void main(String[] args) {
        // 创建Runnable对象,表示线程要执行的功能
        MyRunnable myRunnable = new MyRunnable();
        // 创建线程对象 Thread可以传入一个Runnable实现类和线程名字
       Thread th1 = new Thread(myRunnable,"子线程");
        th1.stat();
    }
}
 public static void main(String[] args) {
        // 使用匿名内部类创建Runnable
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                for(int i =0;i<100;i++){
                    System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+i);
                }
            }
        };
        // 创建线程对象
        Thread th1 = new Thread(runnable,"runnable线程");
        th1.start();
    }

线程的状态

线程状态:

  • new Thread() 线程对象被创建 即为 初始状态 只在堆中开辟内存,与常规对象无异
  • start() 就绪状态
  • run() 运行run()方法 运行状态
  • sleep join … 阻塞
  • 终止线程 结束

线程休眠

sleep():

每过多久就会让线程休息一会,然后再执行

			Runnable runnable = new Runnable(){
            @Override
            public void run() {
                for(int i =0;i<10;i++){
                    System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
                    try {
                        // 线程休眠 
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable,"牛牛").start();

线程礼让

Yield():

线程与线程之间相互礼让 轮流交替执行

			Runnable runnable = new Runnable(){
            @Override
            public void run() {
                for(int i =0;i<10;i++){
                    System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
                    // 线程礼让
                    Thread.yield();
                }
            }
        };
        new Thread(runnable,"熊大").start();
        new Thread(runnable,"熊二").start();

加入线程

join():

加入到当前线程,并阻塞当前的线程,直到加入的线程执行完毕

 public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                for(int i =0;i<10;i++){
                    System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread th = new Thread(runnable, "熊二");
        th.start();
        // join 加入到当前线程 并阻塞当前线程 直到加入线程执行完毕
        th.join();
        for(int i =0;i<10;i++){
            System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

设置线程优先级

setPriority() :

线程优先级为1-10,默认5 ,优先级越高,表示获取CPU机会越多

   		// 设置线程优先级:
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                for(int i =0;i<10;i++){
                    System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t1 = new Thread(r1,"熊大");
        Thread t2 = new Thread(r1,"熊二");
        t1.start();
        t2.start();
        t1.setPriority(1);
        t2.setPriority(10);

守护线程

守护线程:

  • 线程对象.setDaemon(true)设置为守护线程
  • 线程有两类:用户线程(前台线程)丶守护线程(后台线程)
  • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
  • 垃圾回收器线程属于守护线程
Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for(int i =0;i<30;i++){
                    System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread th = new Thread(runnable,"前台线程");
        // 设置为守护线程
        th.setDaemon(true);
        th.start();
        for(int i =0;i<10;i++){
            System.out.println("线程名称" + Thread.currentThread().getName() + "-----" +i);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

线程安全

线程安全问题:

  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
  • 原子操作:不可分割的多不操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

同步代码块

synchronized(临界资源){ // 对临界资源对象加锁

​ // 代码 (原子操作)

}

为当前线程上锁,即使当前线程时间片结束,还没执行完毕,下个线程也不会破坏当前线程所执行的结果

        // 创建数组
        String[] s = new String[5];
        // 创建线程
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
               synchronized(s){
                   s[index] = "Hello";
                   index++;
               }
            }
        };
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
               synchronized(s){
                   s[index] = "World";
                   index++;
               }
            }
        };
        Thread th1 = new Thread(r1,"A");
        Thread th2 = new Thread(r2,"B");
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        System.out.println(Arrays.toString(s));

同步方法

为当前方法加锁,synchronized 关键字加载返回值之前。锁是this

修饰符 synchronized 返回值 方法名(){

​ // 代码(原子操作)

}

// 实现Runnable接口
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            @Override
            public void run() {
                while(true){
                  if(!sale()){
                      break;
                  }
                }
            }
            // 同步方法 synchronized 加在返回值之前
            // 同步方法 的锁就是this 谁调用指向谁
            public synchronized boolean sale(){
                synchronized(this){
                    if(ticket <= 0 ){
                       return false;
                    }else{
                        System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票" ) ;
                        ticket--;
                    }
                }
                return true;
            }
        };
        // 创建线程对象
        Thread th1 = new Thread(runnable,"线程1");
        Thread th2 = new Thread(runnable,"线程1");
        Thread th3 = new Thread(runnable,"线程1");
        Thread th4 = new Thread(runnable,"线程1");
        th1.start();
        th2.start();
        th3.start();
        th4.start();

同步静态方法

为当前方法加锁,synchronized 关键字加载返回值之前。锁是this

public class Tickets implements Runnable{
    private static int ticket = 100;

    @Override
    public void run() {
        while(true){
            if (!sale()) {
                break;
            }
        }
    }
    // 同步静态方法 关键字加载静态方法里面 锁是 类名.class
    public static boolean sale(){
        synchronized(Tickets.class){
            if(ticket<=0){
                return false;
            }else{
                System.out.println(Thread.currentThread().getName()+"买了第" + ticket + "票");
                ticket--;
                return true;
            }
        }
    }

    public static void main(String[] args) {
        Tickets tk = new Tickets();
        Thread th = new Thread(tk,"熊二");
        th.start();
    }
}

同步规则

同步规则:

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

死锁

死锁:

  • 当一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象的锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
public class MyLock {
    // 创建两个锁
    public static Object a = new Object();
    public static Object b = new Object();

    public static void main(String[] args) throws InterruptedException {
        Boy boy = new Boy();
        Girl girl = new Girl();
        boy.start();
        Thread.sleep(300);
        girl.start();
    }
}
class Boy extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.a){
            System.out.println("男孩拿到了a锁");
            synchronized (MyLock.b){
                System.out.println("男孩拿到了b锁");
                System.out.println("男孩开始吃东西");
            }
        }
    }
}
class Girl extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.b){
            System.out.println("女孩拿到了b锁");
            synchronized (MyLock.a){
                System.out.println("女孩拿到了a锁");
                System.out.println("女孩开始吃东西");
            }
        }
    }
}

线程通信

等待:锁.wait()

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

通知 唤醒:锁.notify()

  • public final void notify()
  • public final void notifyAll()
public class BankCard {
    // 余额
    private double money;
    // 标记
    private boolean flag = false; // 控制能否存取钱

    // 存钱
    public synchronized void save(double m){
        while(flag){ // 如果flag 为true那么就不能存钱
            try {
                // 进入等待队列 释放锁和cpu
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money + m;
        System.out.println(Thread.currentThread().getName() + "存了" + m + "余额是" +money);
        // 修改标记
        flag = true;
        // 唤醒取钱线程  锁.notify()
        this.notifyAll();
    }

    // 取钱
    public synchronized void take(double m){
        while(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money - m;
        System.out.println(Thread.currentThread().getName() + "取了" + m + "余额是" +money);
        // 修改标记
        flag = false;
        // 唤醒线程
        this.notifyAll();
    }

    public static void main(String[] args) {
        // 创建银行卡
        BankCard card = new BankCard();
        // 创建操作
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        // 创建线程
        Thread xiongda = new Thread(add,"熊大");
        Thread xionger = new Thread(sub,"熊二");
        xiongda.start();
        try {
            xiongda.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        xionger.start();
    }
}

class AddMoney implements Runnable{
    private BankCard card;
    public AddMoney(BankCard card){
        this.card = card;
    }

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

class SubMoney implements Runnable{
    private BankCard card;
    public SubMoney(BankCard card){
        this.card = card;
    }

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

生产者&消费者

生产者丶消费者:

  • 若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者都能并发执行,在两者间设置一个能存储多个产品的缓存区,生产者将生产的产品放入缓冲区,消费者从缓冲区取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区取走产品,也不允许生产者向一个缓冲区放入产品。
public class Bread {
    private int id;
    private String prodcutName;

    @Override
    public String toString() {
        return "Bread{" +
                "id=" + id +
                ", prodcutName='" + prodcutName + '\'' +
                '}';
    }

    public Bread() {
    }

    public Bread(int id, String prodcutName) {
        this.id = id;
        this.prodcutName = prodcutName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProdcutName() {
        return prodcutName;
    }

    public void setProdcutName(String prodcutName) {
        this.prodcutName = prodcutName;
    }
}

public class BreadCon {
    // 创建数组
    private Bread[] cons = new Bread[6];
    // 存放面包的位置
    private int index = 0;

    // 存放面包
    public synchronized void input(Bread b){ // 锁 this
        // 判断容器是否满了
        while(index >= 6){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        cons[index] = b;
        System.out.println(Thread.currentThread().getName()+"生产了" + b.getId()+"");
        index++;
        this.notifyAll();
    }

    // 取出面包
    public synchronized void output(){ // 锁 this
        while(index <= 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index--;
        Bread b = cons[index];
        System.out.println(Thread.currentThread().getName()+"消费了" + b.getId()+"");
        cons[index] = null;
        // 唤醒生产者
        this.notifyAll();
    }

}

public class Product implements Runnable{
    private BreadCon con;

    public Product(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for(int i=0;i<30;i++){
            con.input(new Bread(i,Thread.currentThread().getName() ));
        }
    }
}

public class Cousume implements Runnable{
    private BreadCon con;

    public Cousume(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for(int i=0;i<30;i++){
            con.output();
        }
    }
}

public static void main(String[] args) {
        // 容器
        BreadCon breadCon = new BreadCon();
        // 生产者和消费者
        Product product = new Product(breadCon);
        Cousume cousume = new Cousume(breadCon);
        // 创建线程对象
        Thread xiongda = new Thread(product,"熊大");
        Thread xionger = new Thread(cousume,"熊二");
        xiongda.start();
        xionger.start();

    }

线程池

线程池的概念

问题:

  • 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出,
  • 频繁的创建及销毁线程会增加虚拟机回收频率,资源开销,造成程序性能下降。

线程池

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

线程池原理

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

创建线程池

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

  • Executor: 线程池的顶级接口
  • ExecutorService : 线程池接口,可通过submit(Runnable task)提交任务代码。
  • Executors工厂类:通过此类可以获得一个线程池。
  • 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
  • 通过newCachedThreadPool() 获得动态数量的线程池,如果不够则创建新的,没有上限。
/*
* 线程池创建
* Executor : 线程池的根接口, Executor()
* ExecutorService : 包含管理线程池的一些方法,Submit() 提交线程任务 shutdown()关闭线程池
*       ThreadPoolExecutor
*       ScheduledThreadPoolExecutor
* Executors : 创建线程池的工具类
*   (1) 创建固定线程个数的线程池
*   (2) 创建缓存线程池,由任务的多少决定
*   (3) 创建单线程池
*   (4) 创建调度线程池, 调度 : 周期 定时执行
*
* */
public class Executorss {
    public static void main(String[] args) {
        // 创建固定个数的线程池对象
         ExecutorService es = Executors.newFixedThreadPool(4);
        
        // 创建缓存线程池,线程个数由任务多少决定
        // ExecutorService es = Executors.newCachedThreadPool();
        
        // 创建单线程池 不常用
        // ExecutorService es = Executors.newSingleThreadExecutor();
        
        // 创建调度线程池, 调度 : 周期 定时执行
        // ScheduledExecutorService es = Executors.newScheduledThreadPool(3);

        // 创建线程任务
        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--;
                }
            }
        };
        // 提交线程任务
        for(int i=0;i<5;i++){
            es.submit(runnable);
        }
        // 关闭线程池
        //  shutdown()等待线程池任务执行完毕后关闭线程池
        // shutdownNow()直接关闭线程池不管任务是否执行完毕
        es.shutdown();
    }
}

Callable接口

Callable:

  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值丶可以声明异常。
/*
* Callable 接口
* Callable 和 Runnable区别
* (1) Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
* (2) Callable接口中call方法有声异常,Runnable接口中run方法没有异常
* */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Callable接口创建 它是一个接口 用匿名内部类创建
        Callable<Integer> callable = new Callable() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算1-100和");
                int sum = 0;
                for(int i = 0;i<=100;i++){
                    sum += i;
                }
                return sum;
            }
        };

        // 把Callable接口转换成一个任务
        // FutureTask 实际上继承了Runnable
        FutureTask<Integer> task = new FutureTask(callable);

        // 创建线程对象
        Thread th = new Thread(task);
        // 启动线程
        th.start();

        // get() 获取Callable的返回值
        // 等call方法执行完毕 get()才能获取到结果
        Integer result = task.get();
        System.out.println(result);

    }
}
// Callable接口配合线程池使用
public class CallableExecutor {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建线程池
        ExecutorService es = Executors.newFixedThreadPool(1);

        // 提交任务 把Callable提交给线程池
        // Future 表示将要执行完任务的结果
        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算1-100和");
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum += i;
                }
                return sum;
            }
        });

        // 获取线程任务执行完的结果
        System.out.println(future.get());

        // 关闭线程池
        es.shutdown();

    }
}

Future接口

Future : 表示将要完成线程任务的结果。 等待线程任务执行完毕。

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

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

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建线程池
        ExecutorService es = Executors.newFixedThreadPool(2);

        // 提交任务
        Future<Integer> future1 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // 计算1-50的和
                int sum = 0;
                System.out.println(Thread.currentThread().getName()+"计算1-50的和计算完毕");

                for(int i=0;i<=50;i++){
                    sum += i;
                }
                return sum;
            }
        });
        Future<Integer> future2 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // 计算51-100的和
                int sum = 0;
                System.out.println(Thread.currentThread().getName()+"计算51-100的和计算完毕");
                for(int i=51;i<=100;i++){
                    sum += i;
                }
                return sum;
            }
        });

        // 获取结果
        Integer sum = future1.get() + future2.get();
        System.out.println(sum);

        // 关闭线程池
        es.shutdown();
    }
}

线程的同步和异步

同步:

  • ​ 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

异步:

  • 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回,而这竞争时间片,并发执行。
  • 异步是多条执行路径

Lock接口

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

ReentrantLock : Lock接口的实现类,与 synchronized 一样具有互斥锁功能。

// ReentrantLock
public class ReentrantLockTest {
    // 创建 ReentrantLock
    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(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName() + "添加了" + value);
        }finally {
            // 释放锁
            lock.unlock();
        }
    }
    public String[] getStr(){
        return str;
    }

    // 测试
    public static void main(String[] args) throws InterruptedException {
        // 实例化ReentrantLockTest类
        ReentrantLockTest rl = new ReentrantLockTest();
        // 实现Runnable
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                rl.add("Hello");
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                rl.add("World");
            }
        };

        // 创建线程对象
        Thread th1 = new Thread(runnable1,"熊大");
        Thread th2 = new Thread(runnable2,"熊二");
        // 启动线程
        th1.start();
        th2.start();

        th1.join();
        th2.join();
        System.out.println(Arrays.toString(rl.getStr()));
    }
}
		// 创建线程池
        ExecutorService es = Executors.newFixedThreadPool(4);

        // 提交线程任务
        for(int i=0;i<4;i++){
            es.submit( // 实现Runnable匿名内部类
                     new Runnable(){
                        private int ticket = 100;
                        // 创建 ReentrantLock
                        private Lock lock = new ReentrantLock();

                        @Override
                        public void run() {
                            while(true){
                                try{
                                    // 上锁
                                    lock.lock();
                                    if(ticket <= 0){
                                        break;
                                    }
                                    System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                                    ticket--;
                                }finally {
                                    // 释放锁
                                    lock.unlock();
                                }
                            }
                        }
                    });
        }

        // 关闭线程池
        es.shutdown();
读写锁

ReentrantReadWriteLock :

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
  • 支持多次分配读锁,使多个操作可以并发执行。
// 读写锁
public class ReadWriteLockTest {
    // 创建读写锁
    private ReentrantReadWriteLock rrl = new ReentrantReadWriteLock();

    // 创建读锁
    private ReentrantReadWriteLock.ReadLock readLock = rrl.readLock();

    // 创建写锁
    private ReentrantReadWriteLock.WriteLock writeLock = rrl.writeLock();

    private String value;

    public String getValue(){
        // 使用读锁 上锁
        readLock.lock();
        try{
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取:" + value);
            return this.value;
        }finally {
            // 释放读锁
            readLock.unlock();
        }

    }

    public void setValue(String value){
       // 使用写锁 上锁
        writeLock.lock();
        try{
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("写入:" + value);
            this.value = value;
        }finally {
            writeLock.unlock();
        }
    }
}

class TestReadWriteLock{
    public static void main(String[] args) {

        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();

        // 创建线程池
        ExecutorService es = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        // 提交写入任务线程
       for(int i=0;i<2;i++){
           es.submit(new Runnable() {
               @Override
               public void run() {
                   readWriteLockTest.setValue("牛小牛");
               }
           });
       }

       // 提交读取任务线程
        for(int i=0;i<18;i++){
            es.submit(new Runnable() {
                @Override
                public void run() {
                   readWriteLockTest.getValue() ;
                }
            });
        }

        // 关闭线程池
        es.shutdown();

        // 判断线程任务是否执行完毕 线程池是否关闭
        while(!es.isTerminated()){ // 空转

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

线程安全的集合

CopyOnWriteArrayList

CopyOnWriteArray:

  • 线程安全的ArrayList,加强版的读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时(添加) , 先copy一个容器副本,再添加新元素,最后替换引用。
  • 使用方式 与ArrayList无异。
		// 创建 CopyOnWriteArrayList
        CopyOnWriteArrayList<String> list = 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++){
                        list.add(Thread.currentThread().getName() +"..." + new Random().nextInt(1000));
                    }
                }
            });
        }
        // 关闭线程池
        es.shutdown();
        while (!es.isTerminated()){}
        // 打印结果
        System.out.println("元素个数:" + list.size());
        for (String s : list) {
            System.out.println(s);
        }

CopyOnWriteArraySet

CopyOnWriteArraySet:

  • 线程安全的Set,底层使用CopyOnWrteArrayList实现。
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组,有序
  • 如存在元素,则不添加(扔掉副本)
		// 创建 CopyOnWriteArraySet
        CopyOnWriteArraySet<String> list = new CopyOnWriteArraySet<>();
        // 添加元素
        list.add("苹果");
        list.add("香蕉");
        list.add("葡萄");
        // 不可以添加重复的
        list.add("苹果");
        System.out.println(list.toString());


        // 创建线程池
        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++){
                        list.add(Thread.currentThread().getName() +"..." + new Random().nextInt(1000));
                    }
                }
            });
        }
        // 关闭线程池
        es.shutdown();
        while(!es.isTerminated()){}
        // 打印结果
        for (String s : list) {
            System.out.println(s);
        }

Queue接口(队列)

  • Collection的子接口,表示队列FIFO(First In First Out)先进先出
  • 常用方法:
    • ​ 抛出异常:
      • ​ boolean add(E e) 顺序添加一个元素(到达上限后,再添加则会报异常)
      • E remove() 获得第一个元素并移除(如果队列没有元素则抛异常)
      • E element() 获得第一个元素但不移除 (如果队列没有元素,则抛异常)
    • 返回特殊值: 推荐使用
      • boolean offer(E e) 顺序添加一个元素(到达上限后,再添加则会返回false)
      • E poll() 获得第一个元素并移除 (如果队列没有元素时,则返回null)
      • E peek() 获得第一个元素但不移除(如果队列没有元素时,则返回null)
   		// 创建 Queue 队列
        Queue<String> queue = new LinkedList<>();
        // 入队 offer() 顺序添加元素
        queue.offer("苹果");
        queue.offer("香蕉");
        queue.offer("葡萄");
        int size = queue.size();
        // 获取第一个元素 但不移除 peek()
        System.out.println(queue.peek());
        System.out.println("---------");
        // 出列 poll()
        for(int i=0;i<size;i++){
            System.out.println("出列:" + queue.poll());
        }
ConcurrentLinkedQueue无界(非阻塞队列)
  • 线程安全丶可高效读写的队列丶高并发下性能最好的队列。
  • 无锁丶CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
  • V:要更新的变量丶E:预期值丶N:新值
  • 只有当 V == E 时,V = N ; 否则表示已被更新过,则取消当前操作
		// 创建 ConcurrentLinkedQueue()
        ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue();
        // 线程操作ConcurrentLinkedQueue() 入队
        // 创建线程
        Thread th1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<=5;i++){
                    concurrentLinkedQueue.offer(i);
                }
            }
        });
        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=6;i<=10;i++){
                    concurrentLinkedQueue.offer(i);
                }
            }
        });
        th1.start();
        th2.start();
        th1.join();
        th2.join();

        // 出列 poll()
        int size = concurrentLinkedQueue.size();
        for(int i=0;i<size;i++){
            System.out.println("出列:" + concurrentLinkedQueue.poll());
        }

BlockingQueue接口(阻塞队列)

  • Queue的子接口丶阻塞的队列,增加了两个线程状态为无限期等待的方法
  • 方法:
    • ​ void put(E e) 将指定元素插入此队列中,如果没有可用空间,则等待
    • E take() 获取并移除此队列头部元素,如果没有可用元素,则等待。
  • ArrayBlockingQueue:
    • ​ 数组结构实现,有界队列。(手工固定上限)
  • LinkedBlockingQueue:
    • ​ 链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)

ArrayBlockingQueue

		// 创建 ArrayBlockingQueue(capacity) 阻塞队列
        ArrayBlockingQueue<String> aq = new ArrayBlockingQueue(3);
        // 添加 put() 超出容量阻塞
        aq.put("葡萄");
        aq.put("香蕉");
        aq.put("橘子");
        // 删除元素
        aq.take();
        System.out.println("已经添加了3个元素了");
        // 超出容量阻塞  
        aq.put("苹果");
        System.out.println("已经添加了4个元素了");
        System.out.println(aq.toString());

LinkedBlockingQueue

 		// 创建 LinkedBlockingQueue(capacity) 阻塞队列
        LinkedBlockingQueue<String> lq = new LinkedBlockingQueue(3);
        // 添加 put() 超出容量阻塞
        lq.put("葡萄");
        lq.put("香蕉");
        lq.put("橘子");
        // 删除元素
        lq.take();
        System.out.println("已经添加了3个元素了");
        // 超出容量阻塞
        lq.put("苹果");
        System.out.println("已经添加了4个元素了");
        System.out.println(lq.toString());

public static void main(String[] args) {
        // 创建阻塞队列 ArrayBlockingQueue(capacity)
        ArrayBlockingQueue<Integer> aq = new ArrayBlockingQueue<>(6);
        // 创建线程
        Thread th1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<30;i++){
                    try {
                        aq.put(i);
                        System.out.println(Thread.currentThread().getName() + "添加了" + i + "号元素" );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"熊大");

        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<30;i++){
                    try {
                        Integer num = aq.take();
                        System.out.println(Thread.currentThread().getName() + "移除了" + i + "号元素" );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"熊二");

        th1.start();
        th2.start();
    }

ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计。线程安全的。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16
  • 使用方式与HashMap无异。
  • JDK8之后ConcurrentHashMap采用CAS无锁,效率比分段锁更高。
   		// 创建 ConcurrentHashMap 线程安全的
        ConcurrentHashMap<String,String> chm = new ConcurrentHashMap<>();
        // 多线程操作添加
       for(int i=0;i<5;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j=0;j<20;j++){
                        chm.put(Thread.currentThread().getName() + "--" + j ,j+"");
                        System.out.println(chm);
                    }
                }
            }).start();
       }

你可能感兴趣的:(多线程,队列,java)