浅笑博客
hhhhhhhhhhhhh
浅笑博客
Java多线程基础学习总结经验分享
Java多线程基础学习总结经验分享

本文参考学习自黑马教程《突破Java面试——多线程》

视频及资料网盘链接:https://pan.baidu.com/s/1u66wnGW1_Tx3Cxc480RVTg 提取码:but8

目录

线程创建

4种线程创建方式

  • 继承Thread类重写run方法
  • 实现Runable接口
  • 实现Callable接口
  • 线程池

代码示例:

package fun.qianxiao.multithread.creation;

import java.util.concurrent.*;

public class ThreadCreationTest {
    public static void main(String[] args) {
        //重写Thread类run方法创建线程(此处为匿名类,也可以新建类extends Thread类)
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis()+" "+i);
                }
            }
        }.start();
        //实现Runable接口创建线程(此处使用匿名接口实现,也可以新建类implements Runnable接口),创建好Runnable传给Thread
        new Thread(new Runnable(){
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis()+" "+i);
                }
            }
        }).start();
        //实现Callable接口创建线程(可以对线程进行监控管理,如调用Future接口的cancel方法取消执行线程、调用Future接口的isDone方法判断线程是否执行完成等,也可以获取线程返回结果)
        //类和接口框架图(见下)
        FutureTask<String> test = new FutureTask<String>(new Callable<String>(){
            public String call() throws Exception {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis()+" "+i);
                }
                return "Callable接口执行完成";
            }
        });
        new Thread(test,"MyCallable").start();

        //main主线程
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis());
        }

        //实现Callable接口创建线程时,可调用Future接口的get方法获取线程返回结果
        try {
            //Future.get()方法会阻塞主线程
            System.out.println("test result:"+test.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //线程池管理线程
        //类和接口框架图(见下)
        //4大线程池
        //newFixedThreadPool固定数量线程池
        //拿到线程池对象
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //线程池对象执行
        executorService.execute(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis()+" "+i);
                }
            }
        });
        executorService.shutdown();
    }
}

FutureTask类图:

http://blog.qianxiao.fun/wp-content/uploads/2020/10/未命名文件2.png

线程池相关类类图:

http://blog.qianxiao.fun/wp-content/uploads/2020/10/未命名文件1.png

一般采用实现接口而不是继承Thread类来创建线程,因为接口的好处(略)且 线程池只能放入实现Runable或Callable接口的线程的约束。

Callable接口创建线程可以通过 Future 接口对线程进行监管,且可通过get方法获取线程执行结果(此时会阻塞主线程)。

Callable接口的call方法可以抛出异常,而Runnable接口的run方法不可。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。

线程生命周期

http://blog.qianxiao.fun/wp-content/uploads/2020/10/未命名文件3.png

新建

通过new一个Thread,线程就处于新建状态。

由Java虚拟机(JVM)为线程去分配执行内存并初始化线程用到的成员变量。

就绪

调用线程对象的start方法后,线程就处于就绪状态, 准备执行了。

JVM为线程创建方法栈和程序计数器,等待线程调度器来调度让cpu来执行。

JVM内存模型

运行

就绪态时,线程调度器调度线程,将cpu的执行权限交给当前线程,当前线程获取到执行权限(cpu资源),使用cpu运行run方法,线程就处于运行状态了。

阻塞

线程调用sleep方法,主动放弃占有的cpu资源,线程阻塞。

线程调用了一个阻塞式IO方法(如读文件),此时在此方法返回前,线程就会被阻塞掉。

线程试图去获得一个同步锁,但这个锁正在被其他线程占用,它请求不到,进入阻塞,等待其他线程释放这个锁。

线程通讯时,一个线程等待某个通知时,这个线程本身就会进入阻塞态。

程序调用了线程的suspend方法(挂起),线程阻塞。(容易导致死锁,一般不主动调用)

死亡

死亡态表示线程结束了(正常结束或异常结束)。

run方法或call方法执行完毕,线程正常结束。

