码迷,mamicode.com
首页 > 编程语言 > 详细

多线程

时间:2020-12-07 11:52:51      阅读:15      评论:0      收藏:0      [点我收藏+]

标签:过多   进程   主题   静态代理   运行   struct   打印   ide   write   

多线程

一、线程简介

技术图片

1. Process与Thread

  • 程序-----(运行)------>进程---------->线程

    • 一个进程中包含若干个线程,线程是CPU调度和执行的单位
    • mian即主线程
  • 线程是独立的执行路径

  • 程序运行时,即使自己没有创建线程,后台也会有多个线程---主线程,gc线程

  • 一个进程中,如果开辟多个线程,线程的调度由调度器安排调度,而调度去与系统相关,人为无法干预

  • 对同一份资源,操作时,会存在资源抢夺的问题,需要加入并发控制

  • 线程会带来额外开销,如CPU调度时间,并发控制开销

  • 每个线程只在自己的工作内存交互,互不干预

二、线程的实现(重点)

1. 线程的创建(三种方式)

技术图片

① Thread类

  • 自定义线程类继承Tread类

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用start()方法其拆散多线程

    //继承Thread类
    public class TestThread1 extends Thread {
        //run方法——TestTread1线程
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("这是Testread1线程"+i);
            }
        }
    
        //main方法——主线程
        public static void main(String[] args) {
            TestThread1 testThread1 = new TestThread1();//创建线程
            testThread1.start();//开启多线程
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("这是主线程"+i);
            }
        }
    }
    

    线程开启不一定立即执行,由CPU调度执行,人为无法干预

    技术图片

    testThread1.start()开启多线程后,线程时交替执行的

    而testThread1.run()是调用TestThread1类中的run()方法,顺序执行,因此run执行完后才执行main

    public class TestThread1 extends Thread {
        //run方法——TestTread1线程
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("这是Testread1线程"+i);
            }
        }
    
        //main方法——主线程
        public static void main(String[] args) {
            TestThread1 testThread1 = new TestThread1();
            testThread1.run();//调用run方法
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("这是主线程"+i);
            }
        }
    }
    
    技术图片
下载图片

用多线程下载架包

  • 将commons-io 2.6拷仅idea的lib目录

  • 右键add as library

    技术图片
    //练习Thread,实现多线程同步下载图片
    public class TestThread2 extends Thread{
    
        private String url;//网络图片地址
        private String name;//保存的文件名
    
        public TestThread2(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public void run() {
            WebDownloader webDownloader = new WebDownloader();
            webDownloader.downloader(url,name);
            System.out.println("下载文件名为:"+name);
        }
    
        public static void main(String[] args) {
            TestThread2 t1 = new TestThread2("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_1.jpeg","aaa1.jpg");
            TestThread2 t2 = new TestThread2("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_2.jpeg","aaa2.jpg");
            TestThread2 t3 = new TestThread2("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_3.jpeg","aaa3.jpg");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    技术图片

    下载的顺序不是按t1,t2,t3,开启多线程后而是由系统自动调度分配

② Runnable接口

  • 定义MYRunnable类实现Runable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

    public class TestThread3 implements Runnable{
        //重现run方法
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("这是子线程"+i);
            }
        }
    
        public static void main(String[] args) {
            //创建runnable接口的实现对象
            TestThread3 testThread3 = new TestThread3()
            //创建线程对象——代理
            Thread thread = new Thread(testThread3);
            //开启线程
            thread.start();
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程"+i);
            }
        }
    }
    
    技术图片
