一、实现多线程
多线程就是在同一时间做多件事情。
有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方法用于添加新的任务