zoukankan      html  css  js  c++  java
  • 多线程

    多线程

    ProcessThread

    • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
    • 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
    • 通常在一个进程中可以包含若干个线程,当然一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
    • 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很多,就有了同时执行的错觉。

    核心概念

    • 线程就是独立的执行路径。
    • 在程序运行时,即使没有自己创建的线程,后台也会有多个线程,如主线程,gc线程。
    • main()称之为主线程,为系统的入口,用于执行整个程序。
    • 在一个进程中,如果开辟了多个线程,线程的运行由调度器(cpu)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
    • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
    • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
    • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
    //创建线程的方法一:继承Thread类,重写run方法,调度start方法开启线程;
    //方法二:实现Runable接口,重写run方法,创建Thread类,引用Runable实现类,再调度start方法。
    //注意:线程开启不一定立即执行,由cpu调度执行。
    public class TestThread extends Thread {
        //run方法线程主体
        @Override
        public void run() {
            for (int i=0;i<20;i++){
                System.out.println("我在学习多线程-----"+i);
            }
        }
      //main线程,主线程
        public static void main(String[] args) {
            //创建一个线程对象
            TestThread thread = new TestThread();
            //调用start方法,开启子线程。
            thread.start();
    
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习----"+i);
            }
            //主线程和子线程交替执行
        }
    }
    

    使用多线程同步下载图片

    //练习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() {
            fileDown file = new fileDown();
            file.downloader(url,name);
            System.out.println("下载了文件名为"+name);
        }
    
        public static void main(String[] args) {
            TestThread2 t1 = new TestThread2("http://cdn.xiaxiang.tech/image/blogs/threeJS/case/camera/%E7%94%B5%E5%BD%B1%E7%9B%B8%E6%9C%BA%E7%84%A6%E7%82%B91.png","真难看.jpg");
            TestThread2 t2 = new TestThread2("https://p0.ssl.qhimgs1.com/dmfd/296_196_/t01e84107a259d53f20.jpg", "一般.jpg");
    
            t1.start();
            t2.start();
    
        }
        
    class fileDown{
        //下载图片的方法
          public void  downloader(String url,String name){
                try {
                    FileUtils.copyURLToFile(new URL(url),new File(name));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    Lamda表达式

    • lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么必须用代码块包裹。
    • 使用Lambda的前提时接口为函数式接口。
    • 多个参数也可以去掉参数类型,要么都去掉,要么都加上参数类型,必须加上括号。
    为什么要使用Lambda表达式
    • 避免匿名内部类定义过多
    • 可以让你的代码看起来更简洁
    • 去掉了一个没有意义的代码,只留下核心的逻辑
    函数式接口
    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
    • 对于函数式接口,我们可以通过Lambda表达式创建该接口的对象。

    线程状态

    线程状态之间的关系

    线程停止
    • 线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
    public class TestStop implements Runnable {
    
        private boolean flag=true;
    
        public void run() {
            int i=0;
            while(flag){
                    System.out.println("run在执行----"+i++);
            }
        }
       //利用flag为false,停止线程
        public void toFalse(){
            this.flag=false;
        }
        public static void main(String[] args) {
            TestStop stop = new TestStop();
            Thread thread = new Thread(stop);
            thread.start();
            for (int i = 0; i < 1000; i++) {
                System.out.println("main方法-----"+i);
                if (i==900){
                    stop.toFalse();
                    System.out.println("run方法停止执行");
                }
            }
        }
    }
    
    线程休眠 sleep()
    • sleep(时间)指定当前线程阻塞的毫秒数,存在InterruptedException异常。sleep时间达到后线程就绪状态。

    • 每一个对象都有一个锁,sleep不会释放锁。

    • 放大问题的发生性。

    public class TestSleep implements  Runnable {
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            //线程延迟
          /*  TestSleep s = new TestSleep();
            Thread thread = new Thread(s);
            thread.start();*/
            //获取当前时间延迟
            Date date = new Date();
            SimpleDateFormat f = new SimpleDateFormat("yyyy-HH-mm hh:mm:ss");
            while (true){
                Thread.sleep(1000);
                System.out.println(f.format(date));
                 date = new Date();
            }
        }
    }
    
    线程礼让 yield()
    • 礼让线程,让当前正在执行的线程暂停,但不阻塞。
    • 将线程从运行状态转为就绪状态。
    • 让cpu重新调度,礼让不一定成功!看cpu心情。
    public class TestYield implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread().getName()+"在执行线程");
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"结束线程了");
        }
    
        public static void main(String[] args) {
            TestYield y = new TestYield();
         new Thread(y,"小明").start();
         new Thread(y,"小红").start();
    
        }
    }
    
    线程强制执行 join()
    • join合并线程,待此线程执行完成后,再执行其他线程。在此线程执行过程中,其他线程阻塞。(可以想象成插队)
    public class TestJoin implements  Runnable {
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("我在插队"+i);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            TestJoin join = new TestJoin();
            Thread thread = new Thread(join);
           thread.start();
            for (int i = 0; i < 100; i++) {
                System.out.println("main在执行"+i);
                if (i==59){
                    thread.join();
                }
            }
        }
    }
    
    线程状态观测 getState()
    • 线程状态。线程可以处于以下状态之一:
      • NEW
        尚未启动的线程处于此状态。
      • RUNNABLE
        在Java虚拟机中执行的线程处于此状态。
      • BLOCKED
        被阻塞等待监视器锁定的线程处于此状态。
      • WAITING
        正在等待另一个线程执行特定动作的线程处于此状态。
      • TIMED_WAITING
        正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
      • TERMINATED
        已退出的线程处于此状态。
      public static void main(String[] args) throws InterruptedException {
            TestStatus testStatus = new TestStatus();
            Thread thread = new Thread(testStatus);
            //new线程对象时,线程的状态
            Thread.State state = thread.getState();
            System.out.println(state);
            //start时,线程状态
            thread.start();
            state=thread.getState();
            System.out.println(state);
    
            while(state!=Thread.State.TERMINATED){  //只要线程不结束,就一直输出线程状态
                Thread.sleep(1000);
                state =thread.getState();
                System.out.println(state);
            }
    
        }
    
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("循环完了----");
        }
    
    线程优先级
    • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。(优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了)
    • 线程默认优先级为5,优先级的范围为0~10.
    • 使用以下方式获取和改变优先级:getPriority(),setPriority()
       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());
    
            TestPriority priority = new TestPriority();
            Thread one = new Thread(priority, "one");
            Thread two = new Thread(priority, "two");
            Thread three = new Thread(priority, "three");
            Thread four = new Thread(priority, "four");
            //注意:一般都是先设置优先级,再启动线程
            one.start();
            two.setPriority(1);
            two.start();
            three.setPriority(10);
            three.start();
            four.setPriority(5);
            four.start();
        }
    
    守护线程
    • 线程分为用户线程和守护线程。
    • 虚拟机必须确保用户线程执行完毕。
    • 虚拟机不用等待守护线程执行完毕。
    • 如后台记录操作日志,监控内存、垃圾回收等。
    • 设置为守护线程的方法: setDaemon()(默认为false表示为用户线程,true为守护线程)

    线程同步

    多个线程操作同一资源(并发)

    • 现实生活中,我们会遇到“同一个资源,多人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决方法就是排队,一个一个来。
    • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
    • 由于同一进程的多个线程共享同一块存储空间,在带来方便的的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源时,其他线程必须等待,使用后释放锁即可。但同时也存在以下问题:
      • 一个线程持有锁会导致其他所有需要此锁的线程挂起。
      • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题。
      • 如果一个优先级高的线程等待一个优先级低的线程释放锁 ,会导致优先级倒置,引起性能问题。
    //线程不安全案例,会导致多人买到同一张票
    public class BuyTicket {
        public static void main(String[] args) {
            MyTicket myTicket = new MyTicket();
          new Thread(myTicket,"你").start();
          new Thread(myTicket,"我").start();
          new Thread(myTicket,"他").start();
        }
        static class MyTicket implements Runnable {
            private int nums = 10;
            private Boolean flag = true;
            public void run() {
                while (flag) {
                    if (nums<=0){
                        flag=false;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
                }
            }
        }
    }
    //执行结果如下:
      /*  我拿到了第8票
          他拿到了第9票
          你拿到了第10票
          我拿到了第6票
          你拿到了第5票
          他拿到了第7票
          他拿到了第4票
          我拿到了第3票
          你拿到了第4票
          我拿到了第2票
          他拿到了第1票
          你拿到了第1票
    我拿到了第0票*/
    
    同步方法和同步块 synchronized
    public class BuyTicket {
    
        public static void main(String[] args) {
            MyTicket myTicket = new MyTicket();
            new Thread(myTicket,"你").start();
          new Thread(myTicket,"我").start();
          new Thread(myTicket,"他").start();
        }
    }
    class MyTicket implements Runnable {
        private int nums = 10;
        private Boolean flag = true;
        public  void run() {
            while (flag) {
                buy();
            }
        }
        public synchronized void buy(){
            if (nums<=0){
                flag=false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
        }
    }
    //加入synchronized修饰方法后,执行结果为:
    /*
    你拿到了第10票
    他拿到了第9票
    我拿到了第8票
    他拿到了第7票
    他拿到了第6票
    你拿到了第5票
    你拿到了第4票
    他拿到了第3票
    我拿到了第2票
    我拿到了第1票*/
    

    同步块:synchronized(obj){}

    • obj称之为 同步监视器
      • obj可以时任何对象,但是推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是class
    public class UnsafeList{
    
       public static void main(String[] args) {
           listtwo listtwo = new listtwo();
           for (int i = 0; i < 1000; i++) {
               new Thread(listtwo).start();
           }
        }
    }
    class listtwo implements Runnable{
        List<String> list=new ArrayList<String>();
        public void run() {
            synchronized (list){
                list.add(Thread.currentThread().getName());
                System.out.println(list.size());
            }
    
        }
    }
    
    死锁

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

    • 产生死锁的四个必要条件:
      • 互斥条件:一个资源每次只能被一个进程使用。
      • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
      • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
      • 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。
    //造成死锁的案例
    public class deadLock {
        public static void main(String[] args) {
            hua hua = new hua(0, "小红");
            hua hua1 = new hua(1, "小花");
            new Thread(hua).start();
            new Thread(hua1).start();
        }
    }
    //口红
    class kouHong{ }
    //镜子
    class jiZhi{}
    //化妆
    class hua implements Runnable {
        static kouHong kouHong = new kouHong();
        static jiZhi jiZhi = new jiZhi();
    
        private int choose;
        private String name;
    
        public hua(int choose, String name) {
            this.choose = choose;
            this.name = name;
        }
    
        public void run() {
            if (choose == 0) {
                synchronized (kouHong) {
                    System.out.println(this.name + "在使用口红");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (jiZhi) {
                        System.out.println(this.name + "在使用镜子");
                    }
                }
            } else {
                synchronized (jiZhi) {
                    System.out.println(this.name + "在使用镜子");
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (kouHong) {
                        System.out.println(this.name + "在使用口红");
                    }
                }
            }
        }
    }
    
    Lock锁
    • Lock时显式锁(手动开启和关闭锁,lock()、unlock()方法)synchronized式隐式锁,出了作用域自动释放。
    • Lock只有代码块锁,synchrinized有代码块和方法锁。
    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

    线程协作、通信

    应用场景:生产者和消费者问题

    • 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费着取走为止
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,指导仓库中再次放入产品为止。

    分析

    这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

    • 对于生产者,没有生产产品之间,要通知消费者等待,而生产之后,又需要马上通知消费者消费。
    • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品,以供消费。
    • 在生产者消费者问题中,仅有synchronized是不够的
      • synchronized可阻止并发更新同一个共享资源,实现了同步。
      • synchronized不能用来实现不同线程之间的消息传递(通信)。
    方法名 作用
    wait() 表示线程一直等待,直到其他线程通知,与sleep不同,wait会释放锁
    wait(long timeout) 指定等待的毫秒数
    notify() 唤醒一个处于等待状态的线程
    notifyAll() 唤醒同一个对象所有调用wait方法的线程,优先级别高的线程优先调度
    //管程法实现线程通信
    public class TestPC {
        public static void main(String[] args) {
            SynContainer synContainer = new SynContainer();
            Protucer protucer = new Protucer(synContainer);
            Consumer consumer = new Consumer(synContainer);
            new Thread(protucer).start();
            new Thread(consumer).start();
        }
    }
    //生产者
    class Protucer implements Runnable{
        SynContainer synContainer;
        public Protucer(SynContainer synContainer){
            this.synContainer=synContainer;
        }
        public void run() {
            for (int i = 0; i < 100; i++) {
                synContainer.push(new Chicken(i));
                System.out.println("生产了"+i+"只鸡");
            }
    
        }
    }
    //消费者
    class Consumer implements  Runnable{
        SynContainer synContainer;
        public Consumer(SynContainer synContainer){
            this.synContainer=synContainer;
        }
        public void run() {
            try {
                //开始等待一段时间,等待生产者产到10只鸡
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了第"+synContainer.pop().id+"鸡");
            }
        }
    }
    
    //鸡
    class Chicken{
        int id;
    
        public Chicken(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class SynContainer{
        //需要一个容器大小
        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(){
    
            if(count==0){
                //通知生产者生产,消费者等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            count--;
            Chicken chicken=chickens[count];
            this.notifyAll();
            return  chicken;
    
        }
    }
    
    //信号灯法
    public class TestTV {
        public static void main(String[] args) {
            Tv tv = new Tv();
            player player = new player(tv);
            watcher watcher = new watcher(tv);
            new Thread(player).start();
            new Thread(watcher).start();
        }
    }
    //演员
    class player implements Runnable{
        Tv tv;
        public player(Tv tv){
            this.tv=tv;
        }
        public void run() {
            for (int i = 0; i < 20; i++) {
                if(i%2==0){
                   this.tv.play("快乐大本营");
                }else {
                    this.tv.play("还珠格格");
                }
            }
        }
    }
    //观众
    class watcher implements Runnable{
        Tv tv;
        public watcher(Tv tv){
            this.tv=tv;
        }
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    //电视台
    class Tv{
        String voice;
        Boolean flag=true;
        //演员表演节目
        public synchronized void play(String voice){
            while(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.voice=voice;
            System.out.println("演员表演了"+this.voice);
            this.notifyAll();
            this.flag=!this.flag;
        }
        //观看节目
        public synchronized void watch(){
            while (flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观众观看了"+this.voice);
            this.notifyAll();
            this.flag=!this.flag;
        }
    }
    

    线程池

    • 背景:经常创建和销毁,使用量特别大,比如并发情况下的线程,对性能影响很大。
    • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现了重复利用。
    • 好处:
      • 提高响应速度(减少了创建新线程的时间)
      • 降低了资源消耗(重复利用线程池中的线程,不需要每次都创建)
      • 便于管理:
        • corePoolSize: 核心池的大小
        • maxiumPoolSize 最大线程数
        • keepAliveTime 线程没有任务时最多保持多长时间后会终止。
    //线程池的使用
    public class Execute {
        public static void main(String[] args) {
            ExecutorService service= Executors.newFixedThreadPool(10);//线程池的大小
            MyExecute myExecute = new MyExecute();
            service.execute(new Thread(myExecute));
            service.execute(new Thread(myExecute));
            service.execute(new Thread(myExecute));
    
            //线程池关闭
            service.shutdown();
        }
    }
    class MyExecute implements Runnable{
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
  • 相关阅读:
    ios UIImageView
    ios UILable
    [leetCode]116. 填充每个节点的下一个右侧节点指针
    [leetCode]1002. 查找常用字符
    [leetCode]199. 二叉树的右视图
    [leetCode]784. 字母大小写全排列
    [leetCode]1297. 子串的最大出现次数
    [leetCode]1239. 串联字符串的最大长度
    1095. 山脉数组中查找目标值
    [leetCode]1235. 规划兼职工作
  • 原文地址:https://www.cnblogs.com/xiaopanjava/p/13789470.html
Copyright © 2011-2022 走看看