thread类和runnable接口的区别
技术图片
多个线程同时操作同一个对象
  • 线程不安全

    public class TestThread4 implements Runnable{
        private int ticketNum = 10;
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <=0){
                    break;
                }
                //延迟
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //Thread.currentThread().getName()获取姓名
                System.out.println(Thread.currentThread().getName()+"抢到了第"+ (ticketNum--)+"张票");
            }
        }
    
        public static void main(String[] args) {
            TestThread4 ticket = new TestThread4();
            new Thread(ticket,"小明").start();
            new Thread(ticket,"小红").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    

    技术图片

龟兔赛跑
//龟兔赛跑
public class TestThread5 implements Runnable {

    private static String winner;//胜利者
    private final int LENGTH = 1000;//赛道长度

    //设置赛道
    @Override
    public void run() {
        for (int i = 0; i <= LENGTH; i++) {
            //判断比赛是否结束
            boolean flag = this.gameover(i);
            //如果结束,退出循环
            if (flag){
                break;
            }

            //比赛进度
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");

            //模拟兔子的状态
            if (Thread.currentThread().getName().equals("兔子")) {//不能用 == "兔子"
                //模拟兔子跑步比乌龟快
                i += 49;

                //模拟兔子休息
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //判断比赛是否结束方法
    private boolean gameover(int steps) {
        //已经有胜利者了,返回true
        if (winner != null) {
            return true;
        } else if (steps>=LENGTH){
            winner = Thread.currentThread().getName();
            System.out.println(winner+"赢得了比赛");
            return true;
        }
        //无胜利者,返回false
        return false;
    }

    //主线程
    public static void main(String[] args) {
        TestThread5 race = new TestThread5();
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}
技术图片

③ Callable接口

  • 实现Callable接口,需要返回值类型

  • 重现call方法,需要抛出异常

  • 创建目标对象

  • 创建执行服务

  • 提交执行

  • 获取结果

  • 关闭服务

    public class TestCallable implements Callable{
        private String url;
        private String name;
    
        public TestCallable(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        //重写call方法
        @Override
        public Boolean call() throws Exception {
            this.download(url,name);
            System.out.println("下载文件名为:"+name);
            return true;
        }
    
        //下载方法
        public void download(String url, String name) {
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            TestCallable t1 = new TestCallable("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_1.jpeg","bbb1.jpg");
            TestCallable t2 = new TestCallable("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_2.jpeg","bbb2.jpg");
            TestCallable t3 = new TestCallable("https://04imgmini.eastday.com/mobile/20201124/20201124125422_b97573c574f48a6af5f5c5c9a9beea1b_3.jpeg","bbb3.jpg");
    
            //创建执行服务
            ExecutorService ser = Executors.newFixedThreadPool(3);
    
            //提交执行
            Future<Boolean> submit1 = ser.submit(t1);
            Future<Boolean> submit2 = ser.submit(t2);
            Future<Boolean> submit3 = ser.submit(t3);
    
            //获取结果
            Boolean rs1 = submit1.get();
            Boolean rs2 = submit2.get();
            Boolean rs3 = submit3.get();
    
            System.out.println("bbb1.jpg is "+rs1);//可以打印返回值
            System.out.println("bbb2.jpg is "+rs2);
            System.out.println("bbb3.jpg is "+rs3);
    
            //关闭服务
            ser.shutdownNow();
        }
    
    }
    
    技术图片
Callable的好处
  • 可以获得返回值
  • 可以抛出异常

2. Lambda表达式

  • 函数式编程
  • 避免匿名内部类定义过多
  • 可以让代码变得简洁,去掉无意义的代码,保留核心逻辑

函数式接口

  • 一个接口只有唯一的一个抽象方法

    由于这个接口中只有这个方法,因此相比匿名内部类,可以实现除了类名,连方法名都可以 省略

    public interface Runnable{
        public abstruct run();
    }
    
  • 推导lamda表达式(逐步简化)

    关于内部类

    public class TestLambda {
    
        //成员内部类
        class Like2 implements ILike {
            @Override
            public void show() {
                System.out.println("接口实现方式——成员内部类");
            }
        }
    
        //静态内部类
        static class Like3 implements ILike {
            @Override
            public void show() {
                System.out.println("接口实现方式——静态内部类");
            }
        }
    
    
        public static void main(String[] args) {
            //1.正常操作 —— 在外部写接口的实现类
            new Like1().show();
    
            //2. 成员内部类 —— 将实现类写到内部
            TestLambda testLambda = new TestLambda();
            testLambda.new Like2().show();
    
            //3. 静态内部类
            new Like3().show();
    
            //4. 局部内部类
            class Like4 implements ILike {
                @Override
                public void show() {
                    System.out.println("接口实现方式——局部内部类");
                }
            }
            new Like4().show();
    
    
            //5. 匿名内部类
            new ILike() {
                @Override
                public void show() {
                    System.out.println("接口实现方式——匿名内部类");
                }
            }.show();
    
            //6. lambda简化
            ILike like = ()->{
                System.out.println("接口的实现方式——lambda表达式");
            };
            like.show();
    
        }
    
    }
    
    //定义一个函数式接口
    interface ILike {
        void show();
    }
    
    //接口实现类
    class Like1 implements ILike {
        @Override
        public void show() {
            System.out.println("接口实现方式——写实现类(外部)");
        }
    }
    
  • 示例2

    public class TestLambda2 {
        public static void main(String[] args) {
    
            //匿名内部类
            new Meals() {
                @Override
                public void show(String name, String food, int nums) {
                    System.out.println(name+"吃了"+nums+"份"+food);
                }
            }.show("早餐","面包",3);
    
            //lambda表达式
            Meals meals;
    
            meals = (a,b,c)->{
                System.out.println(a+"吃了"+c+"份"+b);
            };
            meals.show("早餐","面包",3);
    
            //简化lambda表达式
            meals = (a,b,c) -> System.out.println(a+"吃了"+c+"份"+b);
            meals.show("早餐","面包",3);
    
        }
    
    }
    
    interface Meals{
        void show(String name, String food, int nums);
    }
    

    如果方法重载了还满足lambda只有一个方法的要求吗?

    • 不行

      技术图片

3. 静态代理模式

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象要代理真实角色

    不改变原有代码,去实现新的功能

    • 静态代理模式——对方法的增强
    • 装饰器模式——对对象的增强

好处

  • 代理对象可以做很多真实对象做不了的事

  • 真实对象专注做自己的事

    public class StaticProxy {
        public static void main(String[] args) {
            People you = new People();
            new WeddingCompany(you);
        }
    }
    
    interface Merry{
        void merry();
    }
    
    //你——真实对象
    class People implements Merry{
        @Override
        public void merry() {
            System.out.println("我终于结婚了,超开心TAT");
        }
    }
    
    //婚庆公司——代理
    class WeddingCompany implements Merry{
        private Merry customer;
    
        public WeddingCompany(Merry customer) {
            this.customer = customer;
            this.merry();
        }
    
        @Override
        public void merry() {
            berfore();
            customer.merry();
            after();
        }
    
        private void berfore() {
            System.out.println("结婚前,布置婚礼");
        }
    
        private void after() {
            System.out.println("结婚后,收取尾款");
        }
    }
    /*
    结婚前,布置婚礼
    我终于结婚了,超开心TAT
    结婚后,收取尾款
    */
    
  • 与多线程创建类比

    //静态代理模式
    new WeddingCompany(new People()).merry();
    //线程创建也构成静态代理的要求
    new Thread( ()->System.out.println("love")).start();
    
    • 真实对象和代理对象都要实现同一个接口

      Thread 实现 Runnable接口

      lambda表达式为一个匿名的实现类也是实现 Runnable接口

    • 代理对象要代理真实角色

      Thread真实代理lambda表达式的匿名类

      ? 技术图片

三、线程状态

1.线程的五种状态

技术图片 技术图片 技术图片

2. 如何停止线程

技术图片
public class TestStop implements Runnable{
    boolean flag = true;

    @Override
    public void run() {
        //子线程就两条语句,当while执行完,子线程就结束了,但while(true)为无限循环,也就是说子线程只有在flag变为false的时候才会停止
        int i = 0;
        while (flag) {
                System.out.println("*****************子线程跑了"+i++);
        }
    }

    //停止线程
    void stop() {
        flag = false;
        System.out.println("**************子线程停止了");
    }

    public static void main(String[] args) {
        TestStop tt = new TestStop();
        new Thread(tt).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程跑了"+i);
            //主线程跑到800的时候把flag变为false——停止子线程
            if (i==800) {
                tt.stop();
            }
        }

    }
}
技术图片

3. sleep()

  • 模拟网络延迟——放大问题的发生行

    public class TestThread4 implements Runnable{
        private int ticketNum = 10;
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <=0){
                    break;
                }
    
                try {
                    //模拟网络延迟——放大问题的发生行,可能打印负数,这个线程本身是不安全的
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"抢到了第"+ (ticketNum--)+"张票");
            }
        }
    
        public static void main(String[] args) {
            TestThread4 ticket = new TestThread4();
            new Thread(ticket,"小明").start();
            new Thread(ticket,"小红").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    
  • 倒计时

    //倒计时
    public class TestSleep {
        public static void main(String[] args) {
            for (int i = 10; i > 0; i--) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        }
    }
    /*
    10
    9
    8
    ...
    1
    */
    
  • 系统时间

    public class TestSleep2 {
        public static void main(String[] args) {
            //系统时间
            Date date;
    
            while (true) {
                try {
                    date = new Date(System.currentTimeMillis());
                    Thread.sleep(1000);
                    System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    /*
    21:32:09
    21:32:10
    21:32:11
    21:32:12
    ...
    */
    

4. yield()

技术图片
public class TestYield {
    public static void main(String[] args) {
        new Thread(new ThreadYield(),"线程1").start();
        new Thread(new ThreadYield(),"线程2").start();
    }
}

class ThreadYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"运行中");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"已结束");
    }
}
/*
线程1运行中
线程2运行中
线程2已结束
线程1已结束
*/

5. join()

  • 合并线程,待此线程执行完后,再执行其他线程,其他线程阻塞

  • 可以想象成插队

    public class TestJoin {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("子线程join后就是vip线程了"+i);
                }
            });
            t1.start();
    
            for (int i = 0; i < 500; i++) {
                System.out.println("这是main线程"+i);
                
                if (i==400) {
                    t1.join();
                }
                
            }
        }
    
    }
    
    技术图片

    开始时,两个线程交替进行,

    但到主线程i=400开始,子线程的优先级变高,主线程开始等待,子线程执行完后才开始执行主线程

    技术图片 技术图片

