多线程

码云地址

多线程概述

Java.Thread

  • 线程概述
  • 线程实现(重点)
  • 线程状态
  • 线程同步(重点)
  • 线程通信问题
  • 高级主题

简介

  • 任务
  • 进程process
  • 线程thread
  • 多线程

程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程:执行程序的一次执行过程,动态概念,系统资源调度的单位。

线程:一个进程包括多个线程,线程是CPU调度和执行的单位。

注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核服务器。如果是模拟出来的多线程,即再一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

QQ截图20210513084952

核心概念

  1. 线程是独立的执行路径;
  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  3. main()称之为主线程,为系统的入口,用于执行整个程序;
  4. 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能为人干预的。
  5. 对同一份资源操作会存在资源抢夺问题,需要加入并发控制;
  6. 线程会带来额外的开销,如CPU调度时间,并发控制开销;
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

线程创建

  • Thread class 继承Thread类(重点)
  • Runnable接口 实现Runnabel接口(重点)
  • Callable接口 实现1Callable接口(了解)

Thread

  1. 自定义线程类继承Thread类
  2. 重写**run()**方法,编写线程执行体
  3. 创建线程对象,调用**start()**方法启动线程
//创建线程方式一:继承thread类,重写run方法,调用start方法开启线程
public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程-thread-"+i);
        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        //创建一个线程对象
        TestThread01 testThread01 = new TestThread01();
        //调用start()方法开启线程
        //testThread01.run();
        testThread01.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("学习多线程-main-"+i);
        }
    }
}

总结:注意线程开启后不一定立即执行,有CPU调度执行。

QQ截图20210513092405

网图下载

//实现多线程同步下载图片
public class TestThread02 extends  Thread{
    private String url;
    private String name;

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

    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();
        webDownload.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread02 testThread01 = new TestThread02("https://i0.hdslb.com/bfs/face/6f81fe5cf95260f8d745d116d9884b0ee439f227.jpg@96w_96h_1c.webp","01.jpg");
        TestThread02 testThread02 = new TestThread02("https://i0.hdslb.com/bfs/feed-admin/10641bbc5189591221c00958f3458f33798c7caa.png","02.jpg");
        TestThread02 testThread03 = new TestThread02("https://static.tianyaui.com/global/bbs/web/static/images/nav_top_logo_35.png","03.jpg");
        testThread01.start();
        testThread02.start();
        testThread03.start();
    }

}
//下载器
class WebDownload{
    //下载方法
    public void downloader(String url,String name) {
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}
下载了文件名为:03.jpg
下载了文件名为:01.jpg
下载了文件名为:02.jpg

Runnable

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写执行体
  3. 创建线程对象,调用start()方法启动线程

推荐使用Runnable对象,因为Java单继承的局限性。

public class TestThread03 implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程-thread-"+i);
        }
    }

    public static void main(String[] args) {

        //创建runnable接口的实现类对象
        TestThread03 testThread03 = new TestThread03();
        //创建线程对象,通过线程对象开启我们的线程,代理
        new Thread(testThread03).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("学习多线程-main-"+i);
        }
    }
}

对比小结

  • 继承Thread类
    • 子类继承Thread类具备多线程能力;
    • 启动线程:子类对象.start();
    • 不建议使用:避免oop单继承局限性。
  • 实现Runnable接口
    • 实现Runnable具有多线程能力;
    • 启动线程:传入目标对象+Thread对象.start();
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

一份资源,多个代理

        new Thread(testThread03,"01").start();
        new Thread(testThread03,"02").start();
        new Thread(testThread03,"03").start();

并发问题

发现并发问题

QQ截图20210513212025
//多个线程同时操作同一个对象
    //买火车票的例子
    //多个线程操作同一个资源的情况下线程不安全,数据混乱
public class TestThread04 implements Runnable{

    private int tickNums=10;

    @Override
    public void run() {
       while (true){
           if(tickNums<=0) break;
           try {//模拟购票延时
               Thread.sleep(200);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+"==》拿到了第 "+tickNums+" 张票");
           tickNums--;
       }
    }

    public static void main(String[] args) {
        TestThread04 tick = new TestThread04();
        new Thread(tick,"冯冯").start();
        new Thread(tick,"艺艺").start();
        new Thread(tick,"见见").start();

    }
}

龟兔赛跑

//模拟龟兔赛跑
public class Race implements Runnable{

    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {

            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子")&&i%10==0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            if(gameOver(i)) break;
            System.out.println(Thread.currentThread().getName()+"跑了=>"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int step){
        if(winner!=null){
            return true;
        }else {
            if(step>=100){
                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();
    }
}

Callable(了解)

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

可以定义返回值和抛出异常

public class TestCallable implements Callable<Boolean> {
    private String url;
    private String name;

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

    @Override
    public Boolean call(){
        WebDownload webDownload = new WebDownload();
        webDownload.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://i0.hdslb.com/bfs/face/6f81fe5cf95260f8d745d116d9884b0ee439f227.jpg@96w_96h_1c.webp","01.jpg");
        TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/feed-admin/10641bbc5189591221c00958f3458f33798c7caa.png","02.jpg");
        TestCallable t3 = new TestCallable("https://static.tianyaui.com/global/bbs/web/static/images/nav_top_logo_35.png","03.jpg");
        //创建执行服务:
        ExecutorService ser= Executors.newFixedThreadPool(3);
        // 提交执行:
        Future<Boolean> result1=ser.submit(t1);
        Future<Boolean> result2=ser.submit(t2);
        Future<Boolean> result3=ser.submit(t3);
       // 获取结果:
        boolean rs1=result1.get();
        boolean rs2=result1.get();
        boolean rs3=result1.get();
        //关闭服务:
       ser.shutdownNow();
    }
}
//下载器
class WebDownload{
    //下载方法
    public void downloader(String url,String name) {
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}

Lambda

λ希腊字母第十一位字母;

避免匿名内部类定义过多;

实质是函数式编程:

    (params)->expression[表达式]
    (params)->statement[语句]
    (params)->{statements}


    a->System.out.println("lambda--->"+a);
    new Thread(()->System.out.println("多线程学习--")).start();

Functional Interface 函数式接口

函数式接口定义:

任何接口,如果只包含唯一一个抽象方法,那么它是一个函数式接口;

public interface Runnable{
    public abstract void run();
}

对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

  1. 外部类
  2. 静态内部类
  3. 局部内部类
  4. 匿名内部类
  5. lambda
//推导lambda表达式
public class TestLambda01 {
    //3.静态内部类
    static   class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("like Lambda2!");
        }
    }
    public static void main(String[] args) {
        Like like = new Like();
        like.lambda();
        Like2 like2 = new Like2();
        like2.lambda();

        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("like Lambda3!");
            }
        }
        Like3 like3 = new Like3();
        like3.lambda();

        //5.匿名内部类
        ILike like4 = new ILike() {
            @Override
            public void lambda() {
                System.out.println("like Lambda4!");
            }
        };
        like4.lambda();

        //6.简化终极版:Lambda
        ILike like5=()->{
            System.out.println("like Lambda5!");
        };
        like5.lambda();

    }
}
//1.定义一个函数式接口
interface ILike{
    void lambda();
}
//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("like Lambda!");
    }
}

再简化

public class TestLambda02 {
    public static void main(String[] args) {
        //lambda简化
/*       ILove love=(int a)->{
           System.out.println("Love->"+a);
        };
        love.love(520);
        //简化1:去掉参数类型
        love=(a)->{
            System.out.println("Love->"+a);
        };
        love.love(50);
        //简化2:简化括号
        love= a->{
            System.out.println("Love->"+a);
        };
        love.love(550);
        //简化3:去掉花括号
        love=a-> System.out.println("Love->"+a);
        love.love(521);*/
        //总结:lambda表达式只能有一行代码情况下才能简化一行,多个参数也可以都都去但要加括号
        ILove love=(a,b,c)->{
            System.out.println("Love->"+a);
            System.out.println("Love->"+b);
            System.out.println("Love->"+c);
        };
        love.love(521,502,250);

    }
}
interface ILove{
    void love(int a,int b,int c);
}

线程状态

停止stop

//测试stop
    //1.建议线程正常停止
    //2.建议使用标志位
    //3.不建议使用stop和destroy(),JDK不推荐
