Java基础知识——多线程

多线程

一、进程和线程

进程是操作系统资源分配的基本单位。

线程是CPU的基本调度单位

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

线程特点

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

进程和线程区别

  • 一个线程运行后至少有一个进程
  • 一个进程可以包含多个线程,但是至少需要有一个线程
  • 进程间不能共享数据段地址,但进程之间可以

二、线程创建

1.继承Thread类

步骤:

  1. 继承Thread类
  2. 重写run()方法
  3. 创建子类对象
  4. 调用start()方法
  • 子类中

    • this.getId()
    • this.getName()
  • 其他

    • Thread.currentThread().getId()
    • Thread.currentThread().getName()

    获取线程id和线程name

  • 更改线程名字

    • 调用线程对象:setName();
    • 使用线程子类的构造方法赋值
public class MyThread extends Thread{
    public MyThread(){

    }
    public MyThread(String threadName){
        super(threadName);
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //System.out.println(this.getId()+"-->"+this.getName()+"run"+i);
            System.out.println(Thread.currentThread().getId()+"-->"+Thread.currentThread().getName()+" run"+i);
        }
    }
}
//测试类
public class TestThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread("t2");
        myThread.setName("t0");
        myThread.start();
        myThread1.setName("t1");
        myThread1.start();
        myThread2.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main:"+i);
        }

    }

}

2.实现Runnable接口

  1. 实现Runnable接口
  2. 重写run()方法
  3. 创建实现类对象
  4. 创建线程对象
  5. 调用start对象
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class TestRunnable {
    public static void main(String[] args) {
        //创建可运行线程对象
        MyRunnable myRunnable  = new MyRunnable();
        //创建线程对象
        Thread thread = new Thread(myRunnable, "线程");
        //启动线程
        thread.start();
    }
}

常用方法

  • public static void sleep(long millis)
    • 休眠
    • 当前线程主动休眠millis毫秒。释放了CPU,不再争抢CPU
  • public static void yield()
    • 放弃
    • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
    • 注:只让给优先级比当前线程高或一样的
  • public final void join()
    • 加入
    • 允许其他线程加入到当前线程中。当前线程会阻塞,直到加入线程执行完毕
  • .setPriority(int n)
    • 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多
  • .interrupt()
    • 打断线程,被打断线程抛出InterruptedException
  • .setDaemon(true)
    • 设置为守护线程

一般使用Runnable接口:1.有资源共享。2.线程操作相同,共享资源类实现Runnable接口。3.线程操作不同,使用操作类分开实现Runnable接口

三、线程安全

1.线程不安全

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

2.死锁

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

四、线程同步

补充:并发和并行

  • 并发:指应用交替执行不同的任务
  • 并行:指应用同时执行不同的任务
  • 区别:一个是交替执行,一个是同时执行

1.synchronized作用域

/*
同步代码块
*/
synchronized(){//对临界资源加锁 monitor
    //代码(原子操作)
}
/*
同步方法
*/
synchronized 返回值类型 方法名(形参列表){//对当前对象(this)加锁
    //代码(原子操作)
}
  • 非静态方法(加的锁为对象锁)
public class Demo{
    public synchronized void printA(){//this
        System.out.println("A");
    }
    //等同-->代码块
    public void printB(){
        synchronized(this){
            System.out.println("B");
        }
    }
}
  • 静态方法(加的锁为类锁)
public synchronized static void printB(){//Demo.class
    System.out.println("B");
}
//等同-->代码块
public static void printC(){
    synchronized (Demo.class) {
        System.out.println("C");
    }
}

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中

线程退出同步方法时,会释放相应的互斥锁标记

如果时静态方法,锁时【类名.class】

  • 代码块(对象锁与类锁均可)
    public void printA(String name,int i){
        synchronized (this){
            System.out.println(name+"--->"+i);
        }
    }
    public static void printC(){
        synchronized (Demo.class) {
            System.out.println("C");
        }
    }

每个对象都有一个互斥锁标记,用来分配给线程的

只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块

线程退出同步代码块时,会释放相应的互斥锁标记

//四个窗口共卖100张票
public class TicketDemo {
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            private int ticket=100;
            //private Object lock=new Object();
            @Override
            public void run() {
                while(true){
                    synchronized (this){
                        if(ticket<=0){
                            break;
                        }
                        System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                        ticket--;
                    }

                }
            }
        };
        new Thread(runnable,"窗口1").start();
        new Thread(runnable,"窗口2").start();
        new Thread(runnable,"窗口3").start();
        new Thread(runnable,"窗口4").start();
    }
}

