Java多线程多案例全方位讲解

本章主要是把多线程这块理清楚

目录

  • 进程和线程
  • 线程创建三种方式
    • 继承Thread类(不建议使用,避免OOP单继承局限性)
      • 案例:下载图片
    • 实现Runnable接口(建议使用,方便同个对象被多个线程使用)
      • 案例:下载图片
    • 实现Callable接口(了解即可)
    • 案例:龟兔赛跑
  • 静态代理
  • Lambda表达式
    • 函数式接口
    • Lambda表达式的演变
      • 创建对象调方法
      • 静态内部类
      • 局部内部类
      • 匿名内部类
      • 改为Lamda表达式
  • 线程状态
    • 线程停止 stop
    • 线程休眠 sleep
    • 线程礼让 yield
    • 线程强制执行 join
    • 观测线程状态
    • 线程优先级
    • 守护线程
  • 线程同步
    • 同步方法
      • 案例:排队买票
    • 同步块
      • 案例:银行取钱
      • 案例:安全集合
      • 同步方法或同步代码块中的锁对象是什么
    • 死锁
      • 案例:口红与镜子
    • Lock锁
      • 案例:排队买票
  • 线程协作 生产者消费者模式
    • 管程法 利用缓冲区
    • 信号灯法 使用标志位
  • 线程池

进程和线程

  • 进程是系统资源分配的单位,线程是CPU调度和执行的单位

线程创建三种方式

继承Thread类(不建议使用,避免OOP单继承局限性)

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
public class ThreadTest extends Thread{
    //线程入口点
    @Override
    public void run(){
        //线程体
        for (int i = 1; i <= 5; i++) {
            System.out.println("已经执行"+i+"秒");
        }
    }
    public static void main(String[] args) {
        //创建线程对象
        ThreadTest threadTest=new ThreadTest();
        threadTest.start();
    }
}
//输出结果:
已经执行1秒
已经执行2秒
已经执行3秒
已经执行4秒
已经执行5

案例:下载图片

public class ThreadTest extends Thread{
    private String url;
    private String name;

    public ThreadTest(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run(){
        WebDownloader webDownloader=new WebDownloader();
        try {
            webDownloader.downloader(url,name);
            System.out.println("已下载此文件:"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThreadTest t1=new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p1.png");
        ThreadTest t2=new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p2.png");
        ThreadTest t3=new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p3.png");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}
//文件下载工具类
class WebDownloader{
    //远程路径,存储名称
    public  void downloader(String url,String name) throws IOException {
        FileUtils.copyURLToFile(new URL(url),new File(name));
    }
}
//输出结果:
已下载此文件:src/img/p3.png
已下载此文件:src/img/p2.png
已下载此文件:src/img/p1.png

实现Runnable接口(建议使用,方便同个对象被多个线程使用)

  • 自定义线程类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • new Thread(传入目标对象).start()
public class RunnableTest implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("已经执行"+i+"秒");
        }
    }

    public static void main(String[] args) {
        new Thread(new RunnableTest()).start();
    }
}
//输出结果:
已经执行1秒
已经执行2秒
已经执行3秒
已经执行4秒
已经执行5

案例:下载图片

public class RunnableTest implements Runnable{
    private String url;
    private String name;

    public RunnableTest(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        WebDownloader webDownloader=new WebDownloader();
        try {
            webDownloader.downloader(url,name);
            System.out.println("已下载此文件:"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p1.png")).start();
        new Thread(new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p2.png")).start();
        new Thread(new ThreadTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p3.png")).start();
    }
}
//输出结果:
已下载此文件:src/img/p3.png
已下载此文件:src/img/p1.png
已下载此文件:src/img/p2.png

实现Callable接口(了解即可)

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务 ExecutorService executorService=Executors.newFixedThreadPool(1);
  • 提交执行 Future< Boolean > result1=ser.submit(t1);
  • 获取结果 boolean r1=result1.get();
  • 关闭服务 ser.shutdownNow();
public class CallableTest implements Callable<Boolean> {
    private String url;
    private String name;

    public CallableTest(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader=new WebDownloader();
        try {
            webDownloader.downloader(url,name);
            System.out.println("已下载此文件:"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    public static void main(String[] args) {
        CallableTest t1=new CallableTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p1.png");
        CallableTest t2=new CallableTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p2.png");
        CallableTest t3=new CallableTest("https://cdn.jsdelivr.net/gh/jasper807/picgo/cover/javaio.png","src/img/p3.png");
        //创建执行服务
        ExecutorService ex= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> result1= (Future<Boolean>) ex.submit(t1);
        Future<Boolean> result2= (Future<Boolean>) ex.submit(t2);
        Future<Boolean> result3= (Future<Boolean>) ex.submit(t3);
        //获取结果
        try{
            if (result1.get()!=null){
                boolean r1= result1.get();
                System.out.println("r1="+r1);
            }
            if (result2.get()!=null){
                boolean r2= result2.get();
                System.out.println("r2="+r2);
            }
            if (result3.get()!=null){
                boolean r3= result3.get();
                System.out.println("r3="+r3);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭服务
        ex.shutdownNow();
    }
}
//输出结果:
已下载此文件:src/img/p1.png
已下载此文件:src/img/p3.png
r1=true
已下载此文件:src/img/p2.png
r2=true
r3=true

案例:龟兔赛跑

public class Race implements Runnable{
    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")&&i%5==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag=gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+
                    "--->跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    public boolean gameOver(int steps){
        //判断是否有胜利者
        if (winner!=null){
            return true;
        }
        if (steps>=20){
            winner=Thread.currentThread().getName();
            System.out.println("winner is "+winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race=new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}
//输出结果:
兔子--->跑了1步
兔子--->跑了2步
兔子--->跑了3步
兔子--->跑了4步
乌龟--->跑了1步
乌龟--->跑了2步
乌龟--->跑了3步
乌龟--->跑了4步
乌龟--->跑了5步
乌龟--->跑了6步
乌龟--->跑了7步
乌龟--->跑了8步
乌龟--->跑了9步
乌龟--->跑了10步
乌龟--->跑了11步
乌龟--->跑了12步
乌龟--->跑了13步
乌龟--->跑了14步
乌龟--->跑了15步
乌龟--->跑了16步
乌龟--->跑了17步
乌龟--->跑了18步
乌龟--->跑了19步
winner is 乌龟

静态代理

public class StaticProxy {
    public static void main(String[] args) {
        //这个结婚案例相当于模拟线程静态代理,等同于下面
        new WeddingCompany(new Man()).getMarry();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
            }
        });
    }
}
interface Marry{
    void getMarry();
}
//真实角色
class Man implements Marry{
    @Override
    public void getMarry() {
        System.out.println("Marry...");
    }
}
//代理角色
class WeddingCompany implements Marry{
    private Marry target;

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

    @Override
    public void getMarry() {
        before();
        target.getMarry();
        after();
    }
    private void before(){
        System.out.println("Before...");
    }
    private void after(){
        System.out.println("After...");
    }
}
//输出结果:
Before...
Marry...
After...

Lambda表达式

函数式接口

  • 任何借口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 对于函数式接口,我们可以通过Lambda表达式来创建该接口对象

Lambda表达式的演变

创建对象调方法

public class LambdaTest {
    public static void main(String[] args) {
        Tom tom=new Tom();
        tom.eatApple(3);
    }
}
interface Apple{
    void eatApple(int num);
}
class Tom implements Apple{

    @Override
    public void eatApple(int num) {
        System.out.println("Tom 吃了"+ num + "个苹果");
    }
}
//输出结果:
Tom吃了3个苹果

静态内部类

public class LambdaTest {
  	//静态内部类
    static class Tom implements Apple{
        @Override
        public void eatApple(int num) {
            System.out.println("Tom吃了"+ num + "个苹果");
        }
    }
    
    public static void main(String[] args) {
        Tom tom=new Tom();
        tom.eatApple(3);
    }
}
interface Apple{
    void eatApple(int num);
}
//输出结果:
Tom吃了3个苹果

局部内部类

public class LambdaTest {
    public static void main(String[] args) {
      	//局部内部类
        class Tom implements Apple{
            @Override
            public void eatApple(int num) {
                System.out.println("Tom吃了"+ num + "个苹果");
            }
        }
        Tom tom=new Tom();
        tom.eatApple(3);
    }
}
interface Apple{
    void eatApple(int num);
}
//输出结果:
Tom吃了3个苹果

匿名内部类

public class LambdaTest {
    public static void main(String[] args) {
        Apple tomEatApple=new Apple() {
            @Override
            public void eatApple(int num) {
                System.out.println("Tom吃了"+ num + "个苹果");
            }
        };
        tomEatApple.eatApple(3);
    }
}
interface Apple{
    void eatApple(int num);
}
//输出结果:
Tom吃了3个苹果

改为Lamda表达式

  • Lambda表达式只能有一行代码的情况下才能简化成一行,如果有多行,那么就用代码块包裹
  • 用Lambda表达式的前提是接口为函数式接口
  • 多个参数也只可以去掉参数类型,要去掉就都去掉,必须加上括号
public class LambdaTest {
    public static void main(String[] args) {
        Apple tomEatApple=(int num)-> {
           System.out.println("Tom吃了"+ num + "个苹果");
        };
        //简化参数类型
        tomEatApple=(num)-> {
            System.out.println("Tom吃了"+ num + "个苹果");
        };
        //简化括号
        tomEatApple=num-> {
            System.out.println("Tom吃了"+ num + "个苹果");
        };
        //简化花括号
        tomEatApple=num-> System.out.println("Tom吃了"+ num + "个苹果");
        
        tomEatApple.eatApple(3);
    }
}
interface Apple{
    void eatApple(int num);
}
//输出结果:
Tom吃了3个苹果

线程状态

Java多线程多案例全方位讲解_第1张图片

线程停止 stop

  • 不推荐使用JDK提供的stop()、destory()方法[已废弃]
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
public class StopTest implements Runnable{
    private boolean flag=true;
    @Override
    public void run() {
        while(flag){
            System.out.println("run...");
        }
    }
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) throws InterruptedException {
        StopTest stopTest = new StopTest();
        new Thread(stopTest).start();
        for (int i = 1; i <= 10; i++) {
            System.out.println("main"+i);
            if (i==5){
                stopTest.stop();
                System.out.println("run线程即将停止");
            }
        }
    }
}
//输出结果:
main1
main2
main3
main4
main5
run...
run线程即将停止
main6
main7
main8
main9
main10

线程休眠 sleep

  • 每个对象都有一把锁,sleep不会释放锁
public class SleepTest {
    //1.模拟延时
    //2.打印当前系统时间
    public static void getNowTime() throws InterruptedException {
        Date startTime=new Date(System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime=new Date(System.currentTimeMillis());
        }
    }
    //3.模拟倒计时
    public static void timeDown() throws InterruptedException {
        int num=5;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        getNowTime();
        System.out.println("---");
        timeDown();
    }
}
//输出结果:
11:10:44
11:10:45
11:10:46
11:10:47
11:10:48
---
5
4
3
2
1

线程礼让 yield

//源码
/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * 

Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * *

It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();

  • 当调用Thread.yield()的时候,会给线程调度器一个当前线程愿意出让CPU的使用的暗示,但是线程调度器可能会忽略这个暗示
public class YieldTest implements Runnable{
    public static void main(String[] args) {
        YieldTest yieldTest=new YieldTest();
        new Thread(yieldTest,"A").start();
        new Thread(yieldTest,"B").start();
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程为: "+ Thread.currentThread().getName()+ i);
            if (i == 3){
                Thread.yield();
            }
        }
    }
}
//输出结果:
//情况1 B3之后让出CPU,AB线程同时竞争,A获得CPU,礼让成功
当前线程为: B0
当前线程为: B1
当前线程为: B2
当前线程为: B3
当前线程为: A0
当前线程为: A1
当前线程为: A2
当前线程为: A3
当前线程为: A4
当前线程为: B4
//情况2 A3之后让出CPU,AB线程同时竞争,A获得CPU,没有礼让成
当前线程为: A0
当前线程为: A1
当前线程为: A2
当前线程为: A3
当前线程为: A4
当前线程为: B0
当前线程为: B1
当前线程为: B2
当前线程为: B3
当前线程为: B4

线程强制执行 join

public class JoinTest implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("vip"+i+"...");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new JoinTest());
        thread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i+"...");
            if (i==2){
                thread.join();
            }
        }
    }
}
//输出结果:
main0...
main1...
main2...
vip0...
vip1...
vip2...
vip3...
vip4...
vip5...
vip6...
vip7...
vip8...
vip9...
main3...
main4...
main5...
main6...
main7...
main8...
main9...

观测线程状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法

  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)

  • 阻塞(BLOCKED):表示线程阻塞于锁

  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)