6. 线程的状态观测

getState()

  • 新生 new

  • 就绪

  • 运行 Runnable

    • 阻塞 Block
  • 死亡 Dead

    public class TestState{
    
        //观察子线程
        public static void main(String[] args) {
            //1.新生 2.就绪
            Thread t1 = new Thread(()->{
                try {
                    //4.阻塞
                    Thread.sleep(1000);
                } catch (InterruptedExc eption e) {
                    e.printStackTrace();
                }
    
                //5.执行完这句话--->死亡
                System.out.println("lsat sentence");
            });
            System.out.println(t1.getState());//观察状态
    
            //3.启动
            t1.start();
            System.out.println(t1.getState());
    
            //启动之后
            while (t1.getState() != Thread.State.TERMINATED) {
                //只要子线程不终止,在主线程中就不停检测子线程状态
                try {
                    Thread.sleep(100);
                    System.out.println(t1.getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    

7. 线程的优先级

getPriority()

setPriority(int x)

技术图片
    public static void main(String[] args) {
        //查看主线程优先级
        System.out.println(Thread.currentThread().getName() + "优先级为" + Thread.currentThread().getPriority());

        //创建线程
        Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级为" + Thread.currentThread().getPriority()));
        Thread t2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级为" + Thread.currentThread().getPriority()));
        Thread t3 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级为" + Thread.currentThread().getPriority()));
        Thread t4 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级为" + Thread.currentThread().getPriority()));


        //设置优先级
        t1.setPriority(7);
        t2.setPriority(3);
        t3.setPriority(Thread.MAX_PRIORITY);
        t4.setPriority(1);

        //启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
/*
main优先级为5
Thread-2优先级为10
Thread-0优先级为6
Thread-1优先级为3
Thread-3优先级为1
*/
  • 性能倒置

    多跑几次,输出结果也可能是这样,因为设置优先级并不代表一定优先执行,而是优先执行的概率大,(和yield()一样,重新调度线程)具体还是看CPU调度,人为无法干预

    /*
    main优先级为5
    Thread-0优先级为6
    Thread-2优先级为10
    Thread-1优先级为3
    Thread-3优先级为1
    */
    

8. 守护线程

  • 线程分为用户线程守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

    • 如后台记录操作日志、监控内存、垃圾回收等待
    public class TestDeamon {
        public static void main(String[] args) {
            //线程1——输出五秒系统时间
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    Date date = new Date(System.currentTimeMillis());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                    System.out.println(sdf.format(date));
                }
    
                System.out.println("==Goodbye==");
            }).start();
    
            //线程2——守护线程
            Thread t2 = new Thread(() -> {
                while (true) {
                    System.out.println("==守护线程==");
                }
            });
            t2.setDaemon(true);//线程默认false,true后变为守护线程
            t2.start();
        }
    }
    /*
    ==守护线程==
    ==守护线程==
    15:46:49
    ==Goodbye==
    ==守护线程==
    ==守护线程==
    ==守护线程==
    */
    

    线程2为while死循环,但线程1执行完,程序依旧结束了,因为虚拟机不用等待守护线程执行完毕

四、线程同步(重点)

1. 线程同步机制

多个线程访问同一个对象的问题——并发问题

技术图片

队列+锁

解决线程的安全性

技术图片

锁提高安全性的同时,也会降低性能

2. 为什么线程是不安全的

  • 买票案例

    public class Ticket {
    
        public static void main(String[] args) {
            TicketSale station = new TicketSale();
            new Thread(station, "小明").start();
            new Thread(station, "小红").start();
            new Thread(station, "黄牛").start();
    
    
        }
    }
    
    class TicketSale implements Runnable{
        private int ticketNums = 10;
        private boolean flag = true;
    
        @Override
        public void run() {
            //买票
            while (flag) {
                if (ticketNums<=0){
                    flag = false;
                    return;//到0就退出循环,不执行buyTicket();
                }
                buyTicket();
    /*
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    */
            }
        }
    
        //购票方法
        private void buyTicket (){
            System.out.println(Thread.currentThread().getName()+"抢到了第"+(ticketNums--)+"张票");
        }
    }
    /*
    小明抢到了第10张票
    黄牛抢到了第8张票
    小红抢到了第9张票
    小明抢到了第6张票
    黄牛抢到了第7张票
    小明抢到了第4张票
    小红抢到了第5张票
    小明抢到了第2张票
    黄牛抢到了第3张票
    小红抢到了第1张票...*/
    

    这个sleep放buy方法里,会导致第一个线程在run里不断调用buy,直接买完所有的票

    放buy外面,sleep阻塞,下一个线程就有机会买到了

    sleep方法,放大问题的发生性

    /*
    黄牛抢到了第10张票
    小明抢到了第9张票
    小红抢到了第10张票
    小红抢到了第8张票
    小明抢到了第7张票
    黄牛抢到了第6张票...*/
    

    可以看见,黄牛,小红都抢到了第10张票——即表明线程不安全

  • 银行取钱

    public class BankTest {
        public static void main(String[] args) {
            System.out.println("=======小明夫妻共有5000元存款=======");
            Account account = new Account("存款", 5000);
            new Thread(new Drawing(account, 100), "小明").start();
            new Thread(new Drawing(account, 5000), "妻子").start();
    
        }
    }
    
    //账户
    class Account{
        private String name;//账户名
        private int money;//存款
    
        public Account(String name,int money) {
            this.name = name;
            this.money = money;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getMoney() {
            return money;
        }
    
        public void setMoney(int money) {
            this.money = money;
        }
    }
    
    //银行取款
    class Drawing implements Runnable {
        Account account;//账户
        int withdraw;//取现
        int cash;//现金
    
        public Drawing(Account account, int withdraw) {
            this.account = account;
            this.withdraw = withdraw;
        }
    
        @Override
        public void run() {
    
            //如果没有钱退出
            if (account.getMoney() - withdraw <0) {
                System.out.println("存款只有"+account.getMoney()+"取不了"+ withdraw +"元");
                return;
            }
    
            //sleep放大问题的发生性
            //小明和妻子两个线程都在这等待1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            account.setMoney(account.getMoney() - withdraw); //卡内余额
            cash += withdraw;//现金
    
    
            System.out.println(Thread.currentThread().getName()+"取走了"+withdraw+"元");
            System.out.println(account.getName()+"余额为"+account.getMoney()+"元");
            System.out.println(Thread.currentThread().getName()+"手中的钱"+cash+"元");
    
        }
    }
    /*
    =======小明夫妻共有5000元存款=======
    妻子取走了5000元
    存款余额为0元
    妻子手中的钱5000元
    小明取走了100元
    存款余额为-100元
    小明手中的钱100元
    */
    

    取钱前,小明和妻子等待了1秒,同时操作1个对象,因此都能取出钱

  • list案例

    public class ListTest {
        public static void main(String[] args) {
            List list = new ArrayList<>();
    
            for (int i = 0; i < 100000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
    
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(list.size());
    
        }
    }
    //99973
    

    99973而不是预想的100000,等待了100ms,同时添加1个对象,数据覆盖,因此线程也是不安全的

3. 如何解决线程不安全

对属性的保护——private,写set/get方法

同步方法

实现原理:队列与锁

  • synchronized方法
  • synchronized块
技术图片
  • 但也有弊端

    • 不是对象所有内容都需要同步的

    • 修改部分需要同步,而只读部分不需要同步,浪费资源

      技术图片

同步块

  • 买票案例

        private synchronized void buyTicket (){
            System.out.println(Thread.currentThread().getName()+"抢到了第"+(ticketNums--)+"张票");
        }
    }
    /*
    小明抢到了第10张票
    小红抢到了第9张票
    黄牛抢到了第8张票
    小红抢到了第7张票
    小明抢到了第6张票
    黄牛抢到了第5张票
    小红抢到了第4张票
    小明抢到了第3张票
    黄牛抢到了第2张票
    小红抢到了第1张票
    */
    
  • 银行案例

    @Override
    public void run() {
    
        synchronized (account) {
            //如果没有钱退出
            if (account.getMoney() - withdraw <0) {
                System.out.println("存款只有"+account.getMoney()+"取不了"+ withdraw +"元");
                return;
            }
    
            //sleep放大问题的发生性
            //小明和妻子两个线程都在这等待1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            account.setMoney(account.getMoney() - withdraw); //卡内余额
            cash += withdraw;//现金
    
            System.out.println(Thread.currentThread().getName()+"取走了"+withdraw+"元");
            System.out.println(account.getName()+"余额为"+account.getMoney()+"元");
            System.out.println(Thread.currentThread().getName()+"手中的钱"+cash+"元");
        }
    }
    /*
    =======小明夫妻共有5000元存款=======
    小明取走了100元
    存款余额为4900元
    小明手中的钱100元
    存款只有4900取不了5000元
    */
    
  • list案例

    for (int i = 0; i < 100000; i++) {
        new Thread(() -> {
            synchronized (list) {
                list.add(Thread.currentThread().getName());
            }
        }).start();
    }
    //100000
    
  • JUC

    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(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(list.size());
        }
    }
    //10000
    

    transient——临时的

    volatile——不可被序列化

    技术图片

死锁

技术图片
public class DeadLock {
    public static void main(String[] args) {
        System.out.println("0=口红,1=镜子");
        new MyMakeup("小明",0).start();
        new MyMakeup("小红",1).start();
    }
}

class Mirror {}

class Lipstick {}


class MyMakeup extends Thread {
    //资源
    static Mirror mirror = new Mirror();
    static Lipstick lipstick =  new Lipstick();

    //人物、选择
    String name;
    int choice;

    public MyMakeup(String name, int choice) {
        super(name);
        this.choice = choice;
    }

    @Override
    public void run() {
        //化妆
        makeup();
    }

    //化妆,互相持有对方的锁——需要拿到对方的资源
    private void makeup() {
        name = Thread.currentThread().getName();

        if (choice==0) {
            //获得口红
            synchronized (lipstick){
                System.out.println(name+"拿到了口红");
                //使用了1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //获得镜子
                synchronized (mirror) {
                    System.out.println(name+"拿到了镜子");
                }
            }
        } else {
            //获得镜子
            synchronized (mirror) {
                System.out.println(name+"拿到了镜子");
                ////使用了1秒
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //获得口红
                synchronized (lipstick) {
                    System.out.println(name+"拿到了口红");
                }
            }
         }
    }
}
/*
0=口红,1=镜子
小明拿到了口红
小红拿到了镜子
*/

小明先拿到口红,小红先拿到镜子

但都卡住了,拿不到另一样东西

小明锁了口红时,要拿镜子这个资源,但镜子被小红锁了,要等待小红执行完解锁

小红锁了镜子时,要拿口红这个资源,但口红被小明锁了,要等待小明执行完解锁,这样就死锁了

  • 解决方案,将锁提出来
private void makeup() {
    name = Thread.currentThread().getName();

    if (choice==0) {
        //获得口红
        synchronized (lipstick){
            System.out.println(name+"拿到了口红");
            //使用了1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"放回了口红");

        }

        //获得镜子
        synchronized (mirror) {
            System.out.println(name+"拿到了镜子");
        }
        
    } else {
        //获得镜子
        synchronized (mirror) {
            System.out.println(name+"拿到了镜子");
            ////使用了1秒
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"放回了镜子");
        }

        //获得口红
        synchronized (lipstick) {
            System.out.println(name+"拿到了口红");
        }

    }
}
/*
0=口红,1=镜子
小红拿到了镜子
小明拿到了口红
小明放回了口红
小明拿到了镜子
小红放回了镜子
小红拿到了口红
*/

LOCK

ReentrantLock类——可重入锁

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

        Test test = new Test();

        new Thread(test,"线程1").start();
        new Thread(test,"线程2").start();
        new Thread(test,"线程3").start();

    }
}

