Java多线程

整理转载自:

http://blog.csdn.net/evankaka

https://www.cnblogs.com/wxd0108/p/5479442.html

http://www.runoob.com/java/java-multithreading.html

目录

一、    线程和进程    1

二、    继承Thread类和实现Runnable实现多线程    2

1.    继承Thread    2

2.    实现Runnable接口    4

三、    ThreadRunnable的区别    6

四、    线程状态转换    6

1.    新建状态    6

2.    就绪状态    7

3.    运行状态    7

4.    阻塞状态    7

五、    线程的优先级    8

1.    线程优先级    8

2.    线程睡眠sleep()    9

3.    线程等待wait()    9

4.    线程让步yield()    9

5.    线程加入join()    9

六、    常用函数说明    9

1.    sleep(long millis):    9

2.    join():    9

3.    yield():    9

4.    sleep()yield()的区别    10

七、    常见线程名词解释    10

八、    线程同步    11

1.    synchronized关键字的作用域有两种:    11

2.    如何使用synchronized同步解决多线程共享数据同步问题    12

3.    讨论synchronized用到不同地方对代码产生的影响:    12

4.    线程同步的目的    13

5.    synchronized, wait, notify结合:典型场景生产者消费者问题    14

6.    多线程的内存模型:    15

 

 

  1. 线程和进程

    首先我们先了解下在操作系统中进程和线程的区别:

    1. 进程:每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销,一个进程包含多个线程(进程时资源分配的最小单位)
    2. 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程时CPU调度的最小单位)
    3. 线程和进程一样都分为五个阶段:创建、就绪、运行、阻塞、终止。
    4. 多进程是指操作系统能同时运行多个任务(程序)
    5. 多线程是指在同一个程序中有多个顺序流在执行。

    Java想要实现多线程,有两种方法,一种是继承Thread类,另一种是实现Runnable接口。

  2. 继承Thread类和实现Runnable实现多线程
    1. 继承Thread

class Thread1 extends Thread{

public Thread1(){

//重写类的构造方法

}

public void run(){

//重写父类run方法

}

}

public class Main {

 

public static void main(String[] args) {

Thread1 mTh1=new Thread1("A");

Thread1 mTh2=new Thread1("B");

mTh1.start();

mTh2.start();

 

}

 

}

输出:

A运行 : 0

B运行 : 0

A运行 : 1

A运行 : 2

A运行 : 3

A运行 : 4

B运行 : 1

B运行 : 2

B运行 : 3

B运行 : 4

再运行一下:

A运行 : 0

B运行 : 0

B运行 : 1

B运行 : 2

B运行 : 3

B运行 : 4

A运行 : 1

A运行 : 2

A运行 : 3

A运行 : 4

程序启动运行main()的时候,Java虚拟机会启动一个进程,主线程mainmain(0函数调用时候被创建,随着调用Main类的两个对象的start()方法,另外两个线程也被启动,这样,整个应用程序就在多线程下运行。

线程对象只会调用start()方法,不会调用run()方法,run()方法的执行是由操作系统决定的。

注意:start()方法调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

从程序运行的结果可以发现,多线程程序是乱序执行的,只有乱序执行的代码才有必要设计为多线程。

Thread.sleep()方法调用的目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上,所有的多线程代码的执行顺序都是不确定的,每次执行的结果都是随机的。

但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

  1. 实现Runnable接口

    采用实现Runnable接口也是创建多线程的一种方法

class Thread2 implements Runnable{

private String name;

 

public Thread2(String name) {

this.name=name;

}

 

@Override

public void run() {

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

System.out.println(name + "运行 : " + i);

try {

Thread.sleep((int) Math.random() * 10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

 

}

 

}

public class Main {

 

public static void main(String[] args) {

new Thread(new Thread2("C")).start();

new Thread(new Thread2("D")).start();

}

 

}

输出:

 

 

 

C运行 : 0

D运行 : 0

D运行 : 1

C运行 : 1

D运行 : 2

C运行 : 2

D运行 : 3

C运行 : 3

D运行 : 4

C运行 : 4

Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Threadstart()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

  1. ThreadRunnable的区别

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runablecallable类线程,不能直接放入继承Thread的类

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

  1. 线程状态转换
    1. 新建状态

      使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。

       

    2. 就绪状态

      当线程调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

       

    3. 运行状态

      如果就绪状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

       

    4. 阻塞状态

      如果一个线程执行了sleep()休眠、suspend()挂起等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为以下三种:

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态,运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)只有当同一对象的其他进程执行notify()时才会被释放。
  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)JVM会把该线程放入锁池中。。
  • 其他阻塞:通过调用线程的 sleep() join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

    Sleep()不会释放持有的锁,只会超时使得线程终止。

  • 死亡状态(Dead):线程执行完或者因异常退出了run()方法,该线程结束生命周期。

  1. 线程的优先级
    1. 线程优先级

    每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是 1 Thread.MIN_PRIORITY - 10 Thread.MAX_PRIORITY )。

    线程可以具有的最高优先级,取值为10

    线程可以具有的最低优先级,取值为1

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

  1. 线程睡眠sleep()

    Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态

  2. 线程等待wait()

    线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。

  3. 线程让步yield()

    线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

  4. 线程加入join()

    线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

  1. 常用函数说明
    1. sleep(long millis):

      在指定的毫秒数内让当前正在执行的线程休眠(暂停运行)

    2. join():

      指等待t线程终止。该线程是指的主线程等待子线程的终止。有时主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

    3. yield():

      暂停当前正在执行的线程对象,并执行其他线程。

      yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。

      yield()不会导致线程转到等待/睡眠/阻塞状态

    4. sleep()yield()的区别
    5. sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
    6. sleep 方法允许较低优先级的线程获得运行机会;但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。
    7. sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的;yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为"退让",它把运行机会让给了同等优先级的其他线程

 

  1. 常见线程名词解释
    1. 主线程:JVM调用程序main()所产生的线程。
    2. 当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
    3. 后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
    4. 前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()setDaemon()方法来判断和设置一个线程是否为后台线程。
  2. 线程同步
    1. synchronized关键字的作用域有两种:
      1. 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
      2. 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
      3. synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
    2. 如何使用synchronized同步解决多线程共享数据同步问题

      在进一步阐述之前,我们需要明确几点:

      A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

    B.每个对象只有一个锁(lock)与之相关联。

    C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

    1. 讨论synchronized用到不同地方对代码产生的影响:
      1. synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

  1. 同步块,示例代码如下:

public void method3(SomeObject so){

synchronized(so){

//…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序。

  1. synchronized作用于static 函数,示例代码如下:

Class Foo

{

public synchronized static void methodAAA()

{

//….

}

public void methodBBB()

{

synchronized(Foo.class) // class literal(类名称字面常量)

}

}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

  1. 线程同步的目的
    1. 线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏
    2. 线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
    3. 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
    4. 对于同步,要时刻清醒在哪个对象上同步,这是关键。
    5. 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对"原子"操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
    6. 当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
    7. 死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
  2. synchronized, wait, notify结合:典型场景生产者消费者问题

public synchronized void consume()

{

if(this.product <= MIN_PRODUCT)

{

try

{

wait();

System.out.println("缺货,稍候再取");

}

catch (InterruptedException e)

{

e.printStackTrace();

}

return;

}

System.out.println("消费者取走了第" + this.product + "个产品.");

this.product--;

notifyAll(); //通知等待去的生产者可以生产产品了

}

 

  1. 多线程的内存模型:

    main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)

  2.  
posted on 2018-08-23 10:19  夜尽天明00  阅读(143)  评论(0编辑  收藏  举报