多线程(概念、生命周期)

lang包之:多线程(概念、生命周期)

一、线程与进程的关系和概念

线程也称作轻量级进程,是进程的执行单元。是独立的、并发的执行流。

进程 Process:程序进入内存运行时,是处于运行过程中。

并发:同一时刻只能有一条指令执行,多个进程之间进行快速轮换;

并行:同一时刻多条指令在多个CPU上同时执行。

线程是进程的组成部分,一个进程至少拥有一个或多个线程,一个线程必须有一个父进程。

二、线程创建和启动

1.继承Thread类创建线程

1.1思路

(1)定义Thread类的子类,并重写run()方法;

run()方法中的代码就代表了线程需要完成的任务;run()方法也叫线程执行体。

(2)创建Thread子类的实例,即创建了线程对象;

(3)调用线程对象的start()方法来启动该线程(并执行线程中的run()方法)。

1.2示例

public class TestThread extends Thread{

      private int a=10;

      public void run(){

            for( ; a<100; a++){

                  System.out.println(this.getName+a);

                  //当该类继承Thread类时,直接使用this即可获得当前线程名

            }

      }

}

public static void main(String[] args){

      new TestThread().start();

      new TestThread().start();

      // 或者:

      // TestThread tt1=new TestThread();

      // TestThread tt2=new TestThread();

      // tt1.start();

      // tt2.start();

}

1.3注意事项

(1)关于线程中的变量

java程序运行时默认的主线程是main()方法中的代码。

两个线程中的实例变量a是无法共享的,也就是不会相互干涉,出现重叠。

(2)关于run()方法:

直接调用run()方法,则是main方法中主线程的run()方法,不会产生多线程;通过start()方法调用run()方法,则是开启多线程,并执行run()方法。永远不要直接调用run()方法。

2.实现Runnable接口创建线程类

2.1思路

(1)定义Runnable接口的实现类,并重写该接口的run()方法;

(2)创建实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程的start()方法来启动线程,并执行线程中的run()方法。target:目标,对象

2.2示例

public class TestRunnable implements Runnable{

      private int a=10;

      public void run(){

            for( ; a<100; a++){

                  System.out.println(this.getName+a);

                  //当该类继承Thread类时,直接使用this即可获得当前线程名

            }

      }

}

public static void main(String[] args){

      TestRunnable tr=new TestRunnable();

      // 错误 new TestRunnable()是点不出start()方法的

      new Thread(tr).start(); // 将Runnable实例化的对象做为线程对象。

      new Thread(tr).start();

      // 或者给线程指定个名字

      // new Thread(tr,"新线程名").start();

}

2.3注意事项

(1)关于线程名的获取

Thread.currentThread():返回正在执行的线程对象。

getName(): 返回线程对象的名字。在继承Thread类中,获得线程名可以直接使用this.getName(),但如果创建线程没指定名,都指向为一个:Thread-0但在实现Runnable接口中,获得线程名则只能使用Thread.currentThread().getName();

推荐使用第二种方式。

三、线程的生命周期

1. 新建和就绪

新建:当new了一个线程之后。

Java虚拟机为其分配内存,初始化成员变量的值。

就绪:当线程对象调用了start()方法之后。

Java虚拟机为其创建方法调用栈和程序计数器,表示该线程可以运行了。

2. 运行和阻塞

运行:处于就绪状态的线程获得了CPU执行权,开始执行run方法中的代码。

阻塞:当线程在运行中,没有获得CPU的执行权。

当前正在执行的线程被阻塞后,其他线程就可以获得执行的机会。

被阻塞的线程会在合适的时候重新进入就绪状态,等待获取CPU的执行权。

3. 死亡

死亡:线程结束。线程会以三种方式结束:

 (1)run()执行完,线程正常结束;

 (2)线程抛出一个未捕获的Exception或Error;

 (3)直接调用该线程的stop()方法来结束,但该方法容易导致死锁,不推荐使用。

注意事项:

(1) 一但子线程启动后,他与主线程拥有相同的地位。

当主线程结束时,其他线不受任何影响。

(2) 不要试图对一个已死亡的线程调用start()方法重启,死亡就是死亡,该线程不可再次作为线程执行。

报IllegalThreadStateException异常。

isAlive()方法:

当线程处于就绪、运行、阻塞三种状态时,返回true。

当线程处于新建、死亡两种状态时,返回false。

四、控制线程

1. join线程

让一个线程等待另一个线程完成。(你们等着让我先执行完)

join()方法通常由使用的线程的程序调用,以将大问题划

 

join();  等待被join的线程执行完成。

join(long millis);  等待被join的线程的时间最长为多少毫秒

2. 后台线程

也叫守护线程或精灵线程,它在后台运行,它的任务是为其他的线程提供服务。

JVM虚拟机的垃圾回收线程就是个典型的后台线程。

后台线程的特征:如果所有的前台线程死亡,后台线程会自动死亡。

setDaemon(true);  将指定的线程设置成后台线程。

isDaemon();  判断指定的线程是否为后台线程。

举例:

在main()中创建个线程,并设置为后台线程,运行程序:

当main()主线程(前台线程)结束,设置的后台线程也就结束了。

注意:

 (1)setDaemon()方法放在start()方法前,也就是先设置线程为后台线程,再启动线程。

 (2)isDaemon()方法放在setDaemon()方法之后;

 (3)主线程默认是前台线程。

3. 线程睡眠:sleep

让正在执行的线程暂停一段时间,并进入阻塞状态。

sleep(long millis); //让线程睡眠,直到多少毫秒后醒来

当线程调用sleep()方法进入阻塞状态,在其睡眠的时间内,该程序不会获得执行机会,即使没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

暂停主线程执行:Thread.sleep(1000);

4. 线程让步:yield

让正在执行的线程暂停下,进入就绪状态。相当于放弃一次机会,大家从新再抢一次。实际上,当某个线程调用了yield()方法暂停后,只有优先级与当前线程相同,或者更高的处于就绪状态的线程才会获得执行的机会。

sleep()与yield()的区别:

(1)sleep()方法暂停当前线程后,会给其他线程执行机会,不理会其他线程的优先级;

但yield()方法只会给优先级相同或更高的线程执行机会。

(2)sleep()方法会将线程转入阻塞状态,直到转入就绪状态;

而yield()方法是强制当前线程进入就绪状态,因此完全有可能某个线程暂停后再次被获得执行。

(3)sleep()方法要抛异常;yield()方法则不需要。

(4)sleep()方法移植性好,通常不建议使用yield()方法控制并发线程的执行。

五、改变线程的优先级

1.概念

每个线程都具有优先级,优先级高的线程获得较多的执行机会。每个线程的优先级都与创建它的父线程的优先级相同;

默认情况下,main()线程具有普通优先级,由main线程创建的子线程也都是普通优先级。

2. 设置线程优先级

(1)每个线程都有一个优先级;

(2)高优先级线程的执行优先于低优先级线程;

(3)每个线程都可以或不可以标记为一个守护程序。

(4)当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

3.方法

setPriority(int newPriority);  设置线程的优先级

// int newPriority的值1~10,也可用三个常量:

MAX_PRIORITY,其值:10

MIN_PRIORITY, 其值:1

NORM_PRIORITY,其值:5

getPriority();  得到线程的优先级

4. 注意事项

 (1)设置线程优先级的代码,要放在.start()前面。

 (2)优先级高不一定代表该线程就先抢到执行权,理论是是可以,但实际情况不一样。

六、线程同步

1. 线程安全问题

2. 同步代码块

run()方法不具有同步安全性。解决:同步监视器。同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问

   synchronized(obj){

      //同步的代码块

   }

obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

注意:任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

3. 同步方法

用synchronized修饰一个方法

注意:synchronized可修饰代码块、方法,但不能修饰构造方法和成员变量等。同步方法obj可以用this代替。

4. 死锁

死锁是两个线程相互等待对方释放同步监视器时就会发生死锁。

死锁不会出现任何异常和提示,只是所有线程处于阻塞状态,无法继续。

七、多线程

1. 为什么需要多线程?

现代大型应用程序都需要高效地完成大量任务,其中使用多线程就是一个提高效率的重要途径。

2. 多线程存在的意义

多线程可以让我们的程序部分可以产生同时运行的效果,各玩各的。提高效率是其次,主要是能让多段代码同时执行。

3.多线程的目的

为了最大限度的利用CPU资源。

4.线程创建和操作

4.1. 为什么要重写run方法?

目的:将自定义的代码存储在run方法中,让线程运行(也就是将同时要运行的代码写在run()方法中)。

4.2. 通过对象.run()调用,不用start()方法调用也可以吗?

可以,但run()就变成主线程中的方法,与线程没关系。

因为线程没有开启,你只执行了调用。

4.3. run()方法中仅仅是封装多线程运行的代码,而start()则是开启多线程的钥匙。

4.4. start()方法是开启多线程,并执行run()方法。

4.5. 多线程的一个特性:随机性(谁抢到谁执行,至于执行多长,CPU说了算)

4.6多线程的安全性。

卧倒:具有执行资格,执行权被其它线程抢走了。

经过了判断,在执行输出代码时卧倒了,CPU再过来执行时,数可能已变了,票有可能输出0号票,-1,-2等等错票。

//模拟让它停一下:Thread类下面的sleep(毫秒值);

分析问题出在哪?

问题原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句时执行了一部分,还没有执行完,另一个线程参与进来执行。导致了共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程执行完。在执行过程中,其他线程不可以参与执行。

4.7旗标(同步):synchronized

(1) 同步代码块:

    synchronized(对象){ 

      //需要被同步的代码

    }

    //表示该段代码上锁

    //如果是代码块后面要放上锁旗标,如果修饰方法,那么它的锁旗标是隐含的this。

(2)锁旗标也可以修饰方法----同步方法。

5线程的继承方式和实现方式有什么区别吗?(面试)

继承Thread类:线程代码存放在Thread子类run方法中;

实现Runnable:线程代码存在接口的子类的run方法中。

八、单例模式

1.破解单例模式

通过多线程调用静态公开方法

2.完善单例模式

将单例模式中的静态公开方法  同步。

3.示例

public Myf{

      //设置静态成员变量

      private static Myf m;

      //公开方法

public static Myf getM(){

      if(m==null){

            m=new Myf();

      }

      return m;

      }

       //私有化公开方法

          private Myf(){}

      }

  破解单例模式:采用多线程;

      new Thread(){

          public void run(){

            Myf m=Myf.getM();

            System.out.println(m.hashCode());

          }

      }.start();

      //再开个线程

      new Thread(){

          public void run(){

            Myf m=Myf.getM();

            System.out.println(m.hashCode());

          }

      }.start();

  如何防止存解?为单例模式中的公开方法设置锁。

      public synchronized static Myf getM(){

         ……

      }

猜你喜欢

转载自blog.csdn.net/LYQ2332826438/article/details/81347691
今日推荐