疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java基础多线程


 

   

  一:多线程

  (1)多线程:一个应用程序有多条执行路径

  进程:正在执行的应用程序

  线程:进程的执行单元,执行路径

  单线程:一个应用程序只有一条执行路径

  多线程:一个应用程序有多条执行路径

  多进程的意义?

  提高CPU的使用率

  多线程的意义?

  提高应用程序的使用率

  (2)Java程序的运行原理及JVM的启动是多线程的吗?

  A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。

  B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

  (3)多线程的实现方案(线程的创建和启动)

  A:继承Thread类创建线程类的代码

  java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,

  实际上就是执行一段程序流(一段顺序流的代码)。Java使用run方法来封装这样一段程序。

  /**继承Thread来创建线程类*/

  public class FirstThread extends Thread {

  private int i;

  //重写run方法,run方法的方法体就是线程执行体

  public void run() {

  for(;i<10;i++){

  System.out.println(this.getName()+":"+i);

  }

  }

  public static void main(String []args){

  for(int i=0;i<20;i++){

  System.out.println(Thread.currentThread().getName()+" .."+i);

  if(i==10){

  System.out.println("--------------------------------------------");

  new FirstThread().start();

  new FirstThread().start();

  System.out.println("---------------------------------------------");

  }

  }

  }

  }

  结果:红色部分每次运行都不一致,因为多线程也是并发的

  main ..0

  main ..1

  main ..2

  main ..3

  main ..4

  main ..5

  main ..6

  main ..7

  main ..8

  main ..9

  main ..10

  --------------------------------------------

  Thread-0:0

  ---------------------------------------------

  Thread-1:0

  Thread-1:1

  Thread-1:2

  Thread-1:3

  Thread-0:1

  Thread-1:4

  Thread-1:5

  main ..11

  Thread-1:6

  Thread-1:7

  Thread-1:8

  Thread-1:9

  Thread-0:2

  Thread-0:3

  main ..12

  main ..13

  ......

  总结 :从上面结果可以看出Thread-0和Thread-1两条线程输出的i变量都不连续(注意:i变量是FirestThread的实例属性,而不是局部变量,但因为程序每次创建线程都会创建一个FirstThread对象,所以Thread-0和Thread-1不能共享该实例属性)。

  使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。

  B:实现Runnable接口创建线程类

  public class SecondThread implements Runnable {

  private int i;

  public void run() {

  for(;i<20;i++){

  System.out.println(Thread.currentThread().getName()+":"+i);

  }

  }

  public static void main(String [] args){

  for(int i=0;i<20;i++){

  System.out.println(Thread.currentThread().getName()+" .."+i);

  if(i==10){

  SecondThread st=new SecondThread();

  //通过new Thread( Runable target,String name)来创建新线程

  new Thread(st,"线程1").start();

  new Thread(st,"线程2").start();

  }

  }

  }

  结果:红色部分每次运行都不一致,因为多线程也是并发的

  main ..0

  main ..1

  main ..2

  main ..3

  main ..4

  main ..5

  main ..6

  main ..7

  main ..8

  main ..9

  main ..10

  --------------------------------------------

  线程1:0

  --------------------------------------------

  线程1:1

  线程2:1

  线程2:3

  main ..11

  线程2:4

  线程2:5

  线程2:6

  线程1:2

  线程2:7

  线程2:9

  线程2:10

  线程2:11

  线程2:12

  线程2:13

  main ..12

  线程2:14

  线程2:15

  线程2:16

  线程2:17

  线程1:8

  线程2:18

  main ..13

  main ..14

  线程1:19

  main ..15

  main ..16

  main ..17

  。。。。

  总结:根据源代码中Thread类构造方法 Ruanalbe接口对象target只能作为参数传递到Thread构造方法中,所以多个线程可以共用一个Runnable对象,因为都用同一个Runnable对象所以在Runnable实现类的实例变量也可以共享了。

  所以Runable非常适合多个相同线程来处理同一份资源的情况。

  (4)线程的调度和优先级问题

  A:线程的调度

  a:分时调度

  b:抢占式调度 (Java采用的是该调度方式)

  B:获取和设置线程优先级

  a:默认是5

  b:范围是1-10

  (5)线程的控制(常见方法)

  1.join线程:

  让一个线程等待另一个线程完成的方法:join()。当在某个程序执行流中调用其他线程的join()方法,那该执行流对应的线程就会阻塞,知道被 join()加入的join线程完成为止。join方法通常有使用线程的程序调用,将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都 得到处理后,再调用 主线程来进一步操作(Thread t=new Thread();t.start();t.join简单来说就是加入到t线程。等t线程执行完成后才会返回出 来执行线程。)

  Join方法有三种重用形式:

  Join():等待被join的线程执行完成

  Join(long millis):等待join线程的时间最长为millis毫秒,如果在这个时间内,被join的线程还没有执行结束则不再等待)

  Join(long millis,int nanos)千分之一毫秒(不用)

  代码如下

  public class JoinThread implements Runnable{

  @Override

  public void run() {

  for(int i=0;i<5;i++){

  System.out.println(Thread.currentThread().getName()+":"+i);

  }

  }

  public static void main(String [] args) throws InterruptedException{

  //实例化一个Runnable

  JoinThread jt=new JoinThread();

  //创建一个线程

  new Thread(jt).start();

  for(int i=0;i<10;i++){

  if(i==3){

  Thread th=new Thread(jt);

  //启动第二个线程

  th.start();

  //main的线程中调用了th线程的join方法

  //让第二个线程执行完成后再执行main

  th.join();

  }

  System.out.println(Thread.currentThread().getName()+":"+i);

  }

  }

  }

  结果:

  Thread-0:0

  Thread-0:1

  Thread-0:2

  main:0

  main:1

  Thread-0:3

  main:2

  Thread-0:4

  Thread-1:0

  Thread-1:1

  Thread-1:2

  Thread-1:3

  Thread-1:4

  main:3

  main:4

  main:5

  main:6

  main:7

  main:8

  main:9

  2.后台线程:

  代码如下

  public class DaemonThread implements Runnable{

  @Override

  public void run() {

  for(int i=0;i<100;i++){

  System.out.println(Thread.currentThread().getName()+":"+i);

  }

  }

  public static void main(String [] args){

  //要将前台线程转换成后台线程,需要在该线程刚新建还未start()之前转换。main线程也是前台线程

  //所有前台线程死亡时,后台线程也就随之死亡。

  DaemonThread dt=new DaemonThread();

  Thread td=new Thread(dt,"线程1");

  System.out.println("main方法是否是后台线程"+Thread.currentThread().isDaemon());

  System.out.println("td线程最初是否是后台线程"+td.isDaemon());

  //指定td为后台线程

  td.setDaemon(true);

  System.out.println("td线程执行setDaemon方法后是否是后台线程"+td.isDaemon());

  //就绪启动后台线程

  td.start();

  for(int i=0;i<5;i++){

  System.out.println(Thread.currentThread().getName()+" "+i);

  }

  }

  }

  结果:只要前台线程结束,后台线程也会随之结束,并不是马上结束

  main方法是否是后台线程false

  td线程最初是否是后台线程false

  td线程执行setDaemon方法后是否是后台线程true

  main 0

  main 1

  线程1:0

  线程1:1

  main 2

  线程1:2

  线程1:3

  main 3

  线程1:4

  线程1:5

  main 4

  线程1:6

  线程1:7

  线程1:8

  线程1:9

  线程1:10

  线程1:11

  线程1:12

  线程1:13

  3.线程睡眠:sleep

  /**

  * 线程睡眠:sleep有两种重载形式:

  * static void sleep(long millis)

  * static void sleep(long millis,int nanos)

  *

  */

  public class SleepThread {

  public static void main(String [] args) throws InterruptedException{

  for(int i=0;i<5;i++){

  System.out.println("线程:"+Thread.currentThread().getName()+"当前时间:"+new Date());

  //让当前线程暂停2秒

  Thread.sleep(2000);

  }

  }

  }

  结果:

  线程:main当前时间:Fri Nov 04 18:51:33 CST 2011

  线程:main当前时间:Fri Nov 04 18:51:35 CST 2011

  线程:main当前时间:Fri Nov 04 18:51:37 CST 2011

  线程:main当前时间:Fri Nov 04 18:51:39 CST 2011

  线程:main当前时间:Fri Nov 04 18:51:41 CST 2011

  4.线程让步(yield)

  /**

  * yield()方法是一个和sleep方法有点类似的静态方法。yield也可以让当前正在执行的线程暂停

  * 但它不会阻塞该线程,它只是将该线程转入就绪状态。yield只是让当前线程暂停一会儿,让系统的

  * 调度器重新调度一次(完全可能的情况是:当一个线程调用了yield方法暂停之后,线程调度器又马上

  * 将其调度出来重新执行。)

  * 实际上,当前线程调用了yield方法后,只有优先级和当前线程相同,甚至优先级高于当前线程的处于

  * 就绪状态的线程才会获得执行机会。

  *

  */

  public class YieldThread implements Runnable{

  @Override

  public void run() {

  for(int i=0;i<50;i++){

  System.out.println(Thread.currentThread().getName()+":"+i);

  if(i==20){

  Thread.yield();

  }

  }

  }

  public static void main(String [] args){

  //启动第一条子线程

  Thread td1=new Thread(new YieldThread(),"线程1");

  //最高级

  //td1.setPriority(Thread.MAX_PRIORITY);

  //启动第二条子线程

  Thread td2=new Thread(new YieldThread(),"线程2");

  //最低级

  td2.setPriority(Thread.MIN_PRIORITY);

  td1.start();

  td2.start();

  System.out.println(Thread.currentThread().getName());

  }

  }

  总结:sleep和yield区别

  A.sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。而yield只会给优先级>=当前优先级的线程执行机会

  B.Sleep方法会将线程转入阻塞状态,知道经过阻塞时间才会转入就绪状态。而yield是不会将线程转入阻塞状态的,它只是强制当前线程进入就绪状态。

  C.Sleep会抛出InterruptedException异常。而yield没有声明任何异常

  D.Sleep方法比yield方法有更好的移植性。

  E.通常不依靠yield来控制并发线程控制

  (6)线程的生命周期

  1.New新建 :当线程被创建时,该线程处于新建状态,此时它和其他java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量的值。(此时的线程没有表现出任何表现出任何线程的动态特征,程序也不会执行线程的线程执行体)new Thread()||new Thread(Runnable target,String name)。

  2.Runnable就绪:就绪也就是说启动线程,但是启动线程使用start方法,而不是run方法!永远不要调用线程对象的run()方法!调用start方法来启动线程,系统会将该run方法当成线程执行体来处理。如果直接调用线程对象的run方法。则run方法会立即执行,且在这个run方法的执行体未执行结束前其他线程无法并发执行(即系统会将run方法当做一个普通对象的普通方法,而不是线程执行体对待)

  附1:如果有一个主线程,一个子线程。当根据逻辑代码该调用子线程时不一定会立即调用,为了想在子线程start()后立即调用子线程,可以考虑使用Thread.sleep(1),这样会让当前线程(主线程)睡眠1毫秒,因为cpu在这1毫秒中是不会休息的,这样就会去执行一条处于就绪状态的线程。

  附2:不能对已经处于就绪状态的线程,再次使用start()

  3.Running 运行:当处于就绪状态时,该线程获得cpu,执行体开始运行,就处于运行状态了。

  4.Blocked 阻塞:线程不可能一直处于运行状态(线程执行体足够短,瞬间就可以完成的线程排除),线程会在运行过程中需要被中断,因为是并发,目的是会让其他线程获得执行的机会,线程的调度细节取决于OS采用的策略。(抢占式调度xp win7 linux unix..)。如果是一些特殊的小型设备可能采用 协作式调度(只有线程自己调用它的sleep()或yield()才会放弃所占用的资源)。

  5.Dead死亡:测试某条线程是否已经死亡,可以调用线程对象的isAlive()方法,当线程处于就绪,运行,阻塞时,返回true。线程处于新建,死亡时返回false。不能对已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,是不能再次作为线程执行的。

  当主线程结束时候,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受到主线程的影响。

  (7)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)

  A:是否有多线程环境

  B:是否有共享数据

  C:是否有多条语句操作共享数据

  (8)同步解决线程安全问题

  A:同步代码块

  synchronized(对象) {

  需要被同步的代码;

  }

  这里的锁对象可以是任意对象。

  B:同步方法

  把同步加在方法上。

  这里的锁对象是this

  C:静态同步方法

  把同步加在方法上。