本文主要介绍:线程创建的三种方式,线程间的方法,线程状态以及如何转换,最后再通过简单的习题理解多线程的使用。
1、继承Thread类创建线程类
步骤:(1)创建Thread类的子类,并重写run方法,run方法的方法体代表该线程需要完成的任务;
(2)创建Thread类的实例,即创建线程对象;
(3)调用线程对象的start()方法启动线程;
示例如下:
public class Thread1208 extends Thread{
@Override
public void run() {
//重写run方法
System.out.println("创建线程1");
}
}
public class Test{
public static void main(String[] args){
/**
* 继承Thread创建线程(该方法无法实现多个线程共享线程类的实例变量)
*/
Thread1208 t = new Thread1208();
t.start();
}
}
2、实现Runnable接口,创建线程
步骤:(1)定义Runnable接口的实现类,并重写run ()方法;
(2)创建实现类的实例对象,将此实例作为Thread类的target创建Thread对象,该Thread对象才是真正的线程对象;
(3)调用start()方法启动线程。
示例如下:
public class MyThread immplements Runnable {
public void run(){
system.out.println("MyThread.run()");
}
}
public class Test{
public static void main(String[] args){
MyThread runThread = new MyThread();
Thread runThread = new Thread(runThread,"");
runThread.start();
}
}
3、实现Callable接口,通过FutureTask包装器创建线程
步骤:(1)创建Callable接口的实现类,重写call()方法,call()方法的执行体也是执行该线程任务,且call()方法有返回值;
(2)使用FutureTask类包装Callable对象,该FutureTask封装类Callable的call()方法的返回值;
(3)使用FutureTask对象作为Thread类的target创建线程;
(4)调用线程的start()方法启动线程。
示例如下:
public class Callable1208 implements Callable {
int i = 0;
@Override
public Integer call() throws Exception {
for(;i < 20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
public class Test{
public static void main(String[] args){
/**
* 通过实现Callable接口创建线程(将Callable实现类作为Thread的target,并接受线程体的返回值)
*/
Callable1208 target = new Callable1208();
FutureTask f = new FutureTask(target);
Thread thread = new Thread(f,"新线程");
thread.start();
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
结论:采取Runnable、Callable的优势在于——线程类只是实现了Runnable或Callable接口,还可以继承其它类;在这种方法下,多个线程可以共享一个target对象,因此非常适合多个相同线程处理同一份资源的情况,从而将CPU、代码和数据分开,形参清晰的模型,体现了面对对象的编程思想。劣势在于编程复杂度略高。
三种实现方式的区别:
start()方法
用于启动一个线程,使相应的线程进入排队等待状态。一旦其获得CPU资源,就可以脱离它的主线程独立开始自己的生命周期。也就是说,一个线程即使调用了start()方法,也不一定会立即执行。start()方法要首先调用,不能重复调用。
run()方法
Thread类中的run()方法和Runnable接口中的run()方法作用相同,单独调用run()方法,会在当前线程执行run()方法,而并不会启动新线程。
sleep()方法
线程睡眠,该方法是Thread类的静态方法,作用是:让当前线程休眠指定时间,时间到达后自动恢复线程执行,该方法不会释放对象锁,即若有synchronized代码块,其他线程仍不能访问共享数据,该方法要捕捉异常。
yield()方法
线程让步,该方法与sleep()方法类似,也是Thread类的静态方法(获取当前正在执行的程序,通过Thread实例对象不一定正在执行,不一定正在使用cpu使用权),让正在执行的程序让步(让出cpu使用权),让给优先级较高的线程执行。(如果没有优先级较高的线程,当前程序会再次获得cpu的使用权);该方法不能由用户指定暂停时间,且yield()方法只能让同优先级的线程有执行机会。
join()方法
该方法使调用该方法的线程在此之前执行完毕,即等待该方法的线程执行完毕后再向下继续执行。该方法也捕捉异常,该方法的影响只存在于执行join()方法的线程和调用该线程的线程之间。
interrupt()方法
Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!
interrupt()的作用是中断当前线程。终止处于“阻塞状态”的线程,当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常,将InterruptedException放在适当的位置就能终止线程;终止处于“运行状态”的线程interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
setDeamon(boolean b)与isDeamon()方法
首先介绍一下守护线程的含义,守护线程为脱离于终端控制的线程,服务于非守护线程 -->java --> JVM实例 -->gc --->守护线程,守护线程生命周期,有非守护线程存在,那么守护线程存在,否则,守护线程也结束;setDeamon(boolean b)方法用于设置线程为守护线程 ,false:非守护线程(默认),true:守护线程;isDeamon();//判断当前这个线程是否为守护线程 返回值为Boolean类型,返回 true:守护线程,反之为非守护线程。
setPriority()方法
setPriority()方法用于设置线程优先级,来让线程具有较高的执行权限(1-10,默认5);
习题练习:
创建3个线程,然后倒序执行;
public class Test{
public static void main(String[] args){
/**
*创建3个线程,倒叙启动
*/
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t3");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1");
}
});
t1.start();
t2.start();
t3.start();
}
}
线程状态可以有以下6种状态:
New //新建状态
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
Runnable //可执行状态
可执行状态又分为Runnable(就绪状态)和Running(执行状态)。
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
而Running(运行状态),处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
Blocked //阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。 阻塞状态可分为以下3种:
1、位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中, 这涉及到“线程通信”的内容。
2、位于对象锁池中的阻塞状态(Blocked in object’s lock pool):当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
3、其他阻塞状态(Otherwise Blocked):当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
Waiting //等待状态
当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。在调用Object.wait方法或Thread.join方法,或者等待等待Lock或Condition时,就会出现这种状况。
Timed waiting //超时等待状态
有几个方法有超时参数,调用它们导致线程进入即使等待状态。这一状态将一直保持到超时期满或收到适当的通知。
Terminated //终止状态
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
下图展示线程可以具有的状态以及从一个状态到另一个状态可能的转换。
1、调整现场优先级:Java线程有优先级,优先级高的线程获得较多的运行机会(运行时间);
static int Max_priority 线程可以具有的最高优先级,值为10;
static int MIN_PRIORIYT 线程可以具有的最低优先级,值为1;
static int NORM_PRIORITY 分配给线程的默认优先级,值为5;
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级;
2、线程睡眠:Thread.sleep(long millins)使线程转到阻塞状态;
3、线程等待:Object.wait()方法,释放线程锁,使线程进入等待状态,直到被其他线程唤醒(notify()和notifyAll());
4、线程让步:Thread.yeild()方法暂停当前正在执行的线程,使其进入等待执行状态, 把执行机会让给相同优先级或更高优先级的线程,如果没有较高优先级或相同优先级的线程, 该线程会继续执行;
5、线程加入:join()方法,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,知道另一个进程运行结束,当前线程再有阻塞状态转为就绪状态;
1、yield,sleep,join方法区别??
sleep()方法
线程的方法,sleep()方法需要指定时间,让正在执行的线程在指定时间暂停执行,进入阻塞状态;该方法可以使(同)高优先级获得执行机会,也可以使低优先级的线程获得执行机会,但该方法不能释放锁标志,即如果有synchronized代码块,其他线程仍然不能访问共享数据;
wait()方法
实例方法,用于锁机制中,wait()方法一般是和notify()和notifyAll()方法一起使用,这三个方法用于多个线程对共享数据的存取,必须在synchronized代码块中使用,这三个方法都是Onject类的方法;
与sleep()方法区别在于:wait()方法会释放锁标志,在调用某一对象的wait()方法后,会暂停线程执行,并将当前线程放入对象等待池中,直到调用notify()方法,将从对象等待池中移出任意一个线程放入锁标志等待池,只有锁标志等待池中的线程可以获取锁标志,随时争夺锁的拥有权,而调用notifyAll()方法,将从对象等待池中移出所有的线程到锁标志等待池中。除使用notify和notifyAll方法,也可以使用指定时间的wait方法;
由于这三个方法只能在synchronized代码块中执行,若使用重入锁要实现相同的效果,可以调用重入锁的newCondition()创建对象,调用await(),signal()和signalAll()方法;
yield()
该方法不会释放锁标志,与sleep()方法的区别在于:没有参数,该方法可以使当前线程重新回到可执行状态,只能使同优先级或高优先级的线程得到执行机会;
join()
该方法会使得当前线程在调用join()方法的线程执行完再执行;
2、继承Thread类与实现Runnable接口创建线程的区别:
(1)继承Thread类为单继承,实现Runable接口为多实现,灵活性上,实现Runnable接口创建线程更灵活;
(2)线程类继承自Thread相对于Runable来说,使用线程的方法更方便一些;
(3)实现Runnable接口的线程类的多个线程、可以更方便的访问同一个变量,而Thread类需要内部类来进行替换.
3、 实现Runnable接口与实现Callable接口创建线程的区别:
(1)Callable接口的实现类重写call()方法,有返回值,而Runnable接口实现类重写run()方法没有返回值;
(2)call()方法可抛出异常,而run()方法是不能抛出异常的;
(3)运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。