class Test implements Runnable {

    ReentrantLock lock = new ReentrantLock();//定义Lock锁
    int num = 10;

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();//加锁
                if (num>0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" :"+num--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();//解锁
            }
        }
    }
}

这是lock锁this这个对象,但如果要像Synchronized(特定对象),又该如何用lock实现

五、线程通信问题

生产者消费者问题

1. 管程法

public class Demo02 {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();//缓冲区      
        new Productor(buffer).start(); //生产者
        new Consumer(buffer).start(); //消费者
    }

}

//产品
class Product {
    int id;

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

//缓冲区
class Buffer {
    //1. 定义一个容器——用于存放产品
    Product[] container = new Product[10];//可以存10个
    int count = 0;//产品个数

    //2. 生产者把东西放到缓冲区的方法
    synchronized int push (Product product) {
        //如果容器满了,就通知生产者停止生产
        if (count>= container.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器没满,生产者一直会生产
            //将生产的产品存入缓冲区
        container[count++] = product;

        //通知消费者消费
        this.notifyAll();

        return count;
    }

    //3. 消费者把东西从到缓冲区取出的方法
    synchronized Product pop() {

        //如果容器空了,通知消费者等待
        if (count <= 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果容器没满,通知消费者消费
            //将产品从缓冲区取出
        Product takeout = container[--count];
        System.out.println("            ===取出"+takeout.id+"号===");

        //通知生产者生产
        this.notifyAll();

        return takeout;
    }

}

//生产者
class Productor extends Thread{
    Buffer buffer;

    public Productor(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        int id = 1;

        while(true) {
            //生产、把产品放到缓冲区
            Product product = new Product(id);
            System.out.println("生产了"+id+"号产品\n"+"===投放"+(id++)+"号===\n===仓库共有"+buffer.push(product)+"件产品==========");

            if (id>=100) {
                break;
            }
        }
    }
}

//消费者
class Consumer extends Thread{
    Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        int count = 1;
        while(true){
            //这里buffer.pop()返回的是一个product对象所有能用.id

            System.out.println("            "+buffer.pop().id+"号完成消费");

            if (count>=100) {
                return;
            }
        }

    }
}
  • 输出日志

