您的位置:首页 > 编程语言 > Java开发

Java 7之多线程第1篇 - 基础API介绍

2013-11-23 21:11 453 查看
一个程序只有一个进程,而一个进程可以包含多个线程,所以进程只能是并发,而线程可以并行。 进程是操作系统中资源分配的基本单位,同一进程的线程间可以共享所属进程的资源,在运行期间,线程才是操作系统的调度和分派的基本单位。同时,操作系统在创建、撤销及切换线程的时候,开销会比进程小。线程在状态转换过程中,可以调用Java API提供的某些方法来改变线程运行的状态。如下图。




下面来介绍一下影响线程运行状态的相关方法。

1、创建及启动Java线程

使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动一个Java线程。Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
(1)第一种方法:直接继承Thread类,重写父类的run()方法

public class MyThread extends Thread {
    //重写父类的run()方法
    public void run() {
        this.setName("run Thread");
        for (int i = 0; i < 7; i++) {
            System.out.println(this + " " + i);
        }
    }
    public static void main(String[] args) {
        //注意,线程开始执行的方法是start()而不是run()方法
        new MyThread().start();    
        for (int i = 0; i < 5; i++) {
            System.out.println("Main Thread : " + i);
        }
    }

}
运行的一种结果如下:

Main Thread : 0
Thread[run Thread,5,main] 0
Main Thread : 1
Thread[run Thread,5,main] 1
Main Thread : 2
Thread[run Thread,5,main] 2
Main Thread : 3
Thread[run Thread,5,main] 3
Thread[run Thread,5,main] 4
Main Thread : 4
Thread[run Thread,5,main] 5
Thread[run Thread,5,main] 6

可以看出:一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
(2)定义线程类并实现Runnable接口。因为Java是单继承了,如果继承了Thread类就不能继承其他的类了,而接口却是可以实现多个的,同时这种方式还能够为多个线程提供共享的数据

class  MThread implements Runnable {
    public void run() {
        //获取当前线程并且设置线程的名字
        Thread.currentThread().setName(("The other Thread"));    
        for (int i = 0; i <10; i++) {
            System.out.println(Thread.currentThread() + " " + i);
        }
    }
}
public class MyRunnable {
    public static void main(String[] args) {
        //注意将创建自定义的线程类对象并传进Thread类的构造方法中    
        new Thread(new MThread()).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main Thread : " + i);
        }        
    }
}

运行的一种结果如下:

Main Thread : 0
Main Thread : 1
Thread[The other Thread,5,main] 0
Main Thread : 2
Thread[The other Thread,5,main] 1
Main Thread : 3
Thread[The other Thread,5,main] 2
Main Thread : 4
Thread[The other Thread,5,main] 3
Main Thread : 5
Main Thread : 6
Thread[The other Thread,5,main] 4
Thread[The other Thread,5,main] 5
Thread[The other Thread,5,main] 6
Thread[The other Thread,5,main] 7
Thread[The other Thread,5,main] 8
Thread[The other Thread,5,main] 9
Main Thread : 7
Main Thread : 8
Main Thread : 9

2、设置线程的优先级getPriority()和setPriority()

 通过该方法可以设置线程的优先级,一般情况下,优先级越高的线程获得CPU调度的时间片将会越长。java线程的优先级值默认为5,设置范围为1-10.  因为Java的线程是被映射到系统的原线程上来实现的,所以线程调度最终还是由操作系统说了算的,虽然很多线程都提供线程优先级的概念,但是并不剪得能与java线程的优先级一一对应,如Solaries中有231种优先级,Windows中就只有7中,并不一定能与java线程的优先级设置一一对应,因此,建议还是直接使用java本身自带的三个MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10)来设置优先级比较好
public class TestThread extends Thread{
    public void run() {
        Thread currentThread = Thread.currentThread();
        //输出当前线程的名字、优先等级
        for (int i = 0; i < 50; i++) {
            System.out.println("name : " + currentThread.getName()
                    + "\t priopity : " + currentThread.getPriority() + " " + i);
        }
    }
    
    public static void main(String[] args) {
        //创建优先级为5一个线程对象
        TestThread ttNorm = new TestThread();
        ttNorm.setPriority(NORM_PRIORITY);
        ttNorm.start();
        
        //创建优先级为1一个线程对象
        TestThread ttMin = new TestThread();
        ttMin.setPriority(MIN_PRIORITY);
        ttMin.start();
        
        //创建优先级为10一个线程对象
        TestThread ttMax = new TestThread();
        ttMax.setPriority(MAX_PRIORITY);
        ttMax.start();        
    }
}
运行多次后发现,优先级高的一般会最先执行完循环,而最低优先级的一般会最后执行完循环。

3、让出CPU,当前线程进入就绪队列等待调度yield()

public class Testyield {
    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3("t1");
        MyThread3 t2 = new MyThread3("t2");
        t1.start();
        t2.start();
    }
}

class MyThread3 extends Thread {
    MyThread3(String s) {
        super(s);
    }
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ": " + i);
            if (i % 10 == 0) {
                yield();
            }
        }
    }

}
t1线程和t2线程输出10个数字后就会进入等待队列把CPU让给了对方,所以当某个线程是10的倍数时,下一个执行的线程一定会是另外一个线程。所以可以从打印的结果看到,t1和t2线程肯定是轮流执行的。
如果注释掉t2线程后,则只有t1线程在执行了。