public class TestStop implements Runnable{
    //1.设置一个标志位
    private boolean flag=true;

    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("run....Thread"+i++);
        }
    }
    //2.设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main->"+i);
            if(i==80) {
                testStop.stop();
                System.out.println("线程终止");
            }

        }

    }

}

睡眠sleep

模拟网络延时


//模拟网络延时:放大问题发生性
public class TestSleep implements Runnable{
    private int ticketNums=10;
    @Override
    public void run() {
        while (true){
            if (ticketNums<=0) break;
            //模拟网络延迟
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--拿到了票--》"+ticketNums--);
        }
    }

    public static void main(String[] args) {
        TestSleep ticket = new TestSleep();
        new Thread(ticket,"冯冯").start();
        new Thread(ticket,"哈哈").start();
        new Thread(ticket,"呵呵").start();
    }
}

获取当前时间,同步时间

//获取当前时间
public class TestSleep02 implements Runnable{

    public void testDown(){
        int num=10;
        while (true){
            try {
                Thread.sleep(1_000);
                System.out.println( num--);
                if(num<=0) break;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {

    }

    public static void main(String[] args) {
        Date startTime = new Date(System.currentTimeMillis());
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

抢占Join

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("VIP进程"+i);
        }
    }

    public static void main(String[] args) {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        //主线程
        for (int i = 0; i < 1000; i++) {
            if(i==200){
                try {
                    thread.join();//插队
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main"+i);
        }
    }
}

礼让Yield

//礼让不一定成功,看cpu安排
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}
class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//线程礼让
        System.out.println(Thread.currentThread().getName()+"线程终止执行");
    }
}

状态监测

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

        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("//");
            }
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //观察启动后
        thread.start();
        state=thread.getState();
        System.out.println(state);
        while (state!=Thread.State.TERMINATED) {
            try {
                Thread.sleep(100);
                state=thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }
}

优先级

  • 线程优先级用数字表示,范围从1~10

    Thread.MIN_PRIORITY=1

    Thread.MAX_PRIORITY=10;

    Thread.NORM_PRIORITY=5;

  • 使用一下方式改变或获取优先级

​ getPriority().setPriority(int xxx)

//测试线程优先级
public class TestPriority{
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        //先设置完有优先级在启动
        t1.start();;

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

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



    }

}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,这都是看CPU调度。

性能倒置,默认一般优先级是5,公平竞争。

守护线程daemon

  • 线程分用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕 main
  • 虚拟机不用等待守护线程执行完毕 gc
  • 如,后台记录操作日志,监控内存,垃圾回收等。。
//测试守护线程
    //上帝守护你  人生不过三万天
public class TestDaemon {
    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){
            System.out.println("God guard You!");
        }
    }
}
//你
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 365; i++) {
            System.out.println("You live happily!"+i);
        }
        System.out.println("good Bye!");
    }
}

线程同步

同步问题是重点加难点。

并发

多线程同时操作同一份资源,出现并发问题—>需要引入同步机制来解决。

同步

天然解决办法就是排队。

多线程同时访问一个对象,并且有些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个线程同时访问时需要进入这个对象的等待池形成队列,排队等待。

队列和锁

同步两个必须可少条件:同步+锁。

每个对象都有一把锁,保护对象只能被一个获得该锁的线程单独访问。

线程同步

  • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    • 一个线程持有锁会导致其他所有需要此资源的线程挂起;
    • 多线程竞争下,加锁,释放锁会导致较多的上下文切换引起性能问题;
    • 优先级高的进程等待优先级低的进程释放锁,会发生优先级倒置性能问题。

三大不安全例子

不安全买票

//不安全的买票
    //线程不安全,有负数,有相同的票号
public class UnSafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"a").start();
        new Thread(buyTicket,"b").start();
        new Thread(buyTicket,"c").start();

    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
           buy();
        }
    }
    //买票
    private void buy(){
        //判断是否有票
        if(ticketNums<=0){
            flag=false;
            return;
        }
        //模拟延迟方法问题发生性
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到第" + ticketNums-- + "张票");
    }

}

不安全取钱

//不安全的取钱
    //两个人去银行取钱,相同账户
public class UnSafeBank {
    public static void main(String[] args) {
        Account account = new Account(100, "结婚基金");
        Drawing you = new Drawing(account, 50, "you");
        Drawing girlfriend = new Drawing(account, 70, "girlfriend");
        you.start();
        girlfriend.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+account.name+" 还剩余额 "+account.money);

    }
}
//账户
class Account{
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends Thread{
    Account account;//账户
    //取了多少钱
    int drawingMoney;
    //现在手中有多少钱
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);//继承thread方法,name为线程名字
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
       if(account.money<drawingMoney) {
           System.out.println(Thread.currentThread().getName()+"==>您的余不足");
           return;
       }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money-=drawingMoney;
        nowMoney+=drawingMoney;
        System.out.println(this.getName()+account.name + " 余额为 " + account.money);
        //this.getName()===Thread.currentThread().getName() 继承
        System.out.println(this.getName()+" 手里的钱有 " + nowMoney);

    }
}

线程不安全的集合

//线程不安全的集合

public class UnSafeList {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            //采用lambda写线程体
           new Thread(()->{
               strings.add(Thread.currentThread().getName());
           }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(strings.get(0));
        System.out.println(strings.size());

    }
}

synchronized同步

  • 通过private关键字来保证数据对象只能被方法访问;
  • 针对方法提出一套机制,synchronized关键字,包括两种方法:
    • synchronized方法
    • synchronized块
  • synchronized限制方法只有得到对象的锁才能访问问,否则阻塞,方法一旦执行,就独占该锁,直到方法返回释放该锁,后面的线程才能得到这个锁继续执行。

若将一个大的方法声明为synchronized将会很大影响效率。

同步方法的弊端

A代码:只读

B代码:修改

方法中需要修改的内容才需要锁,锁太多,浪费资源。

同步块

synchronized(Obj){}

  • Obj称之为同步监视器;
  • Obj可以是任何对象,但是推荐使用共享对象作为同步监听器;
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器的执行过程:

  1. 第一个线程访问,锁定同步监视器,执行其中代码;
  2. 第二个线程访问,发现同步监视器被访问,无法执行其中代码;
  3. 第一个线程访问完毕,解锁同步监视器;
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

买票

    //synchronized同步方法 ,锁的是this
    private synchronized void buy(){
        //判断是否有票
        if(ticketNums<=0){
            flag=false;
            return;
        }
        //模拟延迟方法问题发生性
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到第" + ticketNums-- + "张票");
    }

取钱

//synchronized加了同步之后还是不行
    //取钱
    //synchronized加了同步之后还是不行
    @Override
    public synchronized void run() {
       if(account.money<drawingMoney) {
           System.out.println(Thread.currentThread().getName()+"==>您的余不足");
           return;
       }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money-=drawingMoney;
        nowMoney+=drawingMoney;
        System.out.println(this.getName()+account.name + " 余额为 " + account.money);
        //this.getName()===Thread.currentThread().getName() 继承
        System.out.println(this.getName()+" 手里的钱有 " + nowMoney);

    }

synchronized锁的是this对象,所以锁不了账户,需要用到synchronzed块才行。

    //取钱
    //synchronized加了同步之后还是不行,默认锁的是this
    @Override
    public  void run() {
//锁的对象就是变化的量,需要增删改
        synchronized (account){

            if(account.money<drawingMoney) {
                System.out.println(Thread.currentThread().getName()+"==>您的余不足");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money-=drawingMoney;
            nowMoney+=drawingMoney;
            System.out.println(this.getName()+account.name + " 余额为 " + account.money);
            //this.getName()===Thread.currentThread().getName() 继承
            System.out.println(this.getName()+" 手里的钱有 " + nowMoney);
        }
    }

不安全集合

            //采用lambda写线程体
           new Thread(()->{
               synchronized (strings){
                   strings.add(Thread.currentThread().getName());
               }
           }).start();

问题:

必须加sleep时间,否则还是出错。

就是说如果没有延迟的话,那么主线程就会先执行,还没有等子线程修改完数据,就已经输出了,所以需要主线程延时一定时间等子线程跑完先。

JUC

//测试JUC安全类型的集合,CopyOnWriteArrayList是JAVA类下的并发包
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }

死锁

多个对象各自独占一些资源,相互等待对方手里资源才能运行,导致多个线程都在等待对方释放导致线程都停止执行。

某个同步块同时拥有**“两个以上对象的锁”**时,就可以能发生死锁。

//死锁:多个线程互相持有对方手里资源并请求对方资源然后陷入死锁
public class TestDeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0, "g1");
        MakeUp g2 = new MakeUp(1, "g2");
        g1.start();
        g2.start();

    }
}
//口红
class Lipstick{

}
//镜子
class Mirror{

}
class MakeUp extends Thread{
    //需要的资源只有一份,用static 保证
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();

    int choice;//选择

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

    @Override
    public void run() {
        //化妆
        makeup();
    }
    //互相持有对方的的资源,并相互请求对方资源
    private void makeup(){
       if(choice==0){
           synchronized (lipstick){//获得口红的锁
               System.out.println(this.getName()+" 获得口红");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

               //一秒后想获得镜子
               synchronized (mirror){//获得口红的锁
                   System.out.println(this.getName()+" 获得镜子");
               }

           }


       }else {
           synchronized (mirror){
               System.out.println(this.getName()+" 获得镜子");
               try {
                   Thread.sleep(2000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
              //放到同步里面,表示,请求lipstick的同时占有mirror的锁
               synchronized (lipstick){
                   System.out.println(this.getName()+" 获得口红");
               }

           }

       }

    }
}

不同时拥有两把锁

    @Override
    public void run() {
        //化妆
        makeup();
    }
   //互相持有对方的的资源,并相互请求对方资源
    private void makeup(){
       if(choice==0){
           synchronized (lipstick){//获得口红的锁
               System.out.println(this.getName()+" 获得口红");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           }
           //一秒后想获得镜子
           synchronized (mirror){//获得口红的锁
               System.out.println(this.getName()+" 获得镜子");
           }


       }else {
           synchronized (mirror){
               System.out.println(this.getName()+" 获得镜子");
               try {
                   Thread.sleep(2000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           } 
           //放到同步外面,表示,请求lipstick后可以释放mirror这把锁
           synchronized (lipstick){
               System.out.println(this.getName()+" 获得口红");
           }

       }

    }

总结:不能同时拥有两个对象锁,避免陷入死锁,请求与保持。

Lock同步

可重入锁:

  1. JDK1.5开始,java提供了更强大的线程同步机制通过显示定义同步锁对象实现同步。同步锁使用LOCK对象;
  2. Lock接口是控制多个线程对共享资源访问的工具,锁提供了对共享资源的独占访问,每次只有一个线程对Lock对象就锁,线程访问共享资源应该先获得Lock锁;
  3. ReentrantLock类实现了Lock,它拥有与synchronize相同的并发性和语义,在实现线程安全控制的控制中比较常用,可以显示的加锁,释放锁。

try catch快捷键:Ctrl+Alt+T

ReentrantLock显示加锁和释放锁:

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        new Thread(myLock,"a").start();
        new Thread(myLock,"b").start();
        new Thread(myLock,"c").start();

    }
}
class MyLock implements Runnable{
    int ticketNums=10;
    //定义可重入锁
    private final ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
       while (true){

           try {
               lock.lock();//加锁
               if(ticketNums<=0) {
                   System.out.println("票已卖完");
                   break;
               }else {
                   try {
                       Thread.sleep(200);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"正在买第 " + ticketNums-- + " 张票");
               }

           } finally {
               //解锁
               lock.unlock();
           }


       }
    }
}

对比

  • Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放;

  • lock只有代码块锁,synchronized有代码锁和方法锁;

  • 使用lock锁,jvm将花费较少时间调度线程,性能更好,并且拓展性好,有更多子类;

  • 使用顺序:

    Lock->同步代码块(已经进入方法体分配资源)->同步方法

线程通信

线程协作

生产者消费者模式

QQ截图20210514181052

这是一个线程同步问题。

在生产者消费者为中,仅synchronized是不够的:

  • synchronized阻止并发更新同一个共享资源,实现了同步;
  • synchronized不能用来实现不同线程之间的消息传递。

Java中提供了几个方法解决线程之间的通信问题:

wati():一致等待其他线程通知,与sleep不同会释放锁;

wait(long timeout):指定等待毫秒数;

notify():唤醒等待线程;

notifyAll():唤醒同一个对象上所有调用wait()的线程,优先级高的优先调度。

注:均是Object类方法,都只能在同步方法或同步代码块中使用,否则会抛出异常。

ILLegalMonitorStateException

管程法

通过一个缓冲区判断是否已经满了。

生产者将生产好的数据(可能是方法,对象,线程,进程)放入缓冲区;

消费者从缓冲区拿出数据。

//测试:生产者消费者模型--》利用缓冲区解决:管程法
    //生产者,消费之,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContain synContain = new SynContain();
        new Producer(synContain).start();
        new Customer(synContain).start();
    }
}
//生产者
class Producer extends Thread{
    SynContain synContain;

    public Producer(SynContain synContain) {
        this.synContain = synContain;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synContain.push(new Chicken(i));
            System.out.println("生产了第 " + i + " 只鸡");
        }
    }
}
//消费者
class Customer extends Thread{
    SynContain synContain;

    public Customer(SynContain synContain) {
        this.synContain = synContain;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第 " + synContain.pop().id + " 只鸡");
        }
    }
}
//产品
class Chicken{
   int id;

    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContain{
    //需要一个容器大小
    Chicken[] chickens=new Chicken[10];
    int count=0;
    //生产者放入
    public synchronized void push(Chicken chicken){
        //如果容器满了,通知消费者消费,生产者等待
        if(count==chickens.length){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满就生产
        chickens[count]=chicken;
        count++;
        this.notifyAll();
    }
    //消费者消费
    public synchronized Chicken pop(){
        //如果容器为0,通知生产者生产,消费者等待
        if(count==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果不为0就消费
        count--;
        Chicken chicken = chickens[count];
        //通知生产者已消费
        this.notifyAll();
        return chicken;
    }
}

信号灯法

通过一个标志位来判断。

//测试生产者消费者问题2:通过标志位解决,信号灯法
public class TestPC2 {
    public static void main(String[] args) {
        Programme programme = new Programme();
        new Actor(programme).start();
        new Audience(programme).start();
    }
}
//演员-生产者
class Actor extends Thread{
    Programme programme;

    public Actor(Programme programme) {
        this.programme = programme;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2==0)
                this.programme.play("陈情令播放中...");
            else
                this.programme.play("广告播放中...");

        }
    }
}
//观众-消费者
class Audience extends Thread{
    Programme programme;

    public Audience(Programme programme) {
        this.programme = programme;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
           this.programme.watch();
        }
    }
}
//节目-产品
class Programme{
    //演员表演,观众等待
    //观众观看,演员等待
    String  TVName;//表演的节目
    boolean flag=true;

    //演
    public synchronized void play(String TVName){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了 " + TVName);
        //通知观众观看
        this.notifyAll();//唤醒
        this.TVName=TVName;
        this.flag=!this.flag;
    }

    //看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了 " + TVName);
        //通知演员表演
        this.notifyAll();
        this.flag=!this.flag;
    }
}

线程池

背景:经常创建和销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池找中,使用时取出,用完放回,这样就可以避免频繁创建和销毁,实现重复利用。

好处:

  1. 提高响应速度(减少线程创建时间);
  2. 减低资源消耗(重复利用,不需要每次创建);
  3. 便于线程管理:

​ corePoolSize:核心池大小;

​ maximumPoolSize:最大线程数;

​ keepAliveTime: 线程没有任务时最长保存多长时间后终止。

JDK1.5提供线程池相关的API:ExectorService和Executors

ExectorService

真正的线程池接口,常见子类有ThreadPoolExecutor

void execute(Runnable command):执行Runnable任务命令;

Future submit(Callable task):执行Callable任务

void shutdowm:关闭线程池

Executors

工具类,线程池的工厂类,用户创建并返回不同类型的线程池。

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        //1.创建池子
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2.启动
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //3.关闭连接
        service.shutdown();


    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "正在执行...");
    }
}

总结

线程创建

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

同步与通信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值