线程抛出一个未捕获的异常,线程死亡(异常死亡)。

程序调用线程的stop方法来结束线程。(由于调用stop方法时,锁可能没有被释放,调用后线程死亡锁永远不会被释放,其他线程永远等待锁释放,发生死锁。)

线程安全问题

当运行线程的时候,如果单线程和多线程的运行结果是一样的,且线程中变量值和预期的是一样的,就是线程安全的。反之,即如果当运行线程的时候,如果单线程和多线程的运行结果不一样的,或线程中变量值和预期不一样的,则是线程不安全的。

问题模拟

3个窗口同时卖100张票,票号1-100

package fun.qianxiao.multithread.security;

public class ThreadSecurityTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            public void run() {
                while (true){
                    if(ticket>0){
                        //模拟出票
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 售票:"+ticket--);
                    }
                }
            }
        };
        Thread thread1 = new Thread(runnable,"窗口 1");
        Thread thread2 = new Thread(runnable,"窗口 2");
        Thread thread3 = new Thread(runnable,"窗口 3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
http://blog.qianxiao.fun/wp-content/uploads/2020/10/图片-1024x636.png

问题如上运行结果截图中所标识。

问题分析

由于多个窗口同时售票,即多个线程同时并发,例如对于窗口3、2都售出19号票,如果它们睡眠100ms后恰好同时结束,此时执行ticket–操作,实际上先执行获取ticket变量值,然后对ticket值-1,然后把新值赋值给ticket变量。2个窗口同时都去获取ticket的值,此时都获取到19,所以都输出19。

对于窗口2售出0号票,假设窗口3线程执行if(ticket>0)时这时ticket=1满足,执行睡眠,此时窗口2线程执行if(ticket>0)时这时ticket=1满足 ,也执行睡眠,可还没等窗口2醒来,窗口3睡眠结束,执行了ticket–,ticket被改为0了,然后窗口2醒来后执行ticket–,结果就是售出0号票了。那售出-1号票是怎么回事呢?我们都3个窗口呢,正如上面情况 窗口3线程执行if(ticket>0)时这时ticket=1满足,执行睡眠,此时窗口2线程执行if(ticket>0)时这时ticket=1满足 ,也执行睡眠,此时窗口1在窗口3和窗口2都没执行完ticket–时也来了,它判断if(ticket>0)时这时ticket也=1也满足,然后睡眠,此时窗口3最先醒来执行ticket–,ticket值为0,然后窗口2接着醒来,ticket–,ticket=-1,然后最后窗口1也醒来了,ticket–,读取到ticket为-1,所以售出-1。

根本原因分析

由上可看出,发生问题的原因是因为ticket是一个共享变量,且ticket–操作具体实现是由多步操作完成,同时对他的读写所以造成了问题。

线程安全问题都是由全局变量及静态变量引起的。若每个线程对全局变量、静态变量只读,不写,一般来说,这个变量是线程安全的。

综上,线程安全的根本原因在于:

  • 多个线程在操作共享的数据
  • 操作共享数据的线程代码有多条
  • 多个线程对共享数据有写操作

问题解决

要解决线程安全问题,就要考虑线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作。保证了数据的同步性。

线程同步

Java7种线程同步方法:

  • 同步代码块 synchronized
  • 同步方法 synchronized
  • 同步锁 ReenreantLock
  • 特殊域变量 volatile
  • 局部变量 ThreadLocal
  • 阻塞队列 LinkedBlockingQueue
  • 原子变量 Atomic*

同步代码块(synchronized)

package fun.qianxiao.multithread.security;

public class ThreadSecurityTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            //锁对象(类型不限 对象就行),可以理解只有拿到这个锁对象(钥匙),才能执行synchronized(锁对象)的同步代码块
            private Object lock = new Object();
            public void run() {
                while (true){
                    //同步代码块 来实现限制只有一个线程同时能进入访问这段代码
                    synchronized (lock){
                        if(ticket>0){
                            //模拟出票
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+" 售票:"+ticket--);
                        }
                    }
                }
            }
        };
        Thread thread1 = new Thread(runnable,"窗口 1");
        Thread thread2 = new Thread(runnable,"窗口 2");
        Thread thread3 = new Thread(runnable,"窗口 3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步方法(synchronized)

package fun.qianxiao.multithread.security;

public class ThreadSecurityTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            public void run() {
                while (true){
                    saleTicket();
                }
            }
            /**
             * synchronized 修饰非静态方法 锁对象为this,即调用所在类的实例对象 synchronized(this){@code}
             * synchronized 修饰静态方法 锁对象为t所在类的字节码对象 synchronized(类名.class){@code}
             */
            private synchronized void saleTicket(){
                if(ticket>0){
                    //模拟出票
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" 售票:"+ticket--);
                }
            }
        };
        Thread thread1 = new Thread(runnable,"窗口 1");
        Thread thread2 = new Thread(runnable,"窗口 2");
        Thread thread3 = new Thread(runnable,"窗口 3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步锁(ReenreantLock)

package fun.qianxiao.multithread.security;

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

public class ThreadSecurityTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int ticket = 100;
            /**
             * 重入锁 当你用这个锁的时候,再去请求这个锁,依然可以拿到这个锁
             * @param fair 锁是否公平。如果公平,多个线程都公平拥有执行权。
             *             如果不公平(默认),即独占锁,一个线程拿到了如果不主动释放或执行完毕,永远拥有这个锁
             */
            private Lock lock = new ReentrantLock(true);
            public void run() {
                while (true){
                    lock.lock();//调用Lock.lock()加锁
                    try{
                        if(ticket>0){
                            //模拟出票
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+" 售票:"+ticket--);
                        }
                    }finally {
                        lock.unlock();//调用Lock.unlock()释放锁 必须保证lock调用后unlock也要被调用。所以一般使用try finally
                    }
                }
            }
        };
        Thread thread1 = new Thread(runnable,"窗口 1");
        Thread thread2 = new Thread(runnable,"窗口 2");
        Thread thread3 = new Thread(runnable,"窗口 3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

synchronized和Lock区别

  • synchronized是java关键字,是在JVM级别。Lock是Java类实现,是拜尼马级别。
  • synchronized没有办法去判断是否获取锁的状态,Lock提供方法可以判断是否获取到锁。
  • synchronized可以自动加锁、释放锁,Lock是手工加锁,手工finally中释放锁。
  • synchronized锁使用简单,但不灵活。Lock锁使用较复杂,但灵活。
  • synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
  • synchronized的锁可重入、不可中断、非公平(独占),而Lock锁可重入、可判断、可公平。
  • Lock锁适用于大量同步的代码的同步问题,synchronized锁适合少量代码的同步问题。

线程死锁

显见,多线程提高了系统资源利用率。在为了保证同步,引入了锁机制,虽然能保证同步机制,到可能会造成死锁。

http://blog.qianxiao.fun/wp-content/uploads/2020/10/未命名文件5-1024x561.png

如上图,P1正拥有R2且正取请求R1(如果请求不到R1就得不到执行,R2就不会被释放),同时P2正拥有R1且正请求R2(如果请求不到R2就得不到执行,R1就不会被释放),如此互相等待,产生死锁。

具体来说,多个线程因竞争资源而造成一种互相等待的僵局,若无外力作用,这些进程都将无法向前推进,这就是死锁。

死锁产生的必要条件

即只要系统发生死锁,这些条件必然成立,而只要条件之一不满足,就不会发生死锁。

1.互斥条件

进程要求对所分配的资源(如打印机)进行控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2.不可剥夺条件

进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来主动释放

3.请求于保持条件

进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

4.循环等待条件

即存在一种进程资源的循环等待链。

死锁示例

package fun.qianxiao.multithread.deadlock;

public class DeadLockDemo {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new DeadLock(1));
        Thread thread2 = new Thread(new DeadLock(2));
        thread1.start();
        thread2.start();
    }
}

