`
longgangbai
  • 浏览: 7331281 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java Thread的一点知识

 
阅读更多

线程与进程的区别

  

 线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行地处理一些事情。线程通过并行的处理给用户带来更好的使用体验,比如你使用的邮件系统(outlookThunderbirdfoxmail等),你当然不希望它们在收取新邮件的时候,导致你连已经收下来的邮件都无法阅读,而只能等待收取邮件操作执行完毕。这正是线程的意义所在.

 

实现一个线程的方法?

多线程有两种实现方法,分别是继承Thread与实现Runnable接口

继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。

实现java.lang.Runnable接口实现它的run()方法,并将线程的执行主体放入其中。

这两种实现方式的区别并不大。继承Thread的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。而使用Runnable接口的方式就不存在这个问题了,而且这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种方式。

继承Thread类实现线程的示例:

public class ThreadTest extends Thread { 

    public void run() { 

        // 在这里编写线程执行的主体 

        // do something 

    } 

} 

实现Runnable接口实现多线程的示例:

public class RunnableTest implements Runnable { 

    public void run() { 

        // 在这里编写线程执行的主体 

        // do something 

    } 

} 

 

 

synchronized

每个对象只有一把监视锁(monitor lock),对于同步块,synchornized获取的是参数中的对象锁:

synchornized(obj){

  //...............

}

线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.假如:

public void test(){

  Object o = new Object();

  synchornized(obj){

  //...............

  } }

对于这段程序,多个线程之间执行到Object o = new Object();会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.而如果是类的属性:

class Test{

    Object o = new Object();

    public void test(){

    synchornized(o){

    //...............

    }    }

}

所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁.有时我们会这样:

public void test(){

    synchornized(this){

    //...............

    }  }

那么所有执行Test实例的线程只能有一个线程执行.synchornized(o)synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).而对于

synchornized(Test.class){

    //...............

}

这样的同步块而言,所有调用Test多个实例的线程只能有一个线程可以执行.

[synchornized方法]

如果一个方法声明为synchornized,则等同于在此方法上调用synchornized(this).如果一个静态方法被声明为synchornized,则等同于在此方法上调用synchornized(.class).

如果一个类有两个synchronized方法,是不是同一时间只能有一个处于运行,这个两个方法的锁是一样的吗?答:它们的锁是this对象,如果是对不同的对象调用方法,起不到锁的作用.这儿有一个前提是加在了非静态方法上,如果是静态方法上则锁得是类对象,那么同一时间只能有一个处于运行。

 

 

线程的状态 

Java中的线程有五种状态分别是:新建、就绪、运行、死亡、阻塞。

1.新建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在JVM没有为其分配线程运行资源;对处于创建状态的线程可以进行两种操作:一是启动 (start)操作,使其进入可运行状态,二是终止(stop)操作,使其进入消亡状态。如果进入到消亡状态,那么,此后这个线程就不能进入其他状态,也就是说,它不再存在了。(不能进入阻塞状态)

start方法是对应启动操作的方法,其具体功能是为线程分配必要的系统资源;将线程设置为就绪状态,从而可以使系统调度这个线程。  

2.就绪状态(可运行状态):在处于新建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM线程调度器按照线程的优先级对该线程进行调度,从而能够获得CPU时间片的机会。

在可运行状态可以进行多种操作,最通常的是从run()方法正常退出而使线程结束,进入消亡状态。此外,还可以有如下操作:

     挂起操作,通过调用suspend方法来实现; //须通过恢复(resume)操作使线程回到可运行状态。

     睡眠操作,通过调用sleep方法来实现;

     等待操作,通过调用wait方法来实现;

     退让操作,通过调用yield方法来实现;//CPU控制权提前转交给同级优先权的其他线程。

     终止操作,通过调用stop方法来实现。

     前面三种操作都会使一个处于可运行状态的线程进入不可运行状态。  

3.阻塞状态:线程能够运行,但有某个条件阻止它运行。当线程处于阻塞状态时,调度机制将忽略线程,不会分配给线程任何CPU时间直到线程重新进入了就绪状态,它才有可能执行操作。

一个处于可运行状态的线程,如果遇到挂起 (suspend)操作、睡眠(sleep)操作或者等待(wait)操作,就会进入不可运行状态(阻塞状态)。另外,如果一个线程是和I/O操作有关的,那么,在执行I/O指令时,由于外设速度远远低于处理器速度而使线程受到阻塞,从而进入不可运行状态,只有外设完成输入/输出之后,该线程才会自动回到可运行状态。线程进入不可运行状态后,还可以再回到可运行状态。通常有三种途径 使其恢复到可运行状态。

     一是自动恢复。通过睡眠(sleep)操作而进入不可运行状态的线程会在过了指定睡眠时间以后自动恢复到可运行状态;由于I/O阻塞而进入不可运行状态的线程在外设完成I/O操作后,自动恢复到可运行状态。

     二是用恢复(resume)方法使其恢复。 如果一个线程由于挂起(suspend)操作而从可运行状态进入不可运行状态,那么,必须用恢复(resume)操作使其再恢复到可运行状态。

     三是用通知(notifynotiyA11)方法使其恢复。 如果由于等待(wait)操作转入不可运行状态,那么,必须通过调用notify方法或notifyAll方法才能使其恢复到可运行状态。采用等待操作往往是由于线程需要等待某个条件变量,当获得此条件变量后,便可由notifynotifyAll方法使线程恢复到可运行状态。

     在不可运行状态,也可由终止(stop)操作使其进入消亡状态。  

4.死亡状态:当线程的run()方法完成时就认为它死去。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程在一定条件下,状态会发生变化。

    在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。注意:阻塞态只能进入就绪态。

常见线程名词解释

主线程:JVM调用程序main()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()setDaemon()方法来判断和设置一个线程是否为后台线程。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

 

启动一个线程是用run()还是start()?

启动线程肯定要用start()方法。当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。cpu分配给它时间时,才开始执行run()方法(如果有的话)START()是方法,它调用RUN()方法.RUN()方法是你必须重写的. run()方法中包含的是线程的主体。

继承Thread类的启动方式:

public class ThreadStartTest { 

    public static void main(String[] args) { 

        ThreadTest tt = new ThreadTest();// 创建一个线程实例 

         tt.start();  // 启动线程 

    } 

} 

实现Runnable接口的启动方式:

public class RunnableStartTest { 

    public static void main(String[] args) { 

       Thread t = new Thread(new RunnableTest());    // 创建一个线程实例

        t.start();  // 启动线程 

    } 

} 

实际上这两种启动线程的方式原理是一样的。首先都是调用本地方法启动一个线程,其次是在这个线程里执行目标对象的run()方法。那么这个目标对象是什么呢?为了弄明白这个问题,我们来看看Thread类的run()方法的实现:

public void run() { 

    if (target != null) { 

        target.run(); 

    } 

} 

当我们采用实现Runnable接口的方式来实现线程的情况下,在调用new Thread(Runnable target)构造器时,将实现Runnable接口的类的实例设置成了线程要执行的主体所属的目标对象target当线程启动时,这个实例的 run()方法就被执行了。当我们采用继承Thread的方式实现线程时,线程的这个run()方法被重写了,所以当线程启动时,执行的是这个对象自身的 run()方法。总结起来就一句话,如果我们采用的是继承Thread类的方式,那么这个target就是线程对象自身,如果我们采用的是实现Runnable接口的方式,那么这个target就是实现了Runnable接口的类的实例。

 

线程同步的方法

用什么关键字修饰同步方法 ? synchronized关键字修饰同步方法

 同步有几种实现方法,都是什么?分别是synchronized,waitnotify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock
sleep():
使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():
唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():
唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

实现同步的方式

同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,这两种方式都要用到synchronized关键字。

给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。下面代码是一个同步方法的示例:

public synchronized void aMethod() { 

    // do something 

} 

public static synchronized void anotherMethod() { 

    // do something 

} 

线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。

同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。

synchronized 关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:

public class ThreadTest implements Runnable{

public synchronized void run(){

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

System.out.print(" " + i);

}

}

public static void main(String[] args) {

Runnable r1 = new ThreadTest(); //也可写成ThreadTest r1 = new ThreadTest();

Runnable r2 = new ThreadTest();

Thread t1 = new Thread(r1);

Thread t2 = new Thread(r2);

t1.start();

t2.start();

}}

在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 两个对象(r1,r2)的线程。而不同的对象的数据是不同的r1,r2 有各自的run()方法,所以输出结果无法预知。

synchronized的目的是使同一个对象的多个线程在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。

示例3

public class ThreadTest implements Runnable{

public synchronized void run(){

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

System.out.print(" " + i);

}

}

public static void main(String[] args){

Runnable r = new ThreadTest();

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);

t1.start();

t2.start();

}}

如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。

示例4

public class ThreadTest implements Runnable{

public void run(){

synchronized(this){

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

System.out.print(" " + i);

}} }

public static void main(String[] args){

Runnable r = new ThreadTest();

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);

t1.start();

t2.start();

}}

这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。

示例5

public class ThreadTest implements Runnable{

public void run(){

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

System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);

}

synchronized(this){

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

System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);

}} }

public static void main(String[] args){

Runnable r = new ThreadTest();

Thread t1 = new Thread(r,"t1_name");

Thread t2 = new Thread(r,"t2_name");

t1.start();

t2.start();

} }

运行结果:

t1_name : for loop : 0

t1_name : for loop : 1

t1_name : for loop : 2

t2_name : for loop : 0

t1_name : for loop : 3

t2_name : for loop : 1

t1_name : for loop : 4

t2_name : for loop : 2

t1_name : synchronized for loop : 0

t2_name : for loop : 3

t1_name : synchronized for loop : 1

t2_name : for loop : 4

t1_name : synchronized for loop : 2

t1_name : synchronized for loop : 3

t1_name : synchronized for loop : 4

t2_name : synchronized for loop : 0

t2_name : synchronized for loop : 1

t2_name : synchronized for loop : 2

t2_name : synchronized for loop : 3

t2_name : synchronized for loop : 4

第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2t1 开始执行第二个for 循环,当t1 的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。  

线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行地处理一些事情。线程通过并行的处理给用户带来更好的使用体验,比如你使用的邮件系统(outlookThunderbirdfoxmail等),你当然不希望它们在收取新邮件的时候,导致你连已经收下来的邮件都无法阅读,而只能等待收取邮件操作执行完毕。这正是线程的意义所在。

分享到:
评论

相关推荐

    一点课堂 JAVA核心知识点整理.zip

    "一点课堂 JAVA核心知识点整理.zip"这个压缩包文件包含了关于Java编程的关键概念和技能的详细总结,非常适合初学者和有经验的开发者进行学习和复习。 首先,Java的核心知识点包括基础语法。这包括变量声明、数据...

    Java实现12306查票

    Java提供了Thread类和Runnable接口来实现多线程,同时JavaFX也提供了Task和Service类来更方便地处理FX应用程序中的后台任务。 7. **事件驱动编程**: JavaFX基于事件驱动模型,用户与界面的交互(如点击按钮)会触发...

    java基础知识大总结

    ### Java基础知识大总结 #### 一、基础概念与特性 1. **Null的打印结果**:在Java中,如果尝试打印一个null值的对象引用,输出结果将会是`null`。 2. **字符串常量的拼接问题**:当在字符串中出现如`'10'`这样的...

    Java练习题库(含答案及解析).pdf

    Java提供了Thread类和Runnable接口来创建多线程程序。 以上便是根据提供的Java练习题库内容整理出的知识点概述,掌握了这些基础知识点,对进一步学习Java的高级特性和开发实际应用程序都有很大帮助。

    JAVA 火车售票系统

    Java的线程机制可以帮助你实现这一点,如创建Thread类的子类或者使用Runnable接口。 6. **IO流**:用于读写数据,如保存和加载火车信息、用户购票记录等。Java的FileInputStream和FileOutputStream可以用来读写文件...

    Java code Java code

    标签也进一步确认了这一点,同样显示为"Java code Java code"。下面我们将详细讨论Java编程语言的关键知识点,并可能在这些文件中找到的相应内容。 1. **Java基础知识**:Java是一种面向对象的、跨平台的编程语言,...

    java入门非常的浅显易懂

    "java入门非常的浅显易懂"这个标题暗示了我们将要探讨的内容是针对新手的Java基础知识,而描述中的“很好的入门书”进一步确认了这一点,意味着我们将深入浅出地讲解Java的基本概念。 1. **Java简介**:Java是由Sun...

    java mp3小程序代码

    `Thread`或`Runnable`接口可用于实现这一点。 4. **用户界面(UI)设计**:如果程序具有图形用户界面,可能需要使用Java Swing或JavaFX来创建按钮、进度条等控件,实现与用户的交互。 5. **事件监听**:在UI中,...

    JAVA技术500篇

    以上是Java技术500篇可能涉及的主要知识点,每一点都值得深入学习和实践,以提升你的Java编程能力。在实际开发中,还需要结合设计模式、最佳实践以及不断涌现的新技术和框架,如Spring Boot、MyBatis、Docker、...

    java基础教程(强烈推荐)

    Java通过内置的Thread类和它的子类来支持多线程编程,提供了一套丰富的API来处理多线程环境下的复杂问题。 Java语言的另一个重要特性是安全性。Java通过字节码验证器以及类加载器来保证应用程序的安全性。字节码...

    JAVA多线程教材

    1. **Java多线程基础知识**:Java多线程允许程序同时执行多个任务,这是通过创建和管理线程实现的。Java提供了两种创建线程的方式:继承Thread类和实现Runnable接口。线程的状态包括新建、就绪、运行、阻塞和终止,...

    JAVA的MPEG播放器

    Java的Thread类和ExecutorService可以帮助实现这一目标。 8. **事件处理**:Java的事件监听机制使得播放器能够响应用户的操作,如点击播放按钮或调整音量滑块。 9. **资源管理**:播放器需要有效地管理内存和系统...

    java程序设计课件

    这个“java程序设计课件”为那些已经有一定Java基础的学习者提供了深入学习和巩固知识的机会。课件中不仅涵盖了Java的基础知识,还涉及了如何实现数据库连接,这是在实际项目开发中非常实用的一项技能。 首先,Java...

    java案例经典下载

    Java的EventListener接口和相关类可以帮助我们实现这一点。 5. **算法**:游戏规则的实现需要算法支持,比如洗牌算法、判断胜败的逻辑等。 6. **IO流**:如果游戏有保存和加载功能,那么文件输入输出流...

    JAVA解惑.pdf

    《JAVA解惑》这本书主要针对...以上是《JAVA解惑》一书中可能涉及的部分知识点,每一点都值得深入学习和实践。通过掌握这些内容,开发者可以更好地解决Java编程中的各种问题,提升编程技能,写出更高效、更可靠的代码。

    java学习笔记来自java黑马B站网课,没有图片 看不了一点

    本资源“java学习笔记”源自B站上的黑马程序员Java教学课程,虽然没有图片辅助,但通过文本资料,我们可以深入理解Java的核心概念和关键知识点。 首先,Java的学习通常从基础语法开始,包括变量、数据类型、运算符...

    Java程序设计习题

    - **知识点**: 在Java中,通过继承`Thread`类来创建线程的方式之一。 - **选项分析**: - A、`classA4 extends Thread`: 正确的继承方式,但缺少`run()`方法实现。 - B、`classA4 implements Thread`: 错误,因为`...

    java课程设计音乐时钟

    Java的Thread类和Runnable接口可以帮助你实现这一点。同时,线程同步也是需要注意的问题,以避免多个线程间的冲突。 6. **资源管理**:音乐文件和其他资源的加载和释放是资源管理的一部分。理解如何正确地打开、...

    JAVA贪吃蛇源代码

    在Java中,我们可以使用`while(true)`循环来实现这一点,并通过`Thread.sleep()`方法控制游戏的帧率,以确保游戏流畅运行。 接下来,我们来看看图形用户界面(GUI)的构建。Java提供了`java.awt`和`javax.swing`包...

    Java初学者可尝试的Java小游戏(贪吃蛇+飞机大战)

    总的来说,通过分析和实现这两个Java小游戏,初学者可以巩固理论知识,提升编程技能,并逐渐培养解决实际问题的能力。这是一个很好的学习路径,不仅能激发学习兴趣,也有助于理解Java编程的精髓。希望这个资源对你的...

Global site tag (gtag.js) - Google Analytics