`

Java Thread 的线程 基础知识

阅读更多

 

 Java Thread

 

 threadjava多线程stringimportclass

A thread is a single sequential flow of control within a program.(线程是一个进程中一段独立的控制流)。一个进程可以拥有若干个线程。Java通过java.lang.Thread类来支持多线程。在Thread类中封装了独立的有关线程执行的数据和方法,并将多线程与面向对象的结构合为一体。

Java提供了两种方法创建线程,一种是继承Thread类并重写run方法,另一种则是实现接口Runnable。

继承Thread类

import java.lang.Thread;

import java.lang.Math;

import java.lang.InterruptedException;

public class SimpleThread extends Thread {

    //构造函数,设置线程的名字。否则自动生成的名字格式为 "Thread-"+n, n是整数。

    public SimpleThread(String str) {

        super(str);

    }

    public void run() {

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

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

            try {

                //sleep

                sleep((long)(Math.random() * 1000));

            } catch (InterruptedException e) {}

        }

        System.out.println("DONE! " + getName());

    }

}

 

//Thread的start方法

public native synchronized void start()

使该线程开始执行; Java 虚拟机调用线程 run 方法。

结果是两个线程同时运行: 当前线程 (方法 start 返回的线程) 和另一个线程 (执行方法 run 的线程)。

 

//Thread的run方法

public void run()

如果该线程是用不同的Runnable运行对象构造的, 则该Runnable对象的run方法被调用; 否则, 该方法不做任何事就返回。

Thread 的子类必须覆盖此方法。

抛出: IllegalThreadStateException如果该线程已经启动。

 

虽然run()函数实现了多个线程的并行处理,但不能直接调用run()函数,而是通过调用start()函数来调用run()函数。在调用start()的时候,start()函数会首先进行与多线程相关的初始化(这也是为什么不能直接调用run()函数的原因),然后再调用run()函数。

public class TwoThreadsDemo {

    public static void main (String[] args) {

        new SimpleThread("Jamaica").start();

        new SimpleThread("Fiji").start();

    }

}

实现接口Runnable

    如果有一个类,它已继承了某个类,又想实现多线程,那就可以通过实现Runnable接口来实现。

    1) Runnable接口只有一个run()函数。

    2) 把一个实现了Runnable接口的对象作为参数产生一个Thread对象,再调用Thread对象的start()函数就可执行并行操作。如果在产生一个Thread对象时以一个Runnable接口的实现类的对象作为参数,那么在调用start()函数时,start()会调用Runnable接口的实现类中的run()函数。

import java.lang.Thread;

import java.lang.System;

import java.lang.Math;

import java.lang.InterruptedException;

import java.lang.Runnable;

class MyThread implements Runnable{

    String name;

    private Thread myThread = null;

    public MyThread(String name){

      this.name = name;

    }

    public void start() {

        if (myThread == null) {

            myThread = new Thread(this, "Clock");

            myThread.start();

        }

    }

    public void run(){

        Thread curThread = Thread.currentThread();

        if (curThread == myThread) {

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

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

                try {

                    //sleep

                    //接口Runnable中没有方法sleep()

                    Thread.sleep(1000);

                } catch (InterruptedException e) {}

            }

            System.out.println("DONE! " + getName());

        }

    } 

}

总结:

两种提供线程run方法的途径:

1. 继承java.lang.Thread类并重写run方法

2. 实现java.lang.Runnable接口并实现run方法

如果类是从其它类继承下来的,则应该选用实现java.lang.Runnable接口的方法

 

若干常用的方法

■currentThread()              这是一个类方法,返回当前正在执行的线程。

■isAlive()                    判别一个线程是否仍然活着,包括这个线程正在执行或有机会被执行。返回一个布尔值。

■suspend()                  悬挂起某个线程,并使得这个线程只可被resume()方法激活。

■resume()                   与suspend()配合使用。唤醒线程。

■yeild()                       强迫线程交出执行权利供其它线程使用。

■stop()                       使线程进入死亡(dead)状态。

 

线程的生命周期

●新线程态(New Thread)

    当用new方法创建一个线程对象后,它仅仅是一个空的线程对象,没有分配有任何系统资源。当线程处于该状态时,只能只能启动或终止它任何其他操作都会引发IllegalThreadStateException异常

 

●可运行态(Runnable)    start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理 机。Java通过调度来实现多线程对处理机的共享。

 

●非运行态(Not Runnable)

当下面的任何一个情况发生时,线程进入不可运行状态

A thread becomes Not Runnable when one of these events occurs:

1) If a thread has been put to sleep, then the specified number of milliseconds must elapse.(sleep()方法被调用)

2) suspend()方法被调用;

3) The thread calls the wait method to wait for a specific condition to be satisifed. (线程使用wait()来等待条件变量)

4) The thread is blocking on I/O. (线程处于I/O等待)

 

在第三种情况发生时,线程处于等待状态(等待某个能使它返回可运行状态的事件发生)即使处理器空闲,线程也无法运行。当可使线程返回运行状态的条件满足时,线程处于可运行状态,一旦处理器可用,线程将会再次运行。(例如被方法suspend()挂起的进程就要等待方法resume()方可被唤醒)

For each entrance into the Not Runnable state, there is a specific and distinct escape route that returns the thread to the Runnable state. An escape route works only for its corresponding entrance. For example, if a thread has been put to sleep, then the specified number of milliseconds must elapse before the thread becomes Runnable again. The following list describes the escape route for every entrance into the Not Runnable state:

(对每一个进入非运行状态的入口,都有一个特殊且明确的回路返回到线程的可运行状态。该回路紧紧为对应的入口工作。例如一个线程sleep,必须经过一段时间后才能返回可运行状态。以下的列表描述每一个进入非运行状态入口对应的回路)

If a thread has been put to sleep, then the specified number of milliseconds must elapse.(如果线程sleep,必须等待设定的时间)

If a thread is waiting for a condition, then another object must notify the waiting thread of a change in condition by calling notify or notifyAll. More information is available in Synchronizing Threads. (如果线程等待条件,那么其它的对象必须通过改变条件来通知等待的线程)

If a thread is blocked on I/O, then the I/O must complete. (如果线程处于I/O等待,则I/O操作必须完成)

 

●死亡态(Dead)

当run()方法返回,或别的线程调用stop()方法,线程进入死亡态 。通常Applet使用它的stop()方法来终止它产生的所有线程。

 

The isAlive Method

如果线程已经运行并且没有停止则isAlive方法返回true. 如果isAlive方法返回false,则表示该线程可能是一个新线程或者是进入死亡状态。如果isAlive方法返回true,线程既可以是可运行的也可以是不可运行的。因此不能通过isAlive区分新线程和死亡状态的线程,只能区分可运行线程和不可运行线程

 

线程的同步

生产者/消费者模型

如果一个线程为另一个提供服务,那么我们把提供服务的线程称为生产者(Producer),另外一个线程称为消费者(consumer)。

假设生产者产生0-9的整数并存储在一个公共对象CubbyHole中,消费者从CubbyHole对象中获取生产者生成的整数并打印出来。假定生产者线程每生产一个数都会sleep一段随机时间。

    Class CubbyHole {

        private int contents;

        public int get() {

            return contents;

        }

        public void put(int contents) {

            this.coutents = contents;

        }

    }

 

    //Producer

    Class Producer extends Thread {

        private Cubbyhole hole;

        public Producer(Cubbyhole hole) {

            this.hole = hole;

        }

        public void run() {

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

                hole.put(i);

                try {

                    sleep((long)(Math.random()*100));

                } catch (InterruptedException ex) {

                    System.out.println("Thread Interrupted");

                    System.out.println(ex.getStackTrace());

                }

            }

        }

    }

 

    //Consumer

    public class Consumer extends Thread {

        private Cubbyhole hole;

        public Consumer(Cubbyhole hole){

            this.hole = hole;

        }

        public void run() {

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

                System.out.println(hole.get());

            }

        }

    }

在本例中生产者(Producer)和消费者(Consumer)共享公共的资源--CubbyHole对象。在上述程序中,同步问题将会发生在Producer和Consumer线程之间。如果Producer生成数据速度大于Consumer获取速度或者是Consumer获取速度大于Producer生产速度,Consumer获取的结果都是错误的。实际上,我们需要的是Consumer所取得的数据是Producer每一次生成的数据。

Producer和Consumer的活动在两个方法必须是同步的。

首先,该两个线程必须不能同时访问Cubbyhole对象。在Java线程中可以通过锁定对象来防止该情况发生。当一个对象被一个线程锁定后,第二个线程试图调用该对象的同步方法,则第二个线程将会被阻塞,直到对象被解除锁定。

其次,该两个线程必须做一些简单的合作。也就是说,Producer必须通过某种方法通知Consumer数据已经准备好而Consumer必须告诉Producer数据已经被获取了。线程类提供wait,notify和notifyAll方法帮助线程等待条件并当条件改变时通知其它线程

 

锁定对象

临界区(critical sections)

The code segments within a program that access the same object from separate, concurrent threads are called critical sections. 在java中 critical section 可以是一个block或者是用synchronized关键字标识的方法。

在Producer/consumer例子中,当Producer改变Cubbyhole对象时,Consumer不可以访问CubbyHole对象;当Consumer读取CubbyHole对象时Producer不可以改变CubbyHole对象。因此Cubbyhole对象的put和get方法都应该声明为critical sections.

public class CubbyHole {

    private int contents;

    private boolean available = false;

    public synchronized int get() {

        ...

    }

    public synchronized void put(int value) {

        ...

    }

}

无论什么时候当一个线程进入一个同步方法,其它线程都不能调用该对象的同步方法,直到对象解锁为止。因此当Producer调用put方法时,Cubbyhole对象被锁定,防止Consumer调用Cubbyhole的get方法。当put方法返回后,Coubbyhole对象被解锁。

public synchronized int get() {

    // CubbyHole locked by the Consumer

    ...

    // CubbyHole unlocked by the Consumer

}

锁和解锁都是通过java运行系统自动实现的,以保证数据的完整性。然而仅仅依靠同步还不行,两个线程之间必须相互通知对方各自的工作已经完成。

 

notifyAll和wait方法

在Cubbyhole对象中加入一私有布尔值成员变量available.当available为true时,允许获取数据;为false时允许写入数据。简单实现如下:

public synchronized int get() {

    if (available == true) {

        available = false;

        return contents;

    }

}

public synchronized void put(int value) {

    if (available == false) {

        available = true;

        contents = value;

    }

}

实际情况下以上两个方法不会正常工作。当Producer还没有生产数据时,此时available不可能为true.同样地,当Producer在Consumer取得数据之前调用put方法,则put方法什么都没有做。因此Consumer必须等待Producer把数据放入到CubbyHole中并通知Consumer已放入数据。同样地,Producer必须等待直到Consumer取走数据并通知它才能放入新的数据到CubbyHole中。该两个线程可以通过对象的wait和notifyAll方法来完成。put和get方法修改如下:

public synchronized int get() {

    while (available == false) {

        try {

            // wait for Producer to put value

            wait();

        } catch (InterruptedException e) {

        }

    }

    available = false;

    // notify Producer that value has been retrieved

    notifyAll();

    return contents;

}

public synchronized void put(int value) {

    while (available == true) {

        try {

            // wait for Consumer to get value

            wait();

        } catch (InterruptedException e) {

        }

    }

    contents = value;

    available = true;

    // notify Consumer that value has been set

    notifyAll();

}

 

在get方法中,一直循环直到Producer生成一个数,每一次循环都调用wait方法。wait方法放弃锁Cubbyhole对象,允许Producer锁定并更新CubbyHole对象,并等待Producer的通知。当Producer更新CubbyHole对象后,调用notifyAll方法通知Consumer.然后Consumer退出wait状态,available为true,退出循环,get方法返回值。同样地,put方法等待Consumer线程获取当前值

notifyAll方法唤醒所有等待同一对象(CubbyHole)的进程,被唤醒的进程竞争,一个线程获得锁后,其他线程返回等待。对象类也可以定义notify方法指定唤醒某一等待的线程

 

Java在类java.lang.Object中定义了wait()和notify()方法,调用它们也可以实现线程之间的同步。

要点:可以用sleep方法替代wait方法。但wait方法可以通过notify方法唤醒而sleep必须要等到特定时间过后才能唤醒。

■     public final void wait(long millseconds) throws InterruptedException

调用此方法时,被调对象进入等待状态,直到被唤醒或等待时间到。

■public final void notify()                        唤醒一个对象内处于等待状态的对象。

■public find void Allotify()                       唤醒一个对象内所有处于等待状态的对象。

线程优先级与调度

  由于我们一般使用的计算机是单CPU的,所以在执行多线程程序时需进行线程调度。线程调度是由线程的优先级决定的。高优先级的线程总是先运行的。Java采用的是抢占式(preemptive)的调度方式,即当高优先级的线程进入可运行(runnable)状态时,会抢占低优先级的线程的位置,并开始执行。当同时有两个或两个以上的线程具有高优先级并进入可运行状态,Java的调度会自动在这些线程间交替调度执行。

  在Java中,Thread类中预定义了三个常量:

  MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY,

  一个线程的优先级应在MAX_PRIORITY与MIN_PRIORITY之间。NORM_PRIORITY是缺省的优先级值,一般是MIN_PRIORITY与MAX_PRIORITY的平均值。

  在Java中,Thread类提供了方法设置和获取优先级。

  setPriority(int)  用于设置线程的优先数

  setPriority()   用于获取线程的优先数

 

线程组(Thread Group)

  线程组是包括了许多线程的对象集。每个线程有自己特定的线程组。一个线程在创建时就属于某个线程组,直至其执行结束,此线程不可更改其所属的线程组。Thread类中提供了构造方法使创建线程时同时决定其线程组。Thread类总共提供了六种构造方法:

  Thread();

  Thread(String);

  Thread(Runnable);

  Thread(Runnable,String);

  Thread(ThreadGroup,String);

  Thread(ThreadGroup,Runnable,String);

  前四种缺省了线程组,表示所创建的线程属于main线程组,后两种则指定了所创建的线程的线程组。线程可以访问自己所在的线程组,但不能访问本线程组的父类。对线程组进行操作就是对线程组中的各个线程同时进行操作。

  线程组的构造方法:

  ThreadGroup(String groupName)

创建名为groupName的线程组,该线程组的父类为当前线程所在程组。

  ThreadGroup(ThreadGroup parent,String groupName)

创建名为groupName的线程组,该线程组的父类是parent。

 

------------------------------------------------------------多线程要点----------------------------------------------------------------------------

1.多线程中有主内存和工作内存之分, 在JVM中,有一个主内存,专门负责所有线程共享数据;而每个线程都有他自己私有的工作内存, 主内存和工作内存分贝在JVM的stack区和heap区。

2.线程的状态有'Ready', 'Running', 'Sleeping', 'Blocked', 和 'Waiting'几个状态

3.线程运行次序并不是按照我们创建他们时的顺序来运行的,CPU处理线程的顺序是不确定的,如果需要确定,那么必须手工介入,使用setPriority()方法设置优先级。

4.我们无从知道一个线程什么时候运行,两个或多个线程在访问同一个资源时,需要synchronized

5. 每个线程会注册自己,实际某处存在着对它的引用,因此,垃圾回收机制对它就“束手无策”了。

 

6. Daemon线程区别一般线程之处是:主程序一旦结束,Daemon线程就会结束。

7. 一个对象中的所有synchronized方法都共享一把锁,这把锁能够防止多个方法对通用内存同时进行的写操作。synchronized static方法可在一个类范围内被相互间锁定起来。

8. 对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized,否则就不能正常工作。

9. 假设已知一个方法不会造成冲突,最明智的方法是不要使用synchronized,能提高些性能。

10. 如果一个"同步"方法修改了一个变量,而我们的方法要用到这个变量(可能是只读),最好将自己的这个方法也设为 synchronized。

 

11. synchronized不能继承, 父类的方法是synchronized,那么其子类重载方法中就不会继承“同步”。

12. 线程堵塞Blocked有几个原因造成:

(1)线程在等候一些IO操作

(2)线程试图调用另外一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。

13.原子型操作(atomic), 对原始型变量(primitive)的操作是原子型的atomic. 意味着这些操作是线程安全的, 但是大部分情况下,我们并不能正确使用,来看看 i = i + 1 , i是int型,属于原始型变量:

 

(1)从主内存中读取i值到本地内存.

(2)将值从本地内存装载到线程工作拷贝中.

(3)装载变量1.

(4)将i 加 1.

(5)将结果给变量i.

(6)将i保存到线程本地工作拷贝中.

(7)写回主内存.

 

注意原子型操作只限于第1步到第2步的读取以及第6到第7步的写, i的值还是可能被同时执行i=i+1的多线程中断打扰(在第4步)。double 和long 变量是非原子型的(non-atomic)。数组是object 非原子型。

14. 由于13条的原因,我们解决办法是:

class xxx extends Thread{

//i会被经常修改

private int i;

public synchronized int read(){ return i;}

public synchronized void update(){ i = i + 1;}

    ..........

}

15. Volatile变量, volatile变量表示保证它必须是与主内存保持一致,它实际是"变量的同步", 也就是说对于volatile变量的操作是原子型的,如用在long 或 double变量前。

 

16. 使用yield()会自动放弃CPU,有时比sleep更能提升性能。

17. sleep()和wait()的区别是:wait()方法被调用时会解除锁定,但是我们能使用它的地方只是在一个同步的方法或代码块内。

18. 通过制造缩小同步范围,尽可能的实现代码块同步,wait(毫秒数)可在指定的毫秒数可退出wait;对于wait()需要被notisfy()或notifyAll()踢醒。

19. 构造两个线程之间实时通信的方法分几步:

(1). 创建一个PipedWriter和一个PipedReader和它们之间的管道;

PipedReader in = new PipedReader(new PipedWriter())

(2). 在需要发送信息的线程开始之前,将外部的PipedWriter导向给其内部的Writer实例out

(3). 在需要接受信息的线程开始之前,将外部的PipedReader导向给其内部的Reader实例in

(4). 这样放入out的所有东西度可从in中提取出来。

20. synchronized带来的问题除性能有所下降外,最大的缺点是会带来死锁DeadLock,只有通过谨慎设计来防止死锁,其他毫无办法,这也是线程难以驯服的一个原因。不要再使用stop() suspend() resume()和destory()方法

 

21. 在大量线程被堵塞时,最高优先级的线程先运行。但是不表示低级别线程不会运行,运行概率小而已。

22. 线程组的主要优点是:使用单个命令可完成对整个线程组的操作。很少需要用到线程组。

23. 从以下几个方面提升多线程的性能:

检查所有可能Block的地方,尽可能的多的使用sleep或yield()以及wait();

尽可能延长sleep(毫秒数)的时间;

运行的线程不用超过100个,不能太多;

分享到:
评论

相关推荐

    java线程同步基础知识

    java线程同步的详细讲解,有利于解开一些困扰。

    Java基础知识点总结.docx

    无论是工作学习,不断的总结是必不可少...Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java 性能优化 362

    Java多线程pdf.rar

    部分主要阐述Thread的基础知识,详细介绍线程的API使用、线程安全、线程间数据通信,以及如何保护共享资源等内容,它是深入学习多线程内容的基础

    Java多线程开发(构造函数的使用)

    本文主要总结了Java多线程开发中线程创建时多用到的创建方式(不同的构造函数)以及线程组相关的基础知识

    Java高并发编程详解:多线程与架构设计 (Java核心技术系列)

    部分主要阐述Thread的基础知识,详细介绍线程的API使用、线程安全、线程间数据通信,以及如何保护共享资源等内容,它是深入学习多线程内容的基础。 第二部分引入了ClassLoader,这是因为ClassLoader与线程不无关系,...

    JavaMultiThreading:锁,信号量和线程池的实现以及线程基础知识

    -&gt;您可以通过扩展Thread类或通过实现可运行接口在Java中创建线程。 -&gt;由于您只能扩展一次类,因此可运行接口实现是首选方法。 如何使用Runnable接口创建线程? -&gt; Runnable runnable = new Runnable(){public ...

    Java开发技术大全(500个源代码).

    示例描述:本章介绍开发Java的基础语法知识。 accumulationByDoWhile.java 用do~while语句写的累加程序 accumulationByFor.java 用for语句写的累加程序 accumulationByWhile.java 用while语句写的累加程序 ...

    JAVA基础课程讲义

    通过继承Thread类实现多线程 168 通过Runnable接口实现多线程 169 线程状态和sleep/yield/join/stop/destroy方法 170 新生状态 170 就绪状态 170 运行状态 170 死亡状态 170 终止线程的典型方法(重要!!!) 171 ...

    Java实习生面试复习(五):Thread线程学习

    线程(Thread)是并发编程的基础,也是程序执行的最小单元,它依托进程而存在。一个进程中可以包含多个线&gt; 程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源、更加轻量化。 线程的...

    Java2实用教程.rar

    9 2Thread类的子类创建线程 9 3使用Runnable接口 9 4线程的常用方法 9 5GUI线程 9 6线程同步 9 7在同步方法中使用wait notif 和nodf3 All 方法 9 8挂起 恢复和终止线程 9 9计时器线程Timer 9 10线程联合 9 11守护...

    java基础案例与开发详解案例源码全

    19.1.1 网络基础知识494 19.1.2 网络基本概念495 19.1.3 网络传输协议496 19.2 Java网络类和接口497 19.3 InetAddress类498 19.4 URL和URLConnection类499 19.5 Scoket套接字505 19.6 Datagram套接字509 19.7 综合...

    多线程下载技术论文.rar

    3 相关基础知识以 6 3.1 JAVA中的多线程与线程安全 6 3.1.1 Java中的多线程 6 3.1.2 Java中的线程安全 7 3.2 HTTP协议简介 8 3.3 断点续传原理 8 4 需求分析 10 4.1用户需求分析 10 4.2 业务流分析 11 5. ...

    Java并发编程(学习笔记).xmind

    基础知识 线程安全性 定义 当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的 无状态对象一定是线程安全的,大多数Servlet都是无状态的 原子性 一组不可分割...

    Java典型模块

    4.4 知识点扩展——线程的基础知识 4.4.1 为什么要使用线程 4.4.2 多线程程序的编写方式 4.5 小结 第5章 模拟做饭场景(线程的join()方法) 教学视频:7分钟5.1 做饭场景原理 5.1.1 项目结构框架分析 5.1.2 项目...

    java核心知识点整理.pdf

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    多线程文章目录

    了解线程的状态、常用方法以及Thread、Runnable、Callable、Future、FutureTask这些基础概念和简单应用。 Java多线程②——多线程知识梳理 有助于了解线程池相关的知识、包括线程池执行逻辑、常用方法; 以及java为...

    《Java和Android开发实战详解》第6到10章源代码-by 南邮-陈杨

    10.3 线程的基础知识 200 10.4 创建Java的线程 201 10.4.1 实现Runnable接口来创建线程 201 10.4.2 继承Thread类来创建线程 204 10.5 集合对象简介 205 10.5.1 Java Collections Framework 206 10.5.2 ...

    疯狂JAVA讲义

    第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...

    JAVA核心知识点整理(有效)

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

Global site tag (gtag.js) - Google Analytics