class DeadLock implements Runnable{
    //决定线程走向的标记
    private int flag;
    //一定要加static使其只有一份(共享资源)
    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public DeadLock(int flag) {
        this.flag = flag;
    }

    public void run() {
        if(flag == 1){
            //线程1 run
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"已获取到o1,等待获取o2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"已获取到o2");
                }
            }
        }else{
            //线程2 run
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"已获取到o2,等待获取o1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"已获取到o1");
                }
            }
        }
    }
}

死锁处理

  • 预防死锁
  • 避免死锁
  • 检测死锁
  • 解除死锁
详细展开
keyboard_arrow_down

预防死锁

即取破坏死锁尝试的四大必要条件之一,严格的防止死锁的出现。

  • 破坏“互斥”条件
    互斥条件一般无法破坏,如打印机。
  • 破坏“占有并等待”条件
    即阻止已获取某资源的情况下去申请其他资源。
    • 方法1:一次性分配资源。即创建进程时,要求它申请所需的全部资源,系统或满足其所有要求,或什么也不给它。
    • 方法2:要求每个进程提出新的资源申请前,释放它所占有的资源。
  • 破坏“不可抢占”条件
    允许资源强行抢夺。
    • 方法1:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
    • 方法2:如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。只有在任意两个进程的优先级都不相同的条件下,方法二才能预防死锁。
  • 破坏“循环等待”条件
    将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。