  • 终止(TERMINATED):表示该线程已经执行完毕

public class StateTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("运行中...");
            }
        });

        Thread.State state=thread.getState();
        //NEW
        System.out.println(state);
        //RUNNABLE
        thread.start();
        state=thread.getState();
        System.out.println(state);
        //TERMINATED
        while (state!= Thread.State.TERMINATED){
            Thread.sleep(100);
            state=thread.getState();
            System.out.println(state);
        }
    }
}
//运行结果:
NEW
RUNNABLE
TIMED_WAITING
运行中...
TIMED_WAITING
运行中...
TIMED_WAITING
运行中...
TERMINATED

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
  • Thread.MIN_PRIORITY = 1; Thread.MAX_PRIORITY = 10; Thread.NORM_PRIORITY = 5
public class PriorityTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
    }
    public static void main(String[] args) {
        //主线程优先级
        System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
        PriorityTest p=new PriorityTest();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);
        Thread t4 = new Thread(p);
        Thread t5 = new Thread(p);
        //先设置优先级再启动
        t1.start();

        t2.setPriority(Thread.MAX_PRIORITY);
        t2.start();

        t3.setPriority(Thread.MIN_PRIORITY);
        t3.start();

        t4.setPriority(7);
        t4.start();

        t5.setPriority(3);
        t5.start();
    }
}
//输出结果:
main:5
Thread-1:10
Thread-4:3
Thread-3:7
Thread-2:1
Thread-0:5

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕 如:后台记录操作日志、监控内存、垃圾回收等待
public class DaemonTest {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();

