Java线
线程是一个单一的程序流程。多线程是指一个程序,可以在同一时间执行多个任务。每个任务是由一个单独的线程以完成。那。够同一时候在一个程序中执行,而且每个线程完毕不同的任务。程序能够通过控制线程来控制程序的执行,比如线程的等待、休眠、唤起线程等。
本章将向读者介绍线程的机制、怎样操作和使用线程以及多线程编程。
1. 线程的基本知识
线程是程序执行的基本单位,一个程序中能够同一时候执行多个线程。
假设程序被设置为多线程, 能够提高程序执行的效率和处理速度。 Java中线程的实现通常有两种方法: 派生 Thread类和实现 Runnable接口。本节主要讲述线程的概念和创建线程的方法。
1. 什么是线程
传统的程序设计语言同一时刻仅仅能执行单任务操作。效率很低,假设网络程序在接收数据时发生堵塞。仅仅能等到程序接收数据之后才干继续执行。
随着 Internet 的飞速发展。这样的单任务执行的状况越来越不被接受。假设网络接收数据堵塞,后台服务程序就会一直处于等待状态而不能继续不论什么操作。 这样的堵塞情况常常发生, 这时的 CPU资源全然处于闲置状态。
多线程实现后台服务程序能够同一时候处理多个任务,并不发生堵塞现象。多线程是
Java 语言的一个非常重要的特征。 多线程程序设计最大的特点就是可以提高程序执行效率和处理速度。Java 程序可同一时候并行执行多个相对独立的线程。比如创建一个线程来接收数据,还有一个线程发送数据。既使发送线程在接收数据时被堵塞。接受数据线程仍然可以执行。 线程(Thread)是控制线程(Thread of Control)的缩写。它是具有一定顺序的指令序列(即所编写的程序代码)、存放方法中定义局部变量的栈和一些共享数据。线程是相互独立的。每一个方法的局部变量和其它线程的局部变量是分开的,因此,不论什么线程都不能訪问除自身之外的其它线程的局部变量。假设两个线程同一时候訪问同一个方法,那每一个线程将各自得到此方法的一个拷贝。
Java 提供的多线程机制使一个程序可同一时候运行多个任务。线程有时也被称为小进程。它是从一个大进程里分离出来的小的独立的线程。因为实现了多线程技术,Java 显得更健壮。多线程带来的优点是更好的交互性能和实时控制性能。多线程是强大而机灵的编程工具,但要用好它却不是件easy的事。
在多线程编程中,每一个线程都通过代码实现线程的行为,并将数据供给代码操作。编码和数据有时是相当独立的,可分别向线程提供。多个线程能够同一时候处理同一代码和同一数据。不同的线程也能够处理各自不同的编码和数据。
2 .创建线程方法
Java程序都是声明一个公共类,并在类内实现一个 main 方法。其实,这些程序就是一个单线程程序。当它执行完main 方法的程序后。线程正好退出。程序同一时候结束执行。
- public class OnlyThread {
- public static void main(String args[]) {
- run(); // 调用静态run()方法
- }
- /**
- * 实现run()方法
- */
- public static void run() {
- // 循环计算输出的*数目
- for (int count = 1, row = 1; row < 10; row++, count++) {
- for (int i = 0; i < count; i++) { // 循环输出指定的count数目的*
- System.out.print('*');
- }
- System.out.println();
- }
- }
- }
这仅仅是建立了一个单一线程并运行的普通小程序,并没有涉及到多线程的概念。
在 Java程序中,有两种方法创建线程:
一是对 Thread 类进行派生并覆盖 run方法;
二是通过实现 runnable接口创建。
3. Thread 创建线程
在程序中创建新的线程的方法之中的一个是继承 Thread 类。 并通过 Thread子类声明线程对象。
继承Thread 类并覆盖 Thread类的 run 方法完毕线程类的声明, 通过new创建派生线程类的线程对象。
run 中的代码实现了线程的行为。
java.lang.Thread 类是一个通用的线程类,因为默认情况下 run 方法是空的,直接通过 Thread类实例化的线程对象不能完毕不论什么事。所以能够通过派生 Thread 类,并用详细程序代码覆盖Thread 类中的 run 方法,实现具有各种不同功能的线程类。
1) Thread 创建线程步骤:
(1)创建一个新的线程类,继承 Thread 类并覆盖 Thread 类的 run()方法。
- class ThreadType extends Thread{
- public void run(){
- ……
- }
- }
(2)创建一个线程类的对象,创建方法与一般对象的创建同样,使用keywordnew完毕。
- ThreadType tt = new ThreadType();
(3)启动新线程对象,调用 start()方法。
- tt.start();
(4)线程自己调用 run()方法。
- void run();
2) Thread创建一个线程
以下是通过Thread创建线程的样例:产生一个新的线程
- class ThreadDemo1 extends Thread {
- ThreadDemo1() {
- }
- // 声明ThreadDemo1带參数的构造方法
- ThreadDemo1(String szName) {
- super(szName);
- }
- // 重载run函数
- public void run() {
- for (int count = 1, row = 1; row < 10; row++, count++) {
- for (int i = 0; i < count; i++) {// 循环输出指定的count数目的*
- System.out.print('*');
- }
- System.out.println();
- }
- }
- public static void main(String argv[]) {
- ThreadDemo1 td = new ThreadDemo1(); // 创建,并初始化ThreadDemo1类型对象td
- td.start(); // 调用start()方法运行一个新的线程
- }
- }
OnlyThread.java程序与程序ThreadDemo1.java表面上看执行结果同样,可是细致对比会发现。程序OnlyThread.java中对
run方法的调用在程序ThreadDemo1.java中变成了对 start 方法的调用,而且程序ThreadDemo1.java确派生
Thread类。创建新的线程类。
3) Thread创建多个线程
//文件:程序10.3 ThreadDemo2.java 描写叙述:产生三个新的线程
- public class ThreadDemo2 extends Thread {
- // 声明无參数。空构造方法
- ThreadDemo2() {
- }
- // 声明带有字符串參数的构造方法
- ThreadDemo2(String szName) {
- super(szName); // 调用父类的构造方法
- }
- // 重载run函数
- public void run() {
- for (int count = 1, row = 1; row < 10; row++, count++) {
- for (int i = 0; i < count; i++) {// 循环输出指定的count数目的*
- System.out.print('*');
- }
- System.out.println();
- }
- }
- public static void main(String argv[]) {
- ThreadDemo2 td1 = new ThreadDemo2(); // 创建。并初始化ThreadDemo2类型对象td1
- ThreadDemo2 td2 = new ThreadDemo2(); // 创建。并初始化ThreadDemo2类型对象td2
- ThreadDemo2 td3 = new ThreadDemo2(); // 创建,并初始化ThreadDemo2类型对象td3
- td1.start(); // 启动线程td1
- td2.start(); // 启动线程td2
- td3.start(); // 启动线程td3
- }
- }
创建了 3 个线程 td1、td2、td3。它们分别运行自己的 run方法。在实际中运行的结果并非想要的直角三角形。 而是一些乱七八糟的 “*” 行。长短并没有一定的规律,这是由于线程并没有依照程序中调用的顺序来运行,
而是产生了多个线程赛跑现象。 执行结果:
注意:Java线程并不能按调用顺序运行,而是并行运行的单独代码。假设要想得到完整的直角三角形。须要在运行一个线程之前,推断程序前面的线程是否终止,假设已经终止,再来调用该线程。
4. Runnable 接口创建线程
通过实现 Runnable 接口的方法是创建线程类的另外一种方法。利用实现 Runnable 接口来创建线程的方法能够解决 Java 语言不支持的多重继承问题。 Runnable 接口提供了 run()方法的原型,因此创建新的线程类时。仅仅要实现此接口,即仅仅要特定的程序代码实现Runnable接口中的
run()方法,就可完毕新线程类的执行。
扩展Thread类创建线程的方式,适合编写简单的应用程序代码。而实现Runnable接口创建线程。可以避免Java单继承的局限,适合同一代码的多线程处理同一资源的情况。代码具有良好的一致性,是更符合面向对象思想的设计方式。
1) Runnable 创建线程步骤
(1)创建一个实现 Runnable 接口的类,而且在这个类中重写 run 方法。
- class ThreadType implements Runnable{
- public void run(){
- ……
- }
- }
(2)使用keyword new新建一个 ThreadType 的实例。
- Runnable rb = new ThreadType ();
(3)通过 Runnable 的实例创建一个线程对象。在创建线程对象时。调用的构造函数是new Thread(ThreadType),它用 ThreadType 中实现的 run()方法作为新线程对象的 run()方法。
- Thread td = new Thread(rb);
(4)通过调用 ThreadType 对象的 start()方法启动线程执行。
- td.start();
2) Runnable 创建线程
- class ThreadDemo3 implements Runnable {
- // 重载run函数
- public void run() {
- for (int count = 1, row = 1; row < 10; row++, count++){ // 循环计算输出的*数目
- for (int i = 0; i < count; i++){ // 循环输出指定的count数目的*
- System.out.print('*');
- }
- System.out.println();
- }
- }
- public static void main(String argv[]) {
- Runnable rb = new ThreadDemo3(); // 创建。并初始化ThreadDemo3对象rb
- Thread td = new Thread(rb); // 通过Thread创建线程
- td.start(); // 启动线程td
- }
- }
2. 线程的状态
线程的整个周期由线程创建、可执行状态、不可执行状态和退出等部分组成。这些状态之间的转化是通过线程提供的一些方法完毕的。
1.线程周期
一个线程有4 种状态,不论什么一个线程都处于这4种状态中的一种状态:
1) 创建(new)状态:调用 new方法产生一个线程对象后、调用 start 方法前所处的状态。线程对象尽管已经创建,但还没有调用 start 方法启动。因此无法运行。当线程处于创建状态时,线程对象能够调用 start 方法进入启动状态。也能够调用 stop 方法进入停止状态。
2)可执行(runnable)状态:当线程对象执行 start()方法后,线程就转到可执行状态。
进入此状态仅仅是说明线程对象具有了能够执行的条件,但线程并不一定处于执行状态。
由于在单处理器系统中执行多线程程序时,一个时间点仅仅有一个线程执行,系统通过调度机制实现宏观意义上的执行线程共享处理器。 因此一个线程是否在执行。除了线程必须处于 Runnable 状态之外,还取决于优先级和调度。
3)不可执行(non Runnable)状态:线程处于不可执行状态是因为线程被挂起或者发生堵塞。比如对一个线程调用 wait()函数后,它就可能进入堵塞状态。调用线程的notify或notifyAll 方法后它才干再次回到可执行状态。
4)退出(done)状态:一个线程能够从不论什么一个状态中调用 stop 方法进入退出状态。
线程一旦进入退出状态就不存在了。不能再返回到其它的状态。
除此之外。假设线程运行完 run方法,也会自己主动进入退出状态。
创建状态、可执行状态、不可执行状态、退出状态之间的转换关系如图 所看到的。
通过 new第一次创建线程时,线程位于创建状态,这时不能执行线程。仅仅能等待进一步的方法调用改变其状态。
然后,线程通过调用 start方法启动
线程,并进入可运行状态,或者调用方法 stop进入退出状态。
当程序位于退出状态时,线程已经结束运行,这是线程的最后一个状态,而且不能转化到其它状态。
当程序的全部线程位于退出状态时,程序会强行终止。当线程位于可运行状态时。在一个特定的时间点上。每个系统处理器仅仅能运行一个线程。
此时假设线程被挂起。运行就会被中断或者进入休眠状态。那么线程将进入不可运行状态,而且不可运行状态能够通过 resume、notify等方法返回到可运行状态。
表10-1列举了线程状态转换的函数。
线程状态转换函数:
方法 描写叙述 有效状态 目的状态
start() 開始运行一个线程 New Runnable
stop() 结束运行一个线程 New或Runnable Done
sleep(long) 暂停一段时间。这个时间为给定的毫秒 Runnable NonRunnable
sleep(long,int) 暂停片刻,能够精确到纳秒 Runnable NonRunnable
suspend() 挂起运行 Runnable NonRunnable
resume() 恢复运行 NonRunnable Runnable
yield() 明白放弃运行 Runnable Runnable
wait() 进入堵塞状态 Runnable NonRunnable
notify() 堵塞状态解除 NonRunnable Runnable
注意:stop()、suspend()和 resume()方法如今已经不提倡使用,这些方法在虚拟机中可能引起“死锁”现象。
suspend()和 resume()方法的替代方法是 wait()和 sleep()。线程的退出通常採用自然终止的方法,建议不要人工调用 stop()方法。
2 线程的创建和启动
Java 是面向对象的程序设计语言。设计的重点就是类的设计与实现。
Java 利用线程类Thread 来创建线程,线程的创建与普通类对象的创建操作同样。
Java通过线程类的构造方法创建一个线程。并通过调用 start 方法启动该线程。
实际上。启动线程的目的就是为了运行它的 run()方法,而 Thread 类中默认的 run()方法没有不论什么可操作代码,所以用 Thread类创建的线程不能完毕不论什么任务。为了让创建的线程完毕特定的任务,必须又一次定义 run()方法。在第一节中已经讲述过,Java 通常有两种又一次定义run()方法的方式:
1)派生线程类 Thread 的子类,并在子类中重写 run()方法:Thread 子类的实例对象是一个线程对象,而且该线程有专门定制的线程 run()方法,启动线程后就运行子类中重写的 run()方法。
‰ 2)实现 Runnable 接口并又一次定义 run()方法:先定义一个实现 Runnable()接口的类,在该类中定义 run()方法,然后创建新的线程类对象,并以该对象作为 Thread 类构造方法的參数创建一个线程。
注意:调用线程的 run()方法是通过启动线程的start()方法来实现的。 由于线程在调用start()方法之后,系统会自己主动调用 run()方法。与一般方法调用不同的地方在于一般方法调用另外一个方法后,必须等被调用的方法运行完成才干返回,而线程的 start()方法被调用之后,系统会得知线程准备完成而且能够运行run()方法,start()方法就返回了,start()方法不会等待run()方法运行完成。
3. 线程状态转换
1.线程进入可运行状态
当下面几种情况发生时,线程进入可运行状态。
(1)其它线程调用notify()或者 notifyAll()方法,唤起处于不可运行状态的线程。
public final void notify()
public final void notifyAll()
notify 只唤醒一个线程并同意它获得锁,notifyAll 唤醒全部等待这个对象的线程。并同意它们获得锁。
(2)线程调用 sleep(millis)方法。millis毫秒之后线程会进入可运行状态。
static void sleep(long millis) throws InterruptedException 在 millis 毫秒数内让当前正在运行的线程进入休眠状态,等到时间过后,该线程会自己主动苏醒并继续运行。
sleep方法的准确度受到系统计数器的影响。
static void sleep(long millis, int nanos) throws InterruptedException 在毫秒数(millis)加纳秒数(nanos)内让当前正在运行的线程进入休眠状态,此操作的准确度也受到系统计数器的影响。
(3)线程对I/O操作的完毕。
2.线程进入不可运行状态
当下面几种情况发生时,线程进入不可运行状态。
(1)线程自己主动调用 wait()方法,等待某种条件的发生。
public final void wait() throws InterruptedException
当其它线程调用 notify()方法或 notifyAl()方法后。处于等待状态的线程获得锁之后才会被唤醒。然后该线程一直等待又一次获得对象锁才继续执行。
(2)线程调用 sleep()方法进入不可运行状态。在一定时间后会进入可运行状态。
(3)线程等待 I/O操作的完毕。
线程堵塞的样例。
- public class ThreadSleep
- {
- public static void main(String[ ] args)
- {
- SubThread st = new SubThread("SubThread"); //创建,并初始化SubThread 对象st
- st.start(); //启动线程st
- }
- }
- class SubThread extends Thread{
- SubThread(){} //声明。实现SubThread无參数构造方法
- //声明,实现SubThread带字符串參数构造方法
- SubThread(String Name)
- {
- super(Name); //调用父类的构造方法
- }
- //重载run函数
- public void run()
- {
- for (int count = 1,row = 1; row < 10; row++,count++) //循环计算输出的*数目
- {
- for (int i = 0; i < count; i++) //循环输出指定的count数目的*
- {
- System.out.print('*'); //输出*
- }
- try //try-catch块。用于捕获异常
- { Thread.sleep(1000); //线程休眠1秒钟
- System.out.print("\t wait........");
- }
- catch (InterruptedException e) //捕获异常InterruptedException
- {
- e.printStackTrace(); //异常抛出信息
- }
- System.out.println(); //输出换行符
- }
- }
- }
程序ThreadSleep 中,每输出一行*就要歇息1 秒钟。当运行 sleep()语句后, 线程进入不可运行状态等待1 秒钟之后,线程 st 会自己主动苏醒并继续运行。因为 sleep
方法抛出 InterruptedException异常, 所以在调用时必须捕获异常。
4 .等待线程结束
isAlive()方法用来推断一个线程是否存活。
当线程处于可执行状态或不可执行状态时,isAlive()方法返回 true。 当线程处于创建状态或退出状态时。 则返回 false。 也就是说, isAlive()方法假设返回 true。并不能推断线程是处于可执行状态还是不可执行状态。isAlive()方法的原型例如以下所看到的。
public final boolean isAlive()
该方法用于測试线程是否处于活动状态。
活动状态是指线程已经启动(调用 start方法)且尚未退出所处的状态,包含可执行状态和不可执行状态。
能够通过该方法解决程序 10.3中的问题,先推断第一个线程是否已经终止,假设终止再来调用第二个线程。这里提供两种方法:
第一种方法是不断查询第一个线程是否已经终止,假设没有,则让主线程睡眠一直到它终止即“while/isAlive/sleep”。格式例如以下。
- 线程1.start();
- while(线程 1.isAlive()) {
- Thread.sleep(休眠时间);
- }
- 线程2.start();
另外一种是利用 join()方法。
1)public final void join(long millis) throws InterruptedException 等待该线程终止的时间最长为毫秒(millis),超时为0 意味着要一直等下去。
2) public final void join(long millis,int nanos) throws InterruptedException 等待该线程终止的时间最长为毫秒(millis)加纳秒(nanos)。
3)public final void join() throws InterruptedException 等待该线程终止。
等待线程结束并运行另外一个线程的样例。
该样例 等待一个线程的结束的两种方法 :
- package Test;
- class WaitThreadStop extends Thread {
- // 声明。并实现WaitThreadStop无參数构造方法
- WaitThreadStop() {
- }
- // 声明,并实现带有一个字符串參数的构造方法
- WaitThreadStop(String szName) {
- super(szName); // 调用父类的构造方法
- }
- // 重载run函数
- public void run() {
- for (int count = 1, row = 1; row < 10; row++, count++) {
- for (int i = 0; i < count; i++) {
- System.out.print('*'); // 输出*
- }
- System.out.println(); // 输出换行符
- }
- }
- }
- public class WaitThreadStopMain {
- public static void main(String argv[ ]){
- WaitThreadStopMain test = new WaitThreadStopMain(); //创建,初始化WaitThreadStopMain对象test
- test.Method1(); //调用Method1方法
- //test.Method2();
- }
- // 第一种方法:while/isAlive/sleep
- public void Method1() {
- WaitThreadStop th1 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th1
- WaitThreadStop th2 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th2
- // 运行第一个线程
- th1.start();
- // 查询第一个线程的状态
- while (th1.isAlive()) {
- try {
- Thread.sleep(100); // 休眠100毫秒
- } catch (InterruptedException e) {
- e.printStackTrace(); // 异常信息输出
- }
- }
- // 当第一个线程终止后,执行第二个线程
- th2.start(); // 启动线程th2
- }
- // 另外一种方法,使用join方法实现等待其它线程结束
- public void Method2() {
- WaitThreadStop th1 = new WaitThreadStop(); // 创建。 并初始化WaitThreadStop对象th1
- WaitThreadStop th2 = new WaitThreadStop(); // 创建,并初始化WaitThreadStop对象th2
- // 运行第一个线程
- th1.start();
- try {
- th1.join(); // th1调用join 方法
- } catch (InterruptedException e) {
- e.printStackTrace(); // 异常信息输出
- }
- // 运行第二个线程
- th2.start();
- }
- }
3. 线程调度
多线程应用程序的每个线程的重要性和优先级可能不同,比如有多个线程都在等待获得CPU的时间片, 那么优先级高的线程就能抢占CPU并得以运行。 当多个线程交替抢占CPU时,优先级高的线程占用的时间应该多。因此。高优先级的线程运行的效率会高些。运行速度也会快些。
在 Java 中。CPU的使用一般是抢占式调度模式不须要时间片分配进程。
抢占式调度模式是指很多线程同一时候处于可执行状态,但仅仅有一个线程正在执行。
当线程一直执行直到结束。或者进入不可执行状态,或者具有更高优先级的线程变为可执行状态,它将会让出 CPU。线程与优先级相关的方法例如以下:
public final void setPriority(int newPriority) 设置线程的优先级为 newPriority :
newPriority 的值必须在 MIN_PRIORITY 到MAX_PRIORITY范围内,通常它们的值各自是1和10。眼下Windows系统仅仅支持3个级别的优
先级, 它们各自是Thread.MAX_PRIORITY、 Thread.MIN_PRIORITY和Thread.NORM_PRIORITY。
public final int getPriority() 获得当前线程的优先级。
线程优先级的样例:
- class InheritThread extends Thread {
- //自己定义线程的run()方法
- public void run(){
- System.out.println("InheritThread is running…"); //输出字符串信息
- for(int i=0;i<10;i++){
- System.out.println(" InheritThread: i="+i); //输出信息
- try{
- Thread.sleep((int)Math.random()*1000); //线程休眠
- }
- catch(InterruptedException e) //捕获异常
- {}
- }
- }
- }
通过Runnable接口创建的另外一个线程 :
- class RunnableThread implements Runnable {
- // 自己定义线程的run()方法
- public void run() {
- System.out.println("RunnableThread is running…"); // 输出字符串信息
- for (int i = 0; i < 10; i++) {
- System.out.println("RunnableThread : i=" + i); // 输出i
- try {
- Thread.sleep((int) Math.random() * 1000); // 线程休眠
- } catch (InterruptedException e) { // 捕获异常
- }
- }
- }
- }
- public class ThreadPriority {
- public static void main(String args[]) {
- // 用Thread类的子类创建线程
- InheritThread itd = new InheritThread();
- // 用Runnable接口类的对象创建线程
- Thread rtd = new Thread(new RunnableThread());
- itd.setPriority(5); // 设置myThread1的优先级5
- rtd.setPriority(5); // 设置myThread2的优先级5
- itd.start(); // 启动线程itd
- rtd.start(); // 启动线程rtd
- }
- }
在程序ThreadPriority.java中,线程 rtd 和 itd 具有同样的优先级,所以它们交互占用 CPU,宏观上处于并行执行状态。
结果如图3.
又一次设定优先级:
itd.setPriority(1); //设置myThread1的优先级1
rtd.setPriority(10); //设置myThread2的优先级10
执行程序结果如图4所看到的。
图3同样优先级 图2 不同的优先级
从执行结构能够看出程序ThreadPriority.java改动后。因为设置了线程itd和 rtd 的优先级,而且 rtd的优先级较高,基本上是 rtd都优先抢占 CPU资源。
4. 线程同步
Java 应用程序中的多线程能够共享资源,比如文件、数据库、内存等。
当线程以并发模式訪问共享数据时,共享数据可能会发生冲突。Java引入线程同步的概念,以实现共享数据的一致性。
线程同步机制让多个线程有序的訪问共享资源,而不是同一时候操作共享资源。
1 . 同步概念
在线程异步模式的情况下,同一时刻有一个线程在改动共享数据,还有一个线程在读取共享数据,当改动共享数据的线程没有处理完毕,读取数据的线程肯定会得到错误的结果。
假设採用多线程的同步控制机制。当处理共享数据的线程完毕处理数据之后,读取线程读取数据。
通过分析多线程出售火车票的样例,能够更好得理解线程同步的概念。线程 Thread1 和线程 Thread2 都能够出售火车票,可是这个过程中会出现数据与时间信息不一致的情况。线程 Thread1 查询数据库。发现某张火车票 T 能够出售,所以准备出售此票;此时系统切换到线程Thread2运行。 它在数据库中查询存票。 发现上面的火车票T能够出售, 所以线程Thread2将这张火车票 T 售出;当系统再次切换到线程 Thread1 运行时,它又卖出相同的票 T。
这是一个典型的因为数据不同步而导致的错误。
以下举一个线程异步模式訪问数据的样例。
- //文件:程序ThreadNoSynchronized.java 描写叙述:多线程不同步的原因
- class ShareData {
- public static String szData = ""; // 声明,并初始化字符串数据域。作为共享数据
- }
- class ThreadDemo extends Thread {
- private ShareData oShare; // 声明,并初始化ShareData 数据域
- ThreadDemo() {
- } // 声明,并实现ThreadDemo 构造方法
- // 声明,并实现ThreadDemo 带參数的构造方法
- ThreadDemo(String szName, ShareData oShare) {
- super(szName); // 调用父类的构造方法
- this.oShare = oShare; // 初始化oShare域
- }
- public void run() {
- for (int i = 0; i < 5; i++) {
- if (this.getName().equals("Thread1")) {
- oShare.szData = "这是第 1 个线程";
- // 为了演示产生的问题。这里设置一次睡眠
- try {
- Thread.sleep((int) Math.random() * 100); // 休眠
- } catch (InterruptedException e) { // 捕获异常
- }
- System.out.println(this.getName() + ":" + oShare.szData); // 输出字符串信息
- } else if (this.getName().equals("Thread2")) {
- oShare.szData = "这是第 2 个线程";
- // 为了演示产生的问题,这里设置一次睡眠
- try {
- Thread.sleep((int) Math.random() * 100); // 线程休眠
- } catch (InterruptedException e) // 捕获异常
- {
- }
- System.out.println(this.getName() + ":" + oShare.szData); // 输出字符串信息
- }
- }
- }
- }
- public class ThreadNoSynchronized {
- public static void main(String argv[]) {
- ShareData oShare = new ShareData(); // 创建。初始化ShareData对象oShare
- ThreadDemo th1 = new ThreadDemo("Thread1", oShare); // 创建线程th1
- ThreadDemo th2 = new ThreadDemo("Thread2", oShare); // 创建线程th2
- th1.start(); // 启动线程th1
- th2.start(); // 启动线程th2
- }
- }
执行结果例如以下:
Thread1:这是第 2 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread1:这是第 1 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
Thread2:这是第 2 个线程
程序中预想的结果是:“Thead1:这是第1 个线程”或“Thead2:这是第2 个线程”,可是线程对数据的异步操作导致执行结果出现了差错。 上面程序是因为线程不同步而导致错误。 为了解决此类问题,Java 提供了“锁”机制实现线程的同步。
锁机制的原理是每一个线程进入共享代码之前获得锁。否则不能进入共享代码区。而且在退出共享代码之前释放该锁。这样就攻克了多个线程竞争共享代码的情况,达到线程同步的目的。Java中锁机制的实现方法是共享代码之前增加 synchronized keyword。
在一个类中,用keyword synchonized 声明的方法为同步方法。Java 有一个专门负责管理线程对象中同步方法訪问的工具——同步模型监视器。它的原理是为每一个具有同步代码的对象准备惟一的一把“锁”。当多个线程訪问对象时,仅仅有取得锁的线程才干进入同步方法,其它訪问共享对象的线程停留在对象中等待,假设获得锁的线程调用wait方法放弃锁。那么其它等待获得锁的线程将有机会获得锁。
当某一个等待线程取得锁,它将运行同步方法。而其它没有取得锁的线程仍然继续等待获得锁。
Java 程序中线程之间通过消息实现相互通信,wait()、notify()及 notifyAll()方法可完毕线程间的消息传递。比如,一个对象包括一个 synchonized 同步方法,同一时刻仅仅能有一个获得锁的线程訪问该对象中的同步方法, 其它线程被堵塞在对象中等待获得锁。 当线程调用 wait()方法可使该线程进入堵塞状态,其它线程调用notify()或
notifyAll()方法能够唤醒该线程。
2 .同步格式
当把一语句块声明为 synchornized,在同一时间,它的訪问线程之中的一个才干运行该语句块。
1) 方法同步:用keyword synchonized 可将方法声明为同步。格式例如以下。
- class 类名{
- public synchonized 类型名称 方法名称(){
- ......
- }
- }
2)语句块同步: 对于同步块,synchornized
获取的是參数中的对象锁。
- synchornized(obj)
- {
- //………………….
- }
当线程运行到这里的同步块时,它必须获取 obj 这个对象的锁才干运行同步块。否则线程仅仅能等待获得锁。必须注意的是obj对象的作用范围不同,控制情况不尽同样。
示比例如以下。
- public void method()
- {
- Object obj= new Object(); //创建局部Object类型对象obj
- synchornized(obj) //同步块
- {
- //……………..
- }
- }
上面的代码创建了一个局部对象obj。
因为每个线程运行到 Object obj = new Object()时都会产生一个 obj 对象。每个线程都能够获得创建的新的 obj对象的锁,不会相互影响。因此这段程序不会起到同步作用。
3)同步类的属性:假设同步的是类的属性,情况就不同了。
同步类的成员变量的一般格式例如以下。
- class method
- {
- Object o = new Object(); //创建Object类型的成员变量o
- public void test()
- {
- synchornized(o) //同步块
- {
- //………………………
- }
- }
- }
当两个并发线程訪问同一个对象的 synchornized(o)同步代码块时。一段时间内仅仅能有一个线程运行。另外的线程必须等到当前线程运行完同步代码块释放锁之后,获得锁的线程将运行同步代码块。
有时能够通过以下的格式声明同步块。
- public void method()
- {
- synchornized(this) //同步块
- {
- //………………………
- }
- }
当有一个线程訪问某个对象的 synchornized(this)同步代码块时。另外一个线程必须等待该线程运行完此代码块,其它线程能够訪问该对象中的非 synchornized(this)同步代码。假设类中包括多个 synchornized(this)同步代码块。假设同步线程有一个訪问当中一个代码块,则其它线程不能訪问该对象的全部
synchornized(this)同步代码块。
对于以下形式的同步块而言。调用 ClassName 对象实例的并行线程中仅仅有一个线程可以訪问该对象。
- synchornized(ClassName.class)
- {
- //…………………….
- }
5. 线程通信
1. 生产者与消费者
生产者与消费者是个非常好的线程通信的样例,生产者在一个循环中不断生产共享数据,而消费者则不断消费生产者生产的共享数据。
程序必须保证有共享数据,假设没有,消费者必须等待生产新的共享数据。两者之间的数据关系例如以下:
1) 生产者生产前,假设共享数据没有被消费。则生产等待。生产者生产后。通知消费者消费。
2)消费者消费前,假设共享数据已经被消费完,则消费者等待;消费者消费后。通知生产者生产。
为了解决生产者和消费者的矛盾。引入了等待/通知(wait/notify)机制。
- class Producer extends Thread {
- Queue q;
- Producer(Queue q) {
- this.q = q;
- }
- public void run() {
- for (int i = 1; i < 5; i++) {
- q.put(i);
- }
- }
- }
- class Consumer extends Thread {
- Queue q; // 声明队列q
- Consumer(Queue q){
- this.q = q; // 队列q初始化
- }
- public void run() {
- while (true) {// 循环消费元素
- q.get(); // 获取队列中的元素
- }
- }
- }
Producer 是一个生产者类。该生产者类提供一个以共享队列作为參数的构造方法。它的run 方法循环产生新的元素,并将元素加入于共享队列;Consumer 是一个消费者类。该消费者类提供一个以共享队列作为參数的构造方法,它的 run 方法循环消费元素。并将元素从共享队列删除。
2.共享队列
共享队列类是用于保存生产者生产、消费者消费的共享数据。共享队列有两个域:value(元素的数目)、isEmpty(队列的状态)。共享队列提供了put和 get 两个方法。
- class Queue {
- int value = 0; // 声明。并初始化整数类型数据域value
- boolean isEmpty = true; // 声明。并初始化布尔类型数据域isEmpty。用于推断队列的状态
- // 生产者生产方法
- public synchronized void put(int v) {
- // 假设共享数据没有被消费,则生产者等待
- if (!isEmpty) {
- try {
- System.out.println("生产者等待");
- wait(); // 进入等待状态
- } catch (Exception e) // 捕获异常
- {
- e.printStackTrace(); // 异常信息输出
- }
- }
- value += v; // value值加v
- isEmpty = false; // isEmpty赋值为false
- System.out.println("生产者共生产数量:" + v);
- notify();
- }
- public synchronized int get() {
- if (isEmpty) {
- try {
- System.out.println("消费者等待");
- wait();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- value--;
- if (value < 1) {
- isEmpty = true;
- }
- System.out.println("消费者消费一个,剩余:" + value);
- notify();
- return value;
- }
- }
生产者调用put方法生产共享数据,假设共享数据不为空,生产者线程进入等待状态;否则将生成新的数据,然后调用notify方法唤醒消费者线程进行消费;
消费者调用get方法消费共享数据,假设共享数据为空,消费者进入等待状态,否则将消费共享数据。然后提调用notify方法唤醒生产者线程进行生产。
3. 执行生产者与消费者
以下是生产者与消费者程序的主程序。
- public class ThreadCommunication {
- public static void main(String[] args) {
- Queue q = new Queue();
- Producer p = new Producer(q);
- Consumer c = new Consumer(q);
- c.start();
- p.start();
- }
- }
注意:考虑到程序的安全性。多数情况下使用 notifiAll(),除非明白能够知道唤醒哪一个线程。wait方法调用的前提条件是当前线程获取了这个对象的锁。也就是说 wait方法必须放在同步块或同步方法中。
6. 线程死锁
为了保证数据安全使用 synchronized同步机制, 当线程进入阻塞状态 (不可执行状态和等待状态)时。其它线程无法訪问那个加锁对象(除非同步锁被解除),所以
一个线程会一直处于等待还有一个对象的状态, 而还有一个对象又会处于等待下一个对象的状态。以此类推,这个线程“等待”状态链会发生非常糟糕的情形。即封闭环状态(也就是说最后那个对象在等待第一个对象的锁)。此时,全部的线程都陷入毫无止境的等待状态中,无法继续执行,这样的情况就称为“死锁”。
尽管这样的情况发生的概率非常小。一旦出现,程序的调试变得困难并且查错也是一件非常麻烦的事情。
以下举一个死锁的样例。
- public class ThreadLocked implements Runnable {
- public static boolean flag = true; // 起一个标志作用
- private static Object A = new Object(); // 声明,并初始化静态Object数据域A
- private static Object B = new Object(); // 声明,并初始化静态Object数据域B
- public static void main(String[] args) throws InterruptedException {
- Runnable r1 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r1
- Thread t1 = new Thread(r1); // 创建线程t1
- Runnable r2 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r2
- Thread t2 = new Thread(r2); // 创建线程t2
- t1.start(); // 启动线程t1
- t2.start(); // 启动线程t2
- }
- public void AccessA() {
- flag = false; // 初始化域flag
- // 同步代码快
- synchronized (A) { // 声明同步块,给对象A加锁
- System.out.println("线程t1 : 我得到了A的锁"); // 输出字符串信息
- try {
- // 让当前线程睡眠,从而让另外一个线程能够先得到对象B的锁
- Thread.sleep(1000); // 休眠
- } catch (InterruptedException e) { // 捕获异常
- e.printStackTrace(); // 异常信息输出
- }
- System.out.println("线程t1 : 我还想要得到B的锁");
- // 在得到A锁之后,又想得到B的锁
- // 同步块内部嵌套同步块
- synchronized (B) { // 声明内部嵌套同步块。指定对象B的锁
- System.out.println("线程t1 : 我得到了B的锁"); // 输出字符串信息
- }
- }
- }
- public void AccessB() {
- flag = true; // 改动flag的值
- // 同步代码块
- synchronized (B) { // 指定同步块,给B加锁
- System.out.println("线程t2 : 我得到了B的锁"); // 输出字符串信息
- try {
- // 让当前线程睡眠,从而让另外一个线程能够先得到对象A的锁
- Thread.sleep(1000); // 休眠
- } catch (InterruptedException e) { // 捕获异常InterruptedException
- e.printStackTrace(); // 异常信息输出
- }
- System.out.println("线程t2 : 我还想要得到A的锁"); // 字符串信息输出
- // 在得到B锁之后,又想得到A的锁
- // 同步块内部嵌套内部快
- synchronized (A) { // 指定同步块,给A加锁
- System.out.println("线程t2 : 我得到了A的锁"); // 输出字符串信息
- }
- }
- }
- public void run() {
- if (flag){ // 当flag为true,运行以下语句
- AccessA(); // 调用AccessA方法
- } else {
- AccessB(); // 调用AccessB方法
- }
- }
- }
程序 ThreadLocked.java中创建了两个线程 t1 和 t2,而且声明两个方法:AccessA和 AccessB。
在执行过程中,线程t1 先获得了 A 的锁。然后又要求获得 B 的锁;而 t2
先获得B 的锁,然后又要求获得 A的锁。此时便进入了无休止的相互等待状态,即死锁。
Java 语言本身并没有提供防止死锁的详细方法,可是在详细程序设计时必需要慎重,以防止出现死锁现象。
通常在程序设计中应注意。不要使用 stop()、suspend()、resume()以及 destroy()方法。 stop()方法不安全,它会解除由该线程获得的全部对象锁,并且可能使对象处于不连贯状态,假设其它线程此时訪问对象,而导致的错误非常难检查出来。
suspend()/resume ()方法也极不安全,调用 suspend()方法时,线程会停下来,可是该线程并没有放弃对象的锁。导致其它线程并不能获得对象锁。调用destroy()会强制终止线程,可是该线程也不会释放对象锁。
版权声明:本文博客原创文章,博客,未经同意,不得转载。
Java线的更多相关文章
- 【转】java线上程序排错经验2 - 线程堆栈分析
前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...
- Java线程之 InterruptedException 异常
Java线程之 InterruptedException 异常 当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法. 抛 ...
- Java线上问题排查思路及Linux常用问题分析命令学习
前言 之前线上有过一两次OOM的问题,但是每次定位问题都有点手足无措的感觉,刚好利用星期天,以测试环境为模版来学习一下Linux常用的几个排查问题的命令. 也可以帮助自己在以后的工作中快速的排查线上问 ...
- Arthas - Java 线上问题定位处理的终极利器
前言 在使用 Arthas 之前,当遇到 Java 线上问题时,如 CPU 飙升.负载突高.内存溢出等问题,你需要查命令,查网络,然后 jps.jstack.jmap.jhat.jstat.hprof ...
- Java线上问题排查神器Arthas快速上手与原理浅谈
前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...
- Java线上应用故障排查之二:高内存占用
搞Java开发的,经常会碰到下面两种异常: 1.java.lang.OutOfMemoryError: PermGen space 2.java.lang.OutOfMemoryError: Java ...
- 排查Java线上服务故障的方法和实例分析
前言 作为在线系统负责人或者是一个技术专家,你可能刚刚接手一个项目就需要处理紧急故障,或者被要求帮忙处理一些紧急的故障,这个时候的情景是: (1)你可能对这个业务仅仅是听说过,而不怎么真正了解: (2 ...
- BTrace : Java 线上问题排查神器
BTrace 是什么 BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务.写好脚本,直接用命 ...
- java线上服务问题排查
1.业务日志相关 假设系统出现异常或者业务有异常,首先想到的都是查看业务日志 查看日志工具: less 或者more grep tail -f filename 查看实时的最新内容 ps:切忌vim直 ...
- java线上应用问题排查方法和工具
linux性能监测点 CPU, Memory, IO, Network Linux性能监测工具-cpu 基本概念: 上下文切换(Context Switches): 如果可运行的线程数大于CPU的数量 ...
随机推荐
- 灰度图像阈值化分割常见方法总结及VC实现
转载地址:http://blog.csdn.net/likezhaobin/article/details/6915755 在图像处理领域,二值图像运算量小,并且能够体现图像的关键特征,因此被广泛使用 ...
- java 类 及其 执行过程
java 类 命名 java类文件中 只能有一个公开类 且 公开类类名与当前类文件的文件名一致 方法如果使用了static修饰,那么此方法是类方法,可以 类名.方法名 使用. 你的main方法在同 ...
- 使用配置文件(.settings、.config)存储应用程序配置
原文:使用配置文件(.settings..config)存储应用程序配置 引言 我不知大家早先是如何保存应用程序配置,以备下次打开时使用的,反正我开始学.Net的时候就去研究序列化,以二进制或XML格 ...
- Hibernate操作Clob数据类型
在POJO字符串可以声明为一个大型对象java.lang.String要么java.sql.Clob种类. 当程序从数据库加载Clob数据的类型.负荷只有一个Clob数据的逻辑指针类型.我们需要通过使 ...
- [Windows]_[0基础]_[使用命令行工具dumpbin分析文件]
dumpbin(vs拥有) 1. 出口lib函数符号文件(symbols) dumpbin /exports zlib1.lib Microsoft (R) COFF/PE Dumper Versio ...
- Android他们控制的定义(一)
培养自己的控制步骤定义: 1.要理解View作品 2. 分享到继承View子类 3. 要定义自己的View类添加属性 4. 绘制控件 5. 响应用户消息 6 .自己定义回调函数 一.View ...
- crm采用soap删除记录
//抽样 function demo() { //操作记录id var targetId = "A8A46444-BA10-E411-8A04-00155D002F02&qu ...
- [java面试题]最长的回文字符串中出现确定
<span style="font-family: Arial, Helvetica, sans-serif;">package com.wzw.util;</s ...
- Codeforces Round #248 (Div. 1)——Nanami's Digital Board
题目连接 题意: 给n*m的0/1矩阵,q次操作,每次有两种:1)将x,y位置值翻转 2)计算以(x,y)为边界的矩形的面积最大值 (1 ≤ n, m, q ≤ 1000) 分析: 考虑以(x,y)为 ...
- ViewPager空指针错误,android.support.v4.view.ViewPager.onSaveInstanceState
support.v4 包为我们提供了一个非常实用的滑动控件ViewPager,在使用ViewPager时有一个需要注意的地方: 即: android.support.v4.view.ViewPager ...