4、线程睡眠sleep()

指定线程睡眠时间

public static void main(String[] args) {
    	 Thread currentThread = Thread.currentThread();
         try {
             Thread.sleep(5000);    //让线程休眠5秒钟
         } catch (InterruptedException e) {
             e.printStackTrace();
         }   
         System.out.println("hello world");
        
    }
等待一会儿的时间,可以看到打印出hello world。为什么说一会儿呢,可能还有其他线程也在运行,除非你确保自己的电脑上只有一个线程在运行。 sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行,所以在同步块中一般不使用sleep()方法。

5、判断线程生死isAlive()

public class TestThread extends Thread{
    public void run() {
        //输出当前线程的名字
        System.out.println("in run menthod : " + Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        TestThread tt = new TestThread(); //创建一个线程对象

        System.out.println("线程还或者吗? " + tt.isAlive()); //线程还没有开启,下面语句将会输出false
        tt.start();        
        System.out.println("线程还或者吗? " + tt.isAlive());//线程已经开启,下面语句将会输出true

    }

}
对线程的操作,Java是不可能直接做到的,他也是通过JNI本地调用进行操作
其实线程还可以相互影响,下面来介绍一些线程相互作用时用到的方法。

6、线程的暂停与唤醒wait()、notify()和notifyAll()
 
当前线程进入等待池(wait pool),wait方法通过参数可以指定等待的时长,如果没有指定参数,默认一直等待直到使用notify()方法通知该线程或者notifyAll()方法通知才会继续执行下去。需要注意的是,必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。,否则会报以下错误:

Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at com.thread.second.TestWait.main(TestWait.java:23)

wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。

public class test2 {
	public static void main(String[] args) {
		new Thread(new Thread1()).start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Thread2()).start();
	}

	private static class Thread1 implements Runnable {
		public void run() {
			/* 
			 * 由于这里的Thread1和下面的Thread2内部run方法要用同一对象作为监视器,这里不能用this,
			 * 因为在Thread2里面的this和这个Thread1的this不是同一个对象。我们用test.class这
			 * 个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对象。
			 */
			synchronized (test2.class) {
				System.out.println("enter thread1...");
				System.out.println("thread1 is waiting");
				try {
					/*
					 *  释放锁有两种方式:
					 *  1、程序自然离开监视器的范围,也就是离开了synchronized关键字管辖的代码范围
					 *  2、在synchronized关键字管辖的代码内部调用监视器对象的wait方法。这里,使用wait方法释放锁
					 */
					test2.class.wait();   // 会释放锁,使这个线程处于等待状态且以下方法不会执行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("thread1 is going on...");
				System.out.println("thread1 is being over!");
			}
		}//end run
	}

	private static class Thread2 implements Runnable {
		public void run() {
			synchronized (test2.class) {
				System.out.println("enter thread2...");
				System.out.println("thread2 notify other thread can release wait status..");
				// 由于notify方法并不释放锁,
				// 即使thread2调用下面的sleep方法休息了10毫秒,但thread1仍然不会执行,因为thread2没有释放锁,所以Thread1无法得不到锁。
				test2.class.notify(); // 如果没有这个,thread1剩下的不会执行
				System.out.println("thread2 is sleeping ten millisecond...");
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("thread2 is going on...");
				System.out.println("thread2 is being over!");
			}
		}
	}
}


wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
在使用时注意notify()和notifyAll()方法:
notify():唤醒一个处于等待状态的线程,不包括当前执行notify()方法的线程。由于是运行注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

最后运行的结果如下:
enter thread1...
thread1 is waiting
enter thread2...
thread2 notify other thread can release wait status..
thread2 is sleeping ten millisecond...
thread2 is going on...
thread2 is being over!
thread1 is going on...
thread1 is being over!

7、线程合并的join()方法

调用某线程A的这个方法,将该线程与当前线程B合并,即等待该线程A执行完毕后再继续执行线程B,如下图。



当线程B调用线程A的join方法时,线程B必须等待线程A执行完毕,线程B才能继续往下执行。join方法主要用来将大问题分解成小问题,当小问题计算完成时,大问题才能继续往下执行,这时候我们就可以利用join方法了(这种设计思想在开发复杂的程序时一定要掌握)。

public class Testjoin {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			if (i == 5) {
				JoinThread jt = new JoinThread();
				jt.start();
				// 主线程调用jt的join方法,主线程必须等待jt线程执行完才能继续执行
				try {
					jt.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

class JoinThread extends Thread {
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.getName() + "  " + i);
		}
	}

}
运行结果如下:

main  0
main  1
main  2
main  3
main  4
main  5
Thread-0  0
Thread-0  1
Thread-0  2
Thread-0  3
Thread-0  4
Thread-0  5
Thread-0  6
Thread-0  7
Thread-0  8
Thread-0  9
main  6
main  7
main  8
main  9

8、杀死线程stop()

一般不使用stop()方法,是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

参考文章:http://lavasoft.blog.51cto.com/62575/27069
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: