多线程的优势:多进程运行需要独立的内存空间,而多线程可以共享内存,从而提高了线程的运行效率。
创建线程一般使用两种方式:
1、继承Thread类:
import java.io.IOException;
public class Test extends Thread {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(this.getName()+":"+i);
}
}
public static void main(String[] args) throws IOException {
for(int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" :"+i);
if(i == 20) {
//创建并启动线程一
new Test().start();
//创建并启动线程二
new Test().start();
}
}
}
}
2、通过实现Runnable接口:
import java.io.IOException;
public class Test implements Runnable {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws IOException {
for(int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" :"+i);
if(i == 20) {
Test t =new Test();
//通过new Thread(target,name)的方式来启动线程
new Thread(t,"线程一").start();
new Thread(t,"线程二").start();
}
}
}
}
这两种方式的区别:
通过继承Thread类创建多线程,获得线程对象比较简单,通过this即可获得,而通过实现Runnable接口创建多线程,需要通过Thread.currentThread()方法来获得当前线程对象。前者创建Thread子类便可代表线程对象,后者创建的Runnable只能作为线程对象的target。
两种方式的对比:
通过继承Thread类创建线程较为简单,但Java是单继承,所以无法继承其他类。
通过实现Runnable接口,如果需要访问当前线程对象,需要通过Thread.currentThread()方法来访问,但它优点就是可以继承其他类,多线程共享一个target对象,非常适合多线程共享一份资源的情况。
因此,一般采用实现Runnable接口的方法实现多线程。
线程的生命周期:新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),和死亡(Dead)
join线程:
当在某个程序中执行流调用其他线程的join()时,则当前线程被阻塞,知道被join()加入的join线程执行完毕以后。
import java.io.IOException;
public class Test implements Runnable {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws IOException, InterruptedException {
for(int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" :"+i);
if(i == 20) {
Test t =new Test();
//通过new Thread(target,name)的方式来启动线程
Thread j1 =new Thread(t,"被jion的线程");
j1.start();
j1.join();
}
}
}
}
运行部分结果:
main :18
main :19
main :20
被jion的线程:0
被jion的线程:1
被jion的线程:2
被jion的线程:3
可以看到,当主线程运行到20时被阻塞,在被join线程执行完后才会执行main线程。
后台线程:
有一种线程,叫做“守护线程”,为其他线程提供服务,被称为后台线程。JVM的垃圾回收机制就是典型的后台线程。
特征:当前台线程死亡后,后台线程将随之死亡。
调用Thread对象的setDaemon(true)方法可将指定线程设置为后台线程。Thread类还提供了一个isDaemon()方法来判断是否为后台线程。
下面程序展示了当前台线程执行完毕死亡后,后台线程也随之死亡。
import java.io.IOException;
public class Test implements Runnable {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Test t =new Test();
Thread b =new Thread(t,"后台线程");
//将其设置为后台线程
b.setDaemon(true);
//启动后台线程
b.start();
for(int i =0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
运行结果:
main :0
main :1
main :2
main :3
main :4
后台线程:0
main :5
后台线程:1
后台线程:2
main :6
main :7
main :8
main :9
后台线程:3
后台线程:4
后台线程:5
后台线程:6
后台线程:7
后台线程:8
后台线程:9
后台线程:10
线程睡眠:sleep
如果需要让当前线程暂停一段时间,让出cpu资源,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法实现。
程序如下:
import java.io.IOException;
public class Test implements Runnable {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Test t =new Test();
Thread b =new Thread(t,"线程一");
//启动线程
b.start();
for(int i =0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" :"+i);
if(i ==5) {
Thread.sleep(1000);
}
}
}
}
线程让步:yield
上述的sleep()方法会将当前线程进入阻塞状态,但yield()方法不会阻塞该线程,会将其转入就绪状态。yield()只是当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:线程调度器又将其调度出来执行。
import java.io.IOException;
public class Test implements Runnable {
private int i=0;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==20) {
Thread.yield();
}
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Test t =new Test();
Thread b =new Thread(t,"高级线程");
//将线程设置为最高优先级
//b.setPriority(Thread.MAX_PRIORITY);
//启动线程
b.start();
Thread c = new Thread(t,"低级线程");
//将线程设置为最低优先级
//c.setPriority(Thread.MIN_PRIORITY);
//启动线程
c.start();
}
}
yield()静态方法在没有设置优先级的情况下会让其调度器重新调度,在设置了优先级的情况下,只能调度其同等优先级或者更高优先级的线程。
关于sleep()方法和yield()方法的区别:
sleep()方法暂停当前线程后,会不理会其他线程的优先级,让其他线程获得运行机会。但yield()只会调度其同等优先级或者更高优先级的线程。
sleep()方法让线程转入阻塞状态,直到经历完阻塞时间后才会进入就绪状态,而yield()强制让当前线程进入就绪状态,所以完全有可能调用的还是当前线程。
sleep()方法声明了InteruptedExecption异常,所以调用该方法时要么捕捉该异常,要么显式抛出该异常;而yield()方法则没有声明抛出任何异常。
sleep()方法比yield()有更好的移植性,所以不建议使用yield()方法来控制并发线程的执行。