避免死锁

避免死锁不严格限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。

  • 有序资源分配法
    • 步骤
      必须为所有资源统一编号,例如打印机为1、传真机为2、磁盘为3等。
      同类资源必须一次申请完,例如打印机和传真机一般为同一个机器,必须同时申请。
      不同类资源必须按顺序申请。
    • 例如,有两个进程P1和P2,有两个资源R1和R2
      P1请求资源:R1、R2
      P2请求资源:R1、R2 这样就破坏了环路条件,避免了死锁的发生
  • 银行家算法
    银行家算法的基本思想是分配资源之前,判断系统是否是安全的;若是,才分配。它是最具有代表性的避免死锁的算法。
    http://blog.qianxiao.fun/wp-content/uploads/2020/10/图片-1.png
  • 顺序加锁
    当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。
    按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要事先知道所有可能会用到的锁,但总有些时候是无法预知的,所以该种方式只适合特定场景。
  • 限时加锁
    限时加锁是线程在尝试获取锁的时候加一个超时时间,若超过这个时间则放弃对该锁请求,并回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。

检测死锁

预防和避免死锁系统开销大且不能充分利用资源,更好的方法是不采取任何限制性措施,而是提供检测和解脱死锁的手段,这就是死锁检测和恢复。

解除死锁

  • 利用抢占恢复
    临时将某个资源从它的当前所属进程转移到另一个进程
    这种做法很可能需要人工干预,主要做法是否可行需取决于资源本身的特性。
  • 利用回滚恢复
    周期性的将进程的状态进行备份,当发现进程死锁后,根据备份将该进程复位到一个更早的,还没有取得所需的资源的状态,接着就把这些资源分配给其他死锁进程。
  • 通过杀死进程恢复
    最直接简单的方式就是杀死一个或若干个进程。
    尽可能保证杀死的进程可以从头再来而不带来副作用。

线程通讯

多个线程并发执行时,在默认情况下CPU是随机切换线程的,有时我们希望CPU按我们的规律执行线程,此时就需要线程之间协调通信。

线程通讯方式:

  • 休眠唤醒方式
    • Object的wait、notify、notifyAll
    • Condition的await、signal、signalAll
  • CountDownLatch 用于某个线程等待若干其他线程执行完之后再执行
  • CyclicBarrier 一组线程等待至某个状态之后再全部同时执行
  • Semaphore 用于控制对某组资源的访问权限

代码示例1(多线程打印奇偶数(休眠唤醒 Object 方法)):

