Java多线程(1)

一.实现方法

1.继承Thread类,重写run方法

实例代码:

publicclass MyFirstThread extends Thread {

publicinti;

@Override

publicvoid run() {

while (i++ < 100) {

System.out.println(this.getName() + ":i=" + i);

}

}

publicstaticvoid main(String[] args) {

MyFirstThread thread1 = new MyFirstThread();

MyFirstThread thread2 = new MyFirstThread();

thread1.start();

thread2.start();

}

}

2.实现Runnable接口,传给Thread实现方法。

实现实例

public class ThreadRunnableTest implements Runnable{

@Override

public void run() {

while (true) {

System.out.println("线程执行。。。。。。。");

}

}

public static void main(String[] args) {

new Thread(new ThreadRunnableTest()).start();

System.out.println("主线程执行。。。。。。");

}

}

二、线程的生命周期

新建,就绪,运行,阻塞和死亡状态

·新建:线程被new出来就是新建状态

·就绪:调用start方法,则进入就绪状态,等待CPU的调度

·运行:得到CPU的时间片(可以理解为时间片轮转)则进入运行状态

·阻塞:运行状态中可能会被CPU调度下来(比如被挂起,调用sleep方法,调用一个阻塞方法比如getchar()之类的方法),进入阻塞状态

·死亡:线程执行完毕或者因为异常或者调用stop方法之后线程结束则进入死亡状态。

·ALive状态:是否死亡,是就绪、运行和阻塞三种状态的合集。

·线程进入阻塞状态的情况:

n 线程调用一个阻塞方法,方法返回前该线程一直阻塞。

n 线程调用sleep方法进入阻塞。

n 线程尝试获取同步监视器(常说的线程锁),但该同步监视器被其他线程持有。

线程解除阻塞,重新进入就绪状态的情况:

n 调用的阻塞方法返回。

n 调用的sleep到期。

n 线程成功获取同步监视器(我们常说的线程锁)

线程的死亡状态就是线程的结束,线程结束的情况有如下几种:

n run方法执行完成

n 线程抛出异常

n 直接调用线程的stop方法结束线程(该方法已经过时,并且无代替方法,不建议使用)

判断线程是否死亡可以使用isAlive方法,当线程处于就绪、运行和阻塞三种状态的时候返回true,否则返回false。另外,不能对已经死亡的线程重新调用start方法重新启动。

另外,线程的suspend方法和stop方法非常容易导致死锁,一般不推荐使用。

三.线程控制

join线程

当一个线程需要等待另一个线程完毕再执行的话,可以使用Threadjoin方法。

假设线程A和线程B,在A执行时调用了Bjoin方法(前提是B已经进入Alive状态A将被阻塞,一直等到B线程执行完毕后,A线程继续执行,就好像排队加塞。

示例代码:

publicclass TestJoinThread {

publicstaticvoid main(String[] args) {

MainThread mt = new MainThread();

mt.start();

}

}

class MainThread extends Thread {

publicvoid run() {

// TODO Auto-generated method stub

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

if (i == 30) {

JoinThread jt = new JoinThread();

jt.setName("join线程");

jt.start();

try {

jt.join();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

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

}

}

}

class JoinThread extends Thread {

@Override

publicvoid run() {

// TODO Auto-generated method stub

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

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

}

}

}

运行结果:

Thread-0 27

Thread-0 28

Thread-0 29

join线程 0

join线程 1

join线程 2

join线程 46

join线程 47

join线程 48

join线程 49

Thread-0 30

Thread-0 31

Thread-0 32

Thread-0 33

Thread-0 48

Thread-0 49

可以看到Main线程正常运行,调用了另外一个线程JoinThreadjoin方法,那么Main线程等待JoinThread执行完毕后再执行。

线程睡眠

如果让线程休息一会,我们可以使用Thread的静态方法sleep(long millis)。这个方法的意思是:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

sleep方法会让线程休息指定的毫秒数,在sleep的时候,不会放弃执行权力,等待休息时间到了,马上获取执行机会。也就是说,在这个线程sleep的时候,其他线程不会获得CPU的执行权力。

但如果睡眠过程中会持有锁的话,最好睡眠之前把锁放弃(这是后话了)。

示例代码:

publicclass TestThreadSleep {

publicstaticvoid main(String[] args) {

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

System.out.println(i);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

程序将每隔一秒打印一下变量i的值。

·线程让步和线程优先级使用频率很低,一方面因为其随机性对线程的控制弱,另一方面现在多核CPU的原因,所以不在总结。

线程安全

线程安全的概念不容易定义,在《Java 并发编程实践》中,作者做出了如此定义:多个线程访问一个类对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方法代码不必作其他的协调,这个类的行为仍然是正确的,那么这个类是线程安全的。

也就是说一堆线程去操作一个方法去控制同一个资源,由于是交替执行的,可能会出现一个数据一个线程正在运算还没来得急把数据写进去,结果被另外一个线程把这个数据的脏数据读取出去了。

这样说,可能有些朋友没有看明白,那么我们先来看一个示例,来演示一下什么是现成不安全的情况。

publicclass SafeThreadTest {

V v = new V();

publicstaticvoid main(String[] args) {

SafeThreadTest test = new SafeThreadTest();

test.test();

}

/**

* 开两个线程,分别调用V对象的打印字符串的方法

*/

publicvoid test(){

new Thread(new Runnable() {

@Override

publicvoid run() {

while(true){

v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

}

}

}).start();

new Thread(new Runnable() {

@Override

publicvoid run() {

while(true){

v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

}

}

}).start();

}

/**

* 这个类负责打印字符串

* @author Administrator

*/

class V {

//创建一个锁对象

Lock lock = new ReentrantLock();

/**

* 为了能使方法运行速度减慢,我们一个字符一个字符的打印

* @param s

*/

publicvoid printString(String s){

//加锁,只允许一个线程访问

lock.lock();

try {

for(int i = 0;i

System.out.print(s.charAt(i));

}

System.out.println();

}finally{

//解锁,值得注意的是,这里锁的释放放到了finally代码块中,保证解锁工作一定会执行

lock.unlock();

}

}

}

}

使用这样的方式,与使用synchronized的功能一样,只不过这样使代码看起来更加面向对象一些,怎么加锁,怎么解锁一目了然。

另外,ReentrantLock其实比synchronized增加了一些功能,主要有:

等待可中断

这是指的当前持有锁的线程如果长期不释放锁,正在等待的线程可以放弃等待,处理其他事情。

公平锁

这个是说多个线程在等待同一个锁的时候,必须按照申请锁的时间顺序依次获得锁。synchronized中的锁是不公平锁,锁被释放的时候任何一个等待锁的线程都有机会获得锁,ReentrantLock默认也是不公平锁,可以使用构造函数使得其为公平锁。如果为true代表公平锁Lock lock = new ReentrantLock(true);

绑定条件

这个就是的条件锁。

在性能上,在jdk1.6之前的版本,使用ReentrantLock的性能要好于使用synchronized。而在jdk1.6开始,两者性能均差不多。