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线程
当一个线程需要等待另一个线程完毕再执行的话,可以使用Thread的join方法。
假设线程A和线程B,在A执行时调用了B的join方法(前提是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线程正常运行,调用了另外一个线程JoinThread的join方法,那么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开始,两者性能均差不多。