多线程

一、实现多线程

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

有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方法用于添加新的任务

 

posted @ 2019-05-09 16:05  奥兔man  阅读(289)  评论(0编辑  收藏  举报