        Thread thread=new Thread(god);
        thread.setDaemon(true);//默认false为用户线程,正常的线程都是用户线程

        thread.start();
        new Thread(you).start();
    }
}
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("守护你...");
        }
    }
}
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i%3==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("生活中...");
        }
        System.out.println("生活结束...");
    }
}
//输出结果:
生活中...
生活中...
生活中...
守护你...
生活中...
生活中...
生活中...
守护你...
生活中...
生活中...
生活中...
守护你...
生活中...
生活结束...
守护你...

线程同步

  • 并发:同一个对象被多个线程同时操作

  • 线程同步:处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

  • 由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

    • 存在以下问题
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

同步方法

public synchronized void method(int args) {}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

案例:排队买票

public class BuyTicket{
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        new Thread(ticket,"用户1").start();
        new Thread(ticket,"用户2").start();
        new Thread(ticket,"用户3").start();
    }
}
class Ticket implements Runnable{
    private int ticketNums=10;
    boolean flag= true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(1000);
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private synchronized void buy() {
        if (ticketNums<=0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买了第"+(10-ticketNums+1)+"张票");
        ticketNums--;
    }
}
//输出结果:
用户1买了第1张票
用户2买了第2张票
用户3买了第3张票
用户1买了第4张票
用户2买了第5张票
用户3买了第6张票
用户1买了第7张票
用户2买了第8张票
用户3买了第9张票
用户1买了第10张票

同步块

synchronized (Obj obj) {}
  • Obj称之为同步监视器,Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

案例:银行取钱

public class GoBank {
    public static void main(String[] args) {
        Account account=new Account("Marry Money",100);
        Drawing dad=new Drawing(account,50,"dad");
        Drawing mom=new Drawing(account,100,"mom");
        dad.start();
        mom.start();
    }
}

//账户
class Account{
    private String cardName;//卡名
    private int money;//余额
    public Account(String cardName, int money) {
        this.cardName = cardName;
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    public String getCardName() {
        return cardName;
    }
    public void setCardName(String cardName) {
        this.cardName = cardName;
    }
}

//银行
class Drawing extends Thread{
    private Account account;
    private int drawingMoney;
    private int nowMoney;
    private String name;

    public Drawing(Account account, int drawingMoney, String name) {
        this.account = account;
        this.drawingMoney = drawingMoney;
        this.name=name;
    }
    public void run(){
        synchronized (account){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //判断有没有钱
            if (account.getMoney()-drawingMoney<0){
                System.out.println(name+"钱不够");
                return;
            }
            System.out.println(account.getCardName()+"为"+account.getMoney());
            account.setMoney(account.getMoney()-drawingMoney);
            nowMoney=nowMoney+drawingMoney;
            //Thread.currentThread().getName()=this.getName()
            System.out.println(name+"手里的钱"+nowMoney);
            System.out.println(account.getCardName()+"余额为"+account.getMoney());
        }
    }
}
//输出结果:
Marry Money100
dad手里的钱50
Marry Money余额为50
mom钱不够

案例:安全集合

//方法1
public class SafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()-> {
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}
//输出结果:
10000
  
//方法2 使用系统设计的安全类型集合
public class SafeList {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()-> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}
//输出结果:
10000

同步方法或同步代码块中的锁对象是什么

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchonized括号里配置的对象

死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁时 , 就可能会发生死锁的问题。

案例:口红与镜子

public class DeadLock {
    public static void main(String[] args) {
        MakeUp girl1=new MakeUp(0,"girl1");
        MakeUp girl2=new MakeUp(1,"girl2");
        girl1.start();
        girl2.start();
    }
}
//口红
class LipStick{

}
//镜子
class Mirror{

}

class MakeUp extends Thread{
    //static关键字来保证一份
    static LipStick lipStick=new LipStick();
    static Mirror mirror=new Mirror();

    int choice;
    String girlName;