2.Lock

常用方法
  • void lock()
    • 获取锁,如锁被占用,则等待
  • boolean tryLock()
    • 尝试获取锁。成功返回true,失败返回false,不阻塞
  • void unlock()
    • 释放锁。在finally中释放锁,不然容易造成线程死锁
实现类
  • ReentrantLock --重入锁
    • 与synchronized一样实现重入锁
//lock锁实现卖票
public class Demo02 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int ticket = 1000;
            private Lock lock = new ReentrantLock();
            @Override
            public void run() {
                while (true) {
                    lock.lock();
                    try {
                        if (ticket < 0) {
                            break;
                        }
                        System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "号票");
                        ticket--;
                    } finally {
                        lock.unlock();
                    }
                }

            }
        };
        new Thread(runnable, "窗口一").start();
        new Thread(runnable, "窗口二").start();
        new Thread(runnable, "窗口三").start();
        new Thread(runnable, "窗口四").start();
    }
}
  • ReenTrantReadWriteLock --读写锁
    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
    • 支持多次分配读锁,使多个读操作可以并发执行
public class ReadWriterDemo {
    //创建读写锁
    private ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //变量
    private int value;

    public int getValue() throws InterruptedException {
        //上锁
        readWriteLock.readLock().lock();
        try {
            Thread.sleep(1000);
            return value;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    public void setValue(int value) throws InterruptedException {
        //上锁
        readWriteLock.writeLock().lock();
        try {
            Thread.sleep(1000);
            this.value=value;
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

//测试代码
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriterDemo readWriterDemo=new ReadWriterDemo();
        ExecutorService es = Executors.newFixedThreadPool(20);
        Runnable read=new Runnable() {
            @Override
            public void run() {
                try {
                   int r= readWriterDemo.getValue();
                    System.out.println(r);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable write=new Runnable() {
            @Override
            public void run() {
                try {
                    int i = new Random().nextInt(100);
                    readWriterDemo.setValue(i);
                    System.out.println(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //提交任务
        long start=System.currentTimeMillis();
        for(int i=0;i<2;i++){
            es.submit(write);
        }

        for(int i=0;i<18;i++){
            es.submit(read);
        }
        es.shutdown();
        //等待20个线程执行完毕,才继续执行
        while(!es.isTerminated()){}
        long end=System.currentTimeMillis();
        System.out.println("用时:"+(end-start));
    }
}
synchronized和Lock区别
类别synchronizedLock
存在层次Java关键字,在JVM层面上是一个类或接口
释放锁1.获取锁的线程执行同步到代码,释放锁
2.线程执行发生异常,JVM会让线程释放锁
在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一致等待Lock可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入,不可中断,非公平可重入,可中断,可公平或非公平
深入解析

正在准备。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

五、线程通信

1.常用方法

Object
  • 等待
    • public final void wait()
    • public final void wait(long timeout)
    • 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在锁的等待队列中。释放锁,进入等待队列
  • 通知
    • public final void notity()
      • 从等待队列中随机唤醒一个
    • public final void notifyAll()
      • 唤醒所有等待线程
    • 必须在对obj加锁的同步代码块中。从obj的waiti中释放一个或全部线程。对自身没有任何影响

利用synchronized、wait()、notifyAll()

//面包
public class Bread {
    private String name;
    private int no;

    public Bread(String name, int no) {
        this.name = name;
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }
}
//面包容器
public class BreadArray {

    private Bread []breads = new Bread[6];
    private int size;


    public synchronized void product(Bread bread){

        while(size>=6){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        breads[size++] = bread;
        System.out.println(bread.getName()+"生产了"+bread.getNo()+"号面包");
        this.notifyAll();
    }

    public synchronized void consum(){

        while(size<=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Bread bread = breads[--size];
        breads[size] = null;
        System.out.println(Thread.currentThread().getName()+"消费了"+bread.getNo()+"号面包,生产者是:"+bread.getName());
        this.notifyAll();
    }

}
//生产者
public class Product implements Runnable{

    private BreadArray breadArray;

    public Product(BreadArray breadArray) {
        this.breadArray = breadArray;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            breadArray.product(new Bread(Thread.currentThread().getName(), i));
        }
    }
}
//消费者
public class Consum implements Runnable{
    private BreadArray breadArray;

    public Consum(BreadArray breadArray) {
        this.breadArray = breadArray;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            breadArray.consum();
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        BreadArray breadArray = new BreadArray();
        Product product = new Product(breadArray);
        Consum consum = new Consum(breadArray);
        new Thread(product,"生产者1").start();
        new Thread(consum,"消费者1").start();
        new Thread(product,"生产者2").start();
        new Thread(consum,"消费者2").start();
    }
}

面试题:sleep()和wait有什么区别?

(1)sleep()是休眠 使用Thread.sleep()调用,wait()是等待,使用锁.wait()调用
(2)sleep()休眠时会释放cpu,不会释放资源(锁),自动唤醒
(3)wait()等待时会释放cpu和资源(),一般需要其他线程唤醒。
Condition
  1. Condition接口也提供类似Object的监视器方法,与Lock配合可以实现等待/通知模式

  2. Condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后直到线程等待的某个条件为真的时候才会被唤醒


  • await()
    • 当前线程进入等待状态
  • signal()
    • 唤醒一个等待线程
  • signalAll()
    • 唤醒所有等待线程

利用Lock和Condition实现生产者消费者

//面包类
public class Bread {
    //名字
    private String name;
    //编号
    private int no;

    public Bread(String name, int no) {
        this.name = name;
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }
}

//容器
public class BreadCon {
    //最多能存储6个
    private Bread[] breads = new Bread[10];
    //有效个数
    private int size;
    //定义锁
    Lock lock = new ReentrantLock();
    //创建生产者队列
    Condition proCondition = lock.newCondition();
    //创建消费者队列
    Condition conCondition = lock.newCondition();

    //生产
    public void product(Bread bread){
        lock.lock();
        try {
            while (size >= 10) {
                try {
                    proCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            breads[size++] = bread;
            System.out.println(bread.getName()+"生产了"+bread.getNo()+"号面包");
            conCondition.signal();
        }finally {
            lock.unlock();
        }
    }

    //消费
    public void consum(){
        lock.lock();
        try{
            while(size<=0){
                try {
                    conCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Bread bread = breads[--size];
            breads[size] = null;
            System.out.println(Thread.currentThread().getName()+"消费了"+bread.getNo()+"号面包生产者是:"+bread.getName());
            proCondition.signal();
        }finally {
            lock.unlock();
        }
    }

}
//生产者
public class Product implements Runnable{

    private BreadCon breadCon;

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            breadCon.product(new Bread(Thread.currentThread().getName(),i));
        }
    }
}
//消费者
public class Consume implements Runnable{
    private BreadCon breadCon;

    public Consume(BreadCon breadCon) {
        this.breadCon = breadCon;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            breadCon.consum();
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        BreadCon breadCon = new BreadCon();
        Product product = new Product(breadCon);
        Consume consume = new Consume(breadCon);
        new Thread(product, "生产1").start();
        new Thread(product, "生产2").start();
        new Thread(consume, "消费者1").start();
        new Thread(consume, "消费者2").start();
    }
}



面试题:使用Lock和Condition实现三个线程交替输出20遍“ABC"

public class Alternate {
    //创建锁
    Lock lock = new ReentrantLock();

    private int i = 1;//1-->代表A  2-->代表B  3-->代表C
    //创建队列
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();


    public void printA() {
        lock.lock();
        try {
            while (i != 1) {
                try {
                    conditionA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("A");
            i=2;
            conditionB.signal();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {

            while(i!=2){
                try {
                    conditionB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("B");
            i = 3;
            conditionC.signal();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while(i!=3){
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("C");
            i = 1;
            conditionA.signal();
        } finally {
            lock.unlock();
        }
    }

}

//测试类
public class Test01 {
    public static void main(String[] args) {
        Alternate a =  new Alternate();
        //固定线程池
        ExecutorService es = Executors.newFixedThreadPool(3);
        //提交任务
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    a.printA();
                }
            }
        });
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    a.printB();
                }
            }
        });
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    a.printC();
                }
            }
        });
        //关闭线程池
        es.shutdown();
    }
}

六、线程池

线程容量,可设定线程分配的数量上限。

将预先创建的线程对象存入池中,并重用线程池中的线程对象。

避免频繁的创建和销毁。

1.常用接口或类

  • Executor
    • 线程池的顶级接口
  • ExecutorService 线程接口
    • submit(Runnable task):提交任务代码
    • shutdown():关闭线程池。会等待所有提交的任务执行完毕,才关闭
  • Executors工厂类
    • 通过此类可以获得以下四种线程池
    • newFixedThreadPool(int nThread)
      • 创建固定数量的线程池
      • nThread:线程数量
    • newCachedThreadPool()
      • 创建动态大小线程池
      • 线程池大小根据提交任务变化
    • newSingleThreadPool()
      • 创建单线程池
      • 只有一个线程的线程池
    • newScheduledThreadPool(int n)
      • 调度线程池,实现周期执行或者延迟执行
        • 固定执行
          • .scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
            • 参数:提交任务 初始延迟 延迟 单位
        • 延迟执行
          • .scheduleAtFixedTate(Runnable command,long initialDelay,long period,TimeUnit unit)
            • 参数:提交任务 初始延迟 周期 时间单位

2.ThreadPoolExecutor

阿里巴巴Java开发手册

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

ThreadPoolExecutor的七个参数
  • corePoolSize
    • 核心线程数(不会销毁)
  • maximumPoolSize
    • 最大线程数(用完会销毁)
  • KeepAliveTime
    • 非核心线程的存活时间
  • unit
    • 时间单位
  • workQueue
    • 工作队列
  • threadFactory
    • 线程工厂(默认:Executor.defaultThreadFactory)
  • handle
    • 拒绝策略
      • AbortPolicy:中断,抛出异常(核心业务使用,使用最多)
        • 启动线程数量超过最大线程数量+队列数量时,会抛出异常
      • DiscardPolicy:直接抛弃,不抛出异常(非核心业务)
        • 启动线程数量超过最大线程数量+队列数量时,会直接抛弃线程
      • DiscardOldestPolicy:把旧的抛弃,加入新的
        • 把旧的抛弃,加入新的。首先启动核心线程,核心线程没结束,又有新的线程加入,先放在队列中,如果在有新的线程,则加入非核心线程(不超过最大线程数),超过最大线程则,抛弃队列中的线程
      • CallerRunsPolicy:线程池创建者执行
        • 线程池创建者执行;启动线程超过最大线程池数量+队列数量,如果没有空闲线程会由线程池创建者执行

七、安全集合

1. Queue接口

1.1 ConcurrentLinkedQueue(实现类)
  • 线程安全,可高效读写的队列,高并发下性能最好的队列
  • 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
1.2 BlockingQueue(子接口)
  • 阻塞队列,增加了两个线程状态为无限期等待的方法
  • 方法
    • void put(E e)
      • 将指定元素插入此队列中,如果没有可用空间,则等待
    • E take()
      • 获取并移除此队列头部元素,如果没有可用元素,则等待
//生产者,消费者问题
1.2.1 ArrayBlockingQueue(实现类)
  • 阻塞队列
  • 数组结构实现,有界队列。(手动固定上限)
1.2.1 LinkedBlockingQueue(实现类)
  • 阻塞队列
  • 链表结构实现,有界队列。(默认上线Integer.MAX_VALUE)

2.CopyOnWriteArrayList

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

3.CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
  • 如存在元素,则不添加(扔掉副本)

4.ConCurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计
  • 不对整个Map加锁,而是为每个Segment加锁
  • 当多个对象存入同一个Segment时,才需要互斥
  • 最理想状态为16个对象分别存入16个Segment,并行数量为16
  • JDK1.8改为CAS无锁算法

八、Callable接口

语法

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

对比Runnable

Runnable没有异常抛出,没有返回值

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

九、Future接口

概念

异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值。可与Callable配合使用

方法

V get():以阻塞形式等待Future中的异步畜栏里结果(call()的返回值)

//需求:使用两个线程,并发计算1-50,51-100的和,再进行汇总统计

十、CAS算法

  • CAS:Compare And Swap(比较交换算法)
    • 可以说是并发包的底层实现原理,Java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败
    • 其实现方式是基于硬件平台的汇编指令(compxchg),是靠硬件来实现的,效率高
    • 并且比较和交换过程是同步的
    • compareAndSwap(V,E,N)方法包含三个核心参数
      • V:要更新的变量
      • E:预期值
      • N:新值
      • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作

十一、乐观锁和悲观锁

乐观锁

总是认为线程不安全,不管什么情况都进行加锁,要是获取锁失败,就阻塞。

CAS是乐观锁

悲观锁

总是认为是线程安全,不怕别的线程修改变量,如果修改了再重新尝试,直到成功。

synchronized就是悲观锁

十二、Collections中的工具方法

  • 可查api

十三、并发工具类

1. CountDownLatch(闭锁)

  • 方法

    • await():倒计数到零之前等待
    • countDown():递减锁存的计数,如果到达零则释放所有等待线程
    • getCount():返回当前计数
  • 同步计数器,初始化的时候传入需要计数的线程等待数

  • 作用

    • 闭锁可以用来确保某些操作直到其他活动都完成才能继续执行
//模拟所有员工都到达后,老板开始开会
public class TestCountDownLatchDemo01 {

    public static void main(String[] args) {
        //创建5个线程,模拟5个员工
        CountDownLatch cd = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new Employe(cd, (new Random().nextInt(10)+1)*1000).start();
        }
        new Boss(cd).start();

    }


    static class Boss extends Thread{
        //定义计数器
        private CountDownLatch bc ;

        public Boss(CountDownLatch count){
            this.bc = count;
        }

        @Override
        public void run() {
            System.out.println("老板要开始会了");
            try {
                //计数器倒计数到零之前等待
                bc.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("所有员工都到齐了");
            System.out.println("可以开会了");
        }
    }
    static class Employe extends Thread{
        private CountDownLatch cd;
        private long time;
        public Employe(CountDownLatch cd,long time){
            this.cd = cd;
            this.time = time;
        }

        @Override
        public void run() {
            try {
                sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"员工到了");
            cd.countDown();
        }
    }
}

2. CyclicBarrier(屏障)

  • 构造函数:CyclicBarrier(int partied) 屏障拦截的线程数量

  • 作用

    • 让一组线程在到达一个屏障时被阻塞,等到最后一个线程到达屏障点,才会运行被拦截的线程继续运行
  • 方法

    • await():调用该方法时,表示线程已经到达屏障,随即阻塞
//实现多个线程同时执行
public class Demo03 {
    public static void main(String[] args) {
        //创建屏障,屏障结束后执行的任务
        CyclicBarrier cb = new CyclicBarrier(5,new Runnable(){
            @Override
            public void run() {
                System.out.println("都准备好了");
            }
        });

        for (int i = 0; i < 5; i++) {
            new MyThread(cb,(i+1)*3000).start();
        }
    }
    
    static class MyThread extends Thread{
        private CyclicBarrier cb;//创建屏障
        private long time;
        public MyThread(CyclicBarrier cb,long time){
            this.cb = cb;
            this.time = time;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(time);//延时
                System.out.println(Thread.currentThread().getName()+"到了。。正在等待。。");
                cb.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

3. Semaphore(信号量)

  • Semaphore是Synchronized或Lock的加强版
  • 作用
    • 用来控制同时访问特定资源的线程数量,通过协调保证合理的使用公共资源
  • 方法
    • acquire()
      • 从信号量获取一个许可
    • release()
      • 释放一个许可,将其返回给信号量
//案例:使用Semaphore信号量控制并发的个数

4. Exchange(交换器)

  • 类似于一个交换器,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据,因此第一个线程的数据进入到第二个线程中,第二个线程的数据进入到第一个线程中。
//案例:实现两个线程交换数据

十四、 多线程的三个特征

补充:多线程要保证并发线程正确执行,必须要保证三个特性

1. 原子性(互斥性)

一个或多个操作不能被分割,要么全部执行,要么就都不执行

2. 可见性

多线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值

3. 有序性

程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序和编写顺序一致。但最终结果时一致的。

  • synchronized可保证原子性和可见性,但不能保证有序性
  • volatile可保证可见性和禁止指令重排,但不能保证原子性。(不稳定,高变化。不缓存每次都在堆中取数)Lock接口间接借助了volatile关键字间接地实现了可见性和有序性
问题:i++是原子操作吗?
    不是。
    //可使用以下方式
    AtomicInteger a = new AtomicInteger();
	a.getAndIncrement();//使用CAS算法(i++)
public class PlusDemo {
    private int num=0;
    AtomicInteger atomicInteger=new AtomicInteger();
    //并发特别高的情况下。用来计数,统计getNum方法执行的次数
    LongAdder longAdder=new LongAdder();

    // i++ 包含三步,(1)读取i  (2)改 执行加1  (3)结果写入i
    public int getNum()  {
//        synchronized (this){//效率低
//            return num++;
//        }
        longAdder.decrement();
        longAdder.sum();//统计数据
        return atomicInteger.getAndIncrement();//使用CAS算法
    }
}
//测试类
public class TestAtomic {
    public static void main(String[] args) {
        PlusDemo plusDemo=new PlusDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                   int r= plusDemo.getNum();
                   System.out.println(r);
                }
            }).start();
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值