    生产了1号产品
    ===投放1号===
    ===仓库共有1件产品==========
    生产了2号产品
    ===投放2号===
    ===仓库共有2件产品==========
    生产了3号产品
    ===投放3号===
    ===仓库共有3件产品==========
    生产了4号产品
    ===投放4号===
    ===仓库共有4件产品==========
    生产了5号产品
    ===投放5号===
    ===仓库共有5件产品==========
    生产了6号产品
    ===投放6号===
    ===仓库共有6件产品==========
    生产了7号产品
    ===投放7号===
    ===仓库共有7件产品==========
    生产了8号产品
    ===投放8号===
    ===仓库共有8件产品==========
    生产了9号产品
    ===投放9号===
    ===仓库共有9件产品==========
    生产了10号产品
    ===投放10号===
    ===仓库共有10件产品==========
                ===取出10号===
                10号完成消费
    生产了11号产品
    ===投放11号===
    ===仓库共有10件产品==========
                ===取出11号===
                11号完成消费
                ===取出12号===
                12号完成消费
                ===取出9号===
                9号完成消费
                ===取出8号===
                8号完成消费
                ===取出7号===
                7号完成消费
                ===取出6号===
                6号完成消费
                ===取出5号===
                5号完成消费
                ===取出4号===
                4号完成消费
                ===取出3号===
                3号完成消费
                ===取出2号===
                2号完成消费
                ===取出1号===
                1号完成消费
    生产了12号产品
    ===投放12号===
    ===仓库共有10件产品==========
    生产了13号产品
    ===投放13号===
    ===仓库共有1件产品==========
    生产了14号产品
    ===投放14号===
    ===仓库共有2件产品==========
                ===取出14号===
                14号完成消费
                ===取出13号===
                13号完成消费
    生产了15号产品
    ===投放15号===
    ===仓库共有1件产品==========
    ......
    