    public MakeUp(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    public void run(){
        //化妆
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void makeUp() throws InterruptedException {
        if (choice==0){
            //获得口红的锁
            synchronized (lipStick){
                System.out.println(girlName+"获得口红的锁");
                Thread.sleep(1000);
                //获得镜子的锁
                synchronized (mirror){
                    System.out.println(girlName+"获得镜子的锁");
                }
            }
        }else if (choice==1){
            //获得镜子的锁
            synchronized (mirror){
                System.out.println(girlName+"获得镜子的锁");
                Thread.sleep(2000);
                //获得口红的锁
                synchronized (lipStick){
                    System.out.println(girlName+"获得口红的锁");
                }
            }
        }
    }
}
//输出结果:都想拿到对方的锁导致一直卡在这里
girl1获得口红的锁
girl2获得镜子的锁

Lock锁

  • 如果同步块有异常,要将unlock()写入finally语句块

案例:排队买票

public class LockTest {
    public static void main(String[] args) {
        Ticket2 ticket=new Ticket2();
        new Thread(ticket,"用户1").start();
        new Thread(ticket,"用户2").start();
        new Thread(ticket,"用户3").start();
    }
}
class Ticket2 implements Runnable{
    //定义lock锁
    private final ReentrantLock lock=new ReentrantLock();
    private int ticketNums=10;
    boolean flag= true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(1000);
                //加锁
                lock.lock();
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
    private void buy() {
        if (ticketNums<=0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买了第"+(10-ticketNums+1)+"张票");
        ticketNums--;
    }
}
//输出结果:类比同步方法

线程协作 生产者消费者模式

  • 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖 ,互为条件
  • Java提供了几个方法解决线程之间的通信问题:
    • wait() 表示线程一直等待 , 直到其他线程通知,与sleep不同,会释放锁
    • wait(long timeout) 指定等待的毫秒数
    • notify() 唤醒一个处于等待状态的线程
    • notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
    • 均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

管程法 利用缓冲区

public class PCTest {
    public static void main(String[] args) throws InterruptedException {
        SynContainer container=new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}
//生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }
    public void run(){
        for (int i = 1; i <= 10; i++) {
            try {
                container.push(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产了第"+i+"只鸡");
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }
    public void run(){
        for (int i = 1; i <= 10; i++) {
            try {
                System.out.println("消费了第"+container.pop().id+"只鸡");
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//产品
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{
    //需要一个容器大小,也就是说这个缓冲区只能放五只鸡,生产者虽然再生产出来鸡,但是之后就会被阻塞
    Chicken[] chickens=new Chicken[5];
    //容器计数器
    int count=0;
    //生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        while (count==chickens.length){
            wait();//生产者等待
        }
        //如果没有满就丢入产品
        chickens[count]=chicken;
        count++;
        //通知消费者消费
        notifyAll();
    }
    //消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        Chicken thisChicken=null;
        //判断能否消费
        while (count==0){
            wait();//消费者等待
        }
        //如果可以消费
        count--;
        thisChicken = chickens[count];
        //吃完了,通知生产者生产
        notifyAll();
        return thisChicken;
    }
}
//输出结果:
生产了第1只鸡
生产了第2只鸡
生产了第3只鸡
生产了第4只鸡
生产了第5只鸡
消费了第5只鸡
生产了第6只鸡
生产了第7只鸡
消费了第6只鸡
生产了第8只鸡
消费了第7只鸡
消费了第8只鸡
生产了第9只鸡
消费了第9只鸡
生产了第10只鸡
消费了第10只鸡
消费了第4只鸡
消费了第3只鸡
消费了第2只鸡
消费了第1只鸡

信号灯法 使用标志位

public class PCTest2 {
    public static void main(String[] args) {
        TV tv=new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
//生产者 演员
class Player extends Thread{
    TV tv;
    public Player(TV tv) {
        this.tv = tv;
    }
    public void run(){
        for (int i = 0; i < 5; i++) {
            if (i%2==0){
                try {
                    tv.play("节目1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                try {
                    tv.play("节目2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
//消费者 观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv) {
        this.tv = tv;
    }
    public void run(){
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(300);
                tv.watch();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//产品 节目
class TV{
    String voice;
    boolean flag=true;
    //表演
    public synchronized void play(String voice) throws InterruptedException {
        if (!flag){
            wait();
        }
        System.out.println("演员表演了:"+voice);
        //通知观众观看
        notifyAll();
        this.voice=voice;
        this.flag=!this.flag;
    }
    //观看
    public synchronized void watch() throws InterruptedException{
        if (flag){
            wait();
        }
        System.out.println("观看了:"+voice);
        //通知演员表演
        notifyAll();
        this.flag=!this.flag;
    }
}
//输出结果:
演员表演了:节目1
观看了:节目1
演员表演了:节目2
观看了:节目2
演员表演了:节目1
观看了:节目1
演员表演了:节目2
观看了:节目2
演员表演了:节目1
观看了:节目1

线程池

  • JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • < T > Future< T > submit(Callable< T > task):执行任务,有返回值,一般又来执行 Callable
    • void shutdown():关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

//测试线程池 Runnable接口为例,Callable上面提到过
public class PoolTest {
    public static void main(String[] args) {
        //创建服务,创建线程池
        ExecutorService ex= Executors.newFixedThreadPool(2);
        //执行
        ex.execute(new MyThread());
        ex.execute(new MyThread());
        //关闭连接
        ex.shutdown();
    }
}
class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}
//输出结果:
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-2 0
pool-1-thread-2 1
pool-1-thread-2 2

Hi, welcome to JasperのBlog!

你可能感兴趣的:(JavaSE基础,多线程,javase,经验分享,线程池,线程安全)