package fun.qianxiao.multithread.communication;

public class ThreadCommunicationTest {
    private Object obj = new Object();
    private int i;//0
    
    public void odd(){
        while(i < 10){
            synchronized(obj){
                if(i % 2 == 1){
                    System.out.println("奇数:"+i++);
                    obj.notify();//唤醒偶数线程打印
                }else{
                    try{
                        obj.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    public void even(){
        while(i < 10){
            synchronized(obj){
                if(i % 2 == 0){
                    System.out.println("偶数:"+i++);
                    obj.notify();//唤醒奇数线程打印
                }else{
                    try{
                        obj.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    public static void main(String[] args) {
        //多线程打印奇偶数(休眠唤醒)
        final ThreadCommunicationTest test = new ThreadCommunicationTest();
        
        Thread thread1 = new Thread(new Runnable(){
            public void run(){
                test.odd();
            }
        });
        Thread thread2 = new Thread(new Runnable(){
            public void run(){
                test.even();
            }
        });
        thread1.start();
        thread2.start();
    }
}

注:Object的wait、notify、notifyAll 要依赖synchronized

执行结果:

http://blog.qianxiao.fun/wp-content/uploads/2020/11/图片.png

代码示例2 (多线程打印奇偶数(休眠唤醒 Condition 方法)) :

package fun.qianxiao.multithread.communication;

import java.util.concurrent.locks.*;

public class ThreadCommunicationTest {
    private Lock lock = new ReentrantLock();//独占锁
    private Condition condition = lock.newCondition();
    private int i;//0
    
    public void odd(){
        while(i < 10){
            lock.lock();
            try{
                if(i % 2 == 1){
                    System.out.println("奇数:"+i++);
                    condition.signal();//唤醒偶数线程打印
                }else{
                    try{
                        condition.await();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }finally{
                lock.unlock();
            }
        }
    }
    
    public void even(){
        while(i < 10){
            lock.lock();
            try{
                if(i % 2 == 0){
                    System.out.println("偶数:"+i++);
                    condition.signal();//唤醒奇数线程打印
                }else{
                    try{
                        condition.await();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }finally{
                lock.unlock();
            }
        }
    }
    
    public static void main(String[] args) {
        //多线程打印奇偶数(休眠唤醒)
        final ThreadCommunicationTest test = new ThreadCommunicationTest();
        
        Thread thread1 = new Thread(new Runnable(){
            public void run(){
                test.odd();
            }
        });
        Thread thread2 = new Thread(new Runnable(){
            public void run(){
                test.even();
            }
        });
        thread1.start();
        thread2.start();
    }
}

注:Condition.await()必须和Lock配合使用

结果同上。

代码示例3(CountDownLatch):

原理:使用计数器,计数器初始值为要等待的线程数,每个被等待线程执行会执行计数器-1操作,直到计数器值为0时唤醒等待线程。

示例场景:教练等所有选手到场且准备好后开始训练

package fun.qianxiao.multithread.communication;

import java.util.concurrent.CountDownLatch;

/**
 * Java多线程CountDownLatch线程通讯示例
 * 教练等所有选手到场且准备好后开始训练
 */
public class ThreadCommunicationTest3 {
    //计数器初始值(要等待3个运动员)
    private static CountDownLatch countDownLatch = new CountDownLatch(3);
    /**
     * 运动员封装
     */
    static class Athletes extends Thread{

        public Athletes(String name) {
            setName(name);
        }

        @Override
        public void run() {
            System.out.println(getName()+" 准备中");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+" 准备完毕");
            countDownLatch.countDown();
        }
    }


    static class Coach extends Thread{

        public Coach(String name) {
            setName(name);
        }

        @Override
        public void run() {
            System.out.println(getName()+" 等待运动员");
            try{
                countDownLatch.await();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(getName()+"开始训练");
        }
    }
    public static void main(String[] args) {
        Coach coach = new Coach("教练");
        Athletes[] athletes = new Athletes[]{
                new Athletes("运动员1"),
                new Athletes("运动员2"),
                new Athletes("运动员3")
        };
        coach.start();
        for (Athletes athlete : athletes) {
            athlete.start();
        }
    }
}

运行结果:

http://blog.qianxiao.fun/wp-content/uploads/2020/11/2020-11-06-21-45-29-的屏幕截图.png

代码示例4(CyclicBarrier实现线程通信):

CyclicBarrier底层:ReentrantLock和Condition

场景:运动员同时起跑

package fun.qianxiao.multithread.communication;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class ThreadCommunicationTest4 {
    //3个线程参与同时启动
    private CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

    public void race(){
        Athletes[] athletes = new Athletes[]{
                new Athletes("运动员1"),
                new Athletes("运动员2"),
                new Athletes("运动员3")
        };
        for (Athletes athlete : athletes) {
            athlete.start();
        }

    }
    class Athletes extends Thread{
        public Athletes(String name){
            setName(name);
        }

        @Override
        public void run(){
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+" 起跑:"+System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        new ThreadCommunicationTest4().race();
    }
}

运行结果:

http://blog.qianxiao.fun/wp-content/uploads/2020/11/2020-11-06-22-03-11-的屏幕截图.png

代码示例5(Semaphore实现线程通讯):

场景:8工人使用3机器工作

package fun.qianxiao.multithread.communication;

import java.util.concurrent.Semaphore;

public class ThreadCommunicationTest5 {
    Semaphore semaphore = new Semaphore(3);//3个机器 (资源)

    public void work(int workerNums){
        Worker[] workers = new Worker[workerNums];
        for (int i = 0; i < workerNums; i++) {
            workers[i] = new Worker("工人"+(i+1));
        }
        for (Worker worker : workers) {
            worker.start();
        }
    }

    class Worker extends Thread{
        public Worker(String name){
            setName(name);
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(getName()+" 得到机器,开始工作");
                Thread.sleep(1000);
                System.out.println(getName()+" 工作完毕 释放机器");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ThreadCommunicationTest5().work(8);
    }
}

执行结果:

工人1 得到机器,开始工作
工人2 得到机器,开始工作
工人3 得到机器,开始工作
工人1 工作完毕 释放机器
工人4 得到机器,开始工作
工人2 工作完毕 释放机器
工人5 得到机器,开始工作
工人3 工作完毕 释放机器
工人6 得到机器,开始工作
工人4 工作完毕 释放机器
工人5 工作完毕 释放机器
工人6 工作完毕 释放机器
工人8 得到机器,开始工作
工人7 得到机器,开始工作
工人8 工作完毕 释放机器
工人7 工作完毕 释放机器

Process finished with exit code 0

线程通讯小结:

1.(面试题)sleep和wait方法区别

wait是Object类的实例方法,sleep是Thread类的静态方法

wait方法必须要在synchronized中(同步方法或同步代码块)调用,sleep不需要

wait调用后会释放??对象

sleep方法调用需传入睡眠的时长,wait直接调用

wait调用后需要等待notify、notifyAll唤醒,sleep调用后要等待睡眠时间到或调用中断方法

2.wait方法和notify方法

都是Object中的方法,执行前都必须先获取锁对象

wait使当前线程进入等待状态,notify用以通知其他等待当前锁对象的线程

原创不易,如需转载清注明“来自浅笑博客 Java多线程基础学习总结经验分享(http://blog.qianxiao.fun/?p=1254)”,如有错误,感谢不吝指正,欢迎评论交流学习。

发表评论

textsms
account_circle
email

浅笑博客

Java多线程基础学习总结经验分享
本文参考学习自黑马教程《突破Java面试——多线程》视频及资料网盘链接:https://pan.baidu.com/s/1u66wnGW1_Tx3Cxc480RVTg 提取码:but8 目录 线程创建线程生命周期线程安全问题线程…
扫描二维码继续阅读
2020-11-06