  • 关于wait

2. 信号灯法

//信号灯法
public class Demo03 {
    public static void main(String[] args) {

        TvShow tvShow = new TvShow();
        new Actors(tvShow).start();
        new Audiences(tvShow).start();
    }
}
//演员——生产者
class Actors extends Thread {
    TvShow tvShow;

    public Actors(TvShow tvShow) {
        super("演员");
        this.tvShow = tvShow;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0) {
                tvShow.transcribe("快乐大本营");
            } else {
                tvShow.transcribe("抖音");
            }

        }
    }
}

//观众——消费者
class Audiences extends Thread {
    TvShow tvShow;

    public Audiences(TvShow tvShow) {
        super("观众");
        this.tvShow = tvShow;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0) {
                tvShow.watch("快乐大本营");
            } else {
                tvShow.watch("抖音");
            }
        }
    }
}

//节目——产品————由于只两种种状态,所以不需要缓冲区
class TvShow {
    boolean flag = true;
    String programName;

    //演员录制,观众等待
    synchronized void transcribe (String programName){
        //false通知演员休息
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //true通知演员表演
        System.out.println(Thread.currentThread().getName()+"录制了"+programName);

        this.notifyAll();//演员表演完,通知等待的观众再观看
        flag = !flag;
    }

    //观众观看,演员休息
    synchronized void watch(String programName) {

        //ture通知观众等待
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //false通知观众收看
        System.out.println(Thread.currentThread().getName()+"收看了"+programName);

        this.notifyAll();//观众受看完,通知休息的演员再录制
        flag = !flag;
    }

}

