• 多线程


    一、实现多线程

    多线程就是在同一时间做多件事情。

    有3种方法实现多线程

    一、实现Runnable接口

     定义一个Hero类,有name,hp,damage属性,和一个attack行为

    public class Hero {
        public String name;
        public float hp;
        public int damage;
        public void attack(Hero h) {
            try {
                //为了表示攻击过程 睡眠1000
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            h.hp=h.hp-damage;
            System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
            if(h.isDead()) {
                System.out.println(h.name+"死了");
            }
        }
        public boolean isDead() {
            
            return 0>=hp?true:false;
            
        }
    }

    实现Runnable接口,接口中有个抽象的run()方法

    public class Battle implements Runnable {
        private Hero h1;
        private Hero h2;
        public Battle(Hero h1,Hero h2) {
            this.h1 = h1;
            this.h2 = h2;
        }
    
        @Override
        public void run() {
            while(!h2.isDead()){
                h1.attack(h2);
            }
    
        }
    
    }

    二、继承Thread(其实Thread也是实现的Runnable接口)

    public class KilledThread extends Thread{
        private Hero h1;
        private Hero h2;
        public KilledThread(Hero h1,Hero h2) {
            this.h1 = h1;
            this.h2 = h2;
        }
        public void run() {
            while(!h2.isDead()) {
                h1.attack(h2);
            }
        }
    
    }

    三、匿名类

     //4、匿名类
                Thread t1 = new Thread() {
                    public void run() {
                        //匿名类中用到外部的局部变量teemo,必须把teemo声明为final
                        //但是在JDK7以后,就不是必须加final的了
                        while(!teemo.isDead()) {
                            gareen.attack(teemo);
                        }
                    }
                };
              
                
                Thread t2 = new Thread() {
                    public void run() {
                        while(!leesin.isDead()) {
                            bh.attack(leesin);
                        }
                    }
                };

    Test类

    实例4个英雄 盖伦,赏金猎人,提莫,盲僧,分别用不使用多线程和3种实现多线程的方法,让盖伦打提莫,赏金猎人打盲僧

    注意:run方法并不会启动线程,还需要调用start方法。

    public class ThreadTest {
    
        public static void main(String[] args) {
             Hero gareen = new Hero();
                gareen.name = "盖伦";
                gareen.hp = 616;
                gareen.damage = 65;
         
                Hero teemo = new Hero();
                teemo.name = "提莫";
                teemo.hp = 300;
                teemo.damage = 30;
                 
                Hero bh = new Hero();
                bh.name = "赏金猎人";
                bh.hp = 500;
                bh.damage = 65;
                 
                Hero leesin = new Hero();
                leesin.name = "盲僧";
                leesin.hp = 300;
                leesin.damage = 80;
                /*1、未开启多线程
                 * 
               //盖伦攻击提莫
                while(!teemo.isDead()){
                    gareen.attack(teemo);
                }
                //赏金猎人攻击盲僧
                while(!leesin.isDead()){
                    bh.attack(leesin);
                }
                */
                
                /*
                   2、  继承Thread实现多线程
                KilledThread kt1 = new KilledThread(gareen, teemo);
                kt1.start();
                KilledThread kt2 = new KilledThread(bh, leesin);
                kt2.start();
                */
                
                /*3、
                
                //实现Runnable实现多线程,其实Thread也是实现了Runnable
                Battle b1 = new Battle(gareen, teemo);
                //实现Runnable只有run方法,没有开启线程的start方法
                //所以得借助Thread对象去调用start方法
                new Thread(b1).start();
                Battle b2 = new Battle(bh, leesin);
                new Thread(b2).start();
                */

    二、常用的线程方法

    join

    主线程就是,所有进程都至少会有一个线程即是主线程,main方法执行就会有一个看不见的主线程存在。将线程加入主线程,主线程会等待该线程结束才会继续运行下去。

    setPriority

    线程优先级:当线程处于竞争关系,优先级高的线程将会获得更多的cpu资源。最高的优先级:Thread.MAX_PRIORITY  最低的优先级:Thread.MIN_PRIORITY

    setDaemon

    守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
    就好像一个公司有销售部,生产部这些和业务挂钩的部门。
    除此之外,还有后勤,行政等这些支持部门。
    如果一家公司销售部,生产部都解散了,那么只剩下后勤和行政,那么这家公司也可以解散了。
    守护线程就相当于那些支持部门,如果一个进程只剩下守护线程,那么进程就会自动结束。
    守护线程通常会被用来做日志,性能统计等工作。

    三、多线程会遇到的问题

    新建一个Hero,定义2个方法,一个hp-1,一个hp+1

    public class Hero {
         public String name;
            public float hp;
             
            public int damage;
             
            //回血
            public void recover(){
                hp=hp+1;
            }
             
            //掉血
            public void hurt(){
                hp=hp-1;
            }
             
            public void attackHero(Hero h) {
                h.hp-=damage;
                System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
                if(h.isDead())
                    System.out.println(h.name +"死了!");
            }
          
            public boolean isDead() {
                return 0>=hp?true:false;
            }
    
    }

    测试类,实例化一个Hero,hp尽量大一些,用加血和减血线程同时执行10000次

    public class TestThread {
        
        public static void main(String[] args) {
                
            final Hero gareen = new Hero();
            gareen.name = "盖伦";
            gareen.hp = 10000;
               
            System.out.printf("盖伦的初始血量是 %.0f%n", gareen.hp);
            int n = 10000;
       
            Thread[] addThreads = new Thread[n];
            Thread[] reduceThreads = new Thread[n];
               
            for (int i = 0; i < n; i++) {
                Thread t = new Thread(){
                    public void run(){
                        gareen.recover();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                };
                t.start();
                addThreads[i] = t;
                   
            }
               
            //n个线程减少盖伦的hp
            for (int i = 0; i < n; i++) {
                Thread t = new Thread(){
                    public void run(){
                        gareen.hurt();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                };
                t.start();
                reduceThreads[i] = t;
            }
               
            //等待所有增加线程结束
            for (Thread t : addThreads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            //等待所有减少线程结束
            for (Thread t : reduceThreads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
         System.out.printf("%d个增加线程和%d个减少线程结束后%n盖伦的血量变成了 %.0f%n", n,n,gareen.hp);
               
        }
            
    }

    最后理论上结果还是10000,但是实际上可能会出现10001或者9999等的结果。这种数据就叫做脏数据,是不准确的数据。

    原因是:可能增加血量进程还未修改hp时,减少血量进程过来了,导致hp的值出现错误

    解决办法:当一个进程修改hp时,其他进程不能访问hp

    采用:synchronized 同步对象
    1、可以在线程的匿名类run方法中加synchronized
    2、将hero对象改为synchronized
    3、将hero的hurt和recover方法用synchronized修饰

    采用第一种方法的代码:

    public class ThreadTest {
        public static void main(String [] args) {
            final Hero gareen = new Hero();
            //为synchronized创建Object对象
            final Object someObject = new Object();
            gareen.name="盖伦";
            gareen.hp=100000;
            
            System.out.printf("盖伦的初始血量是 %.0f%n", gareen.hp);
            //建立10000个线程线程减血,一次1点
            int n=10000;
            
            Thread[] reduceThreads = new Thread[n];
            for(int i=0;i<n;i++) {
                Thread t = new Thread() {
                    public void run() {
                        synchronized (someObject) {
                            gareen.hurt();
                        }
                        
                        try {
                            Thread.sleep(10);
                        } catch (Exception e) {
                            // TODO: handle exception
                        }
                    }
                };
                t.start();
                reduceThreads[i] = t;
            }
            //建立n个线程回血,每次回1
            Thread[] addThreads = new Thread[n];
            for(int i=0;i<n;i++) {
                Thread t = new Thread() {
                    public void run() {
                        //采用synchronized,任何修改hp需先占有一个someObject
                        synchronized (someObject) {
                            gareen.recover();
                        }
                        
                        try {
                            Thread.sleep(10);
                        } catch (Exception e) {
                            // TODO: handle exception
                        }
                    }
                };
                t.start();
                addThreads[i] = t;
            }
          //等待所有增加线程结束
            for (Thread t : addThreads) {
                try {//join将当前线程加入主线程
                    t.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            //等待所有减少线程结束
            for (Thread t : reduceThreads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    
                    
                }
            }
            System.out.printf("%d个增加线程和%d个减少线程结束后%n盖伦的血量变成了 %.0f%n", n,n,gareen.hp);
                    
            
            
        }
    
    }

    四、线程池的使用

    每一个线程的启动和结束都是比较消耗时间和占用资源的。
    如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。
    为了解决这个问题,引入线程池这种设计思想。

    使用java自带线程池

    public class ThreadPoolTest {
        
        public static void main(String[] args) {
            ThreadPoolExecutor tpe = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
            
            for(int i = 0;i<20;i++) {
            tpe.execute(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println("任务"+Thread.currentThread().getName());
                    
                    
                }
            });
    
        }
        }
    
    }

    其中new ThreadPoolExecutor()方法中

    第一个参数10 表示这个线程池初始化了10个线程在里面工作
    第二个参数15 表示如果10个线程不够用了,就会自动增加到最多15个线程
    第三个参数60 结合第四个参数TimeUnit.SECONDS,表示经过60秒,多出来的线程还没有接到活儿,就会回收,最后保持池子里就10个
    第四个参数TimeUnit.SECONDS 如上
    第五个参数 new LinkedBlockingQueue() 用来放任务的集合

    execute方法用于添加新的任务

  • 相关阅读:
    ByteBuffer使用实例
    Fiddler抓包显示请求时延
    手机wifi连上Fiddler后无网络问题解决
    git
    git
    Autofac使用
    Redis实战
    Redis实战
    Redis实战
    Redis实战
  • 原文地址:https://www.cnblogs.com/yeyangtao/p/10839038.html
Copyright © 2020-2023  润新知