六、高级主题

1. 线程池

  • 提前再线程池创建好一些线程,使用时调用,结束时放回线程池
  • 便于重复利用
  • ExecutorService——线程池
  • Executors——创建线程池的工具类

2.用Runnable实现线程池

public class executorService {
    public static void main(String[] args) {
        //开启线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        
        service.execute(new Test());//execute(),提交runnable实现的线程
        service.execute(new Test());
        service.execute(new Test());
        service.execute(new Test());
        service.submit(new Test());//通用的提交线程方法
        service.submit(new Test());
        service.submit(new Test());

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

class Test implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"helloworld");
    }
}

七、总结

1. 创建线程的三种方法

public class Demo01 {
    public static void main(String[] args) {
        //Thread
        new Thread1().start();
        //Runnable
        new Thread(new Thread2()).start();
        //Callable
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread3());
        new Thread(futureTask);//futrueTask继承了Runnable接口

        //线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        service.submit(new Thread1());//提交
        service.submit(new Thread2());
        service.submit(new Thread3());
        service.shutdown();//关闭线程池
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Thread实现多线程");
    }
}

class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runnable实现多线程");
    }
}

class Thread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"Callable实现多线程");
        return 100;
    }
}

多线程

标签:过多   进程   主题   静态代理   运行   struct   打印   ide   write   

原文地址:https://www.cnblogs.com/Sheltonz/p/14070779.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!