进程与线程
1. 进程一个进程包含1个以上线程,每个进程都有独立的代码和数据空间即进程上下文,进程间的切换开销比较大。进程是资源分配的最小单位。
2. 线程:每个线程有独立的运行栈和程序计数器,多个线程共享代码和数据空间,线程切换开销小。线程是CPU调度的最小单位。
3. 多进程是指操作系统能同时运行多个任务(程序)。
4. 多线程是指在同一个程序中有多个顺序流在执行。
5. 进程和线程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
6. Java程序在运行中至少启动2个线程,一个是main线程,一个是垃圾收集线程
Java实现多线程的两种方法
Java实现多线程主要有两种方法,继承Thread类和实现Runable接口。还有另外一种方法:实现Callable接口,本文不做介绍。
1.继承Thread类
public class Thread1 extends Thread {
private String name;
private int tickets = 10;
public Thread1(String name) {
this.name = name;
}
public void run() {
if(tickets > 0) {
System.out.println(name + "抢到了第" + tickets-- + "张票");
} else {
System.out.println(name + "没有抢到票");
}
}
public static void main(String[] args) {
for(int i=0; i<10; i++) {
new Thread1("黄牛"+i).start();
}
}
}
运行结果:
黄牛0抢到了第10张票
黄牛4抢到了第10张票
黄牛1抢到了第10张票
黄牛5抢到了第10张票
黄牛2抢到了第10张票
黄牛3抢到了第10张票
黄牛6抢到了第10张票
黄牛7抢到了第10张票
黄牛8抢到了第10张票
黄牛9抢到了第10张票
黄牛10抢到了第10张票
黄牛11抢到了第10张票
1)从运行结果可以看出,12个线程之间不是顺序执行,是交互执行;
2)每个线程的run()方法是独立的,tickets变量不是共享的,所以继承Thread类很难实现资源共享。
将代码修改如下,就可以实现资源共享,该方法跟实现Runnable接口类似,因为Thread也实现了Runnable接口。
public class Thread1 extends Thread {
private String name;
private int tickets = 10;
public Thread1() {
}
public Thread1(String name) {
this.name = name;
}
public void run() {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "抢到了第" + tickets-- + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "没有抢到票");
}
}
public static void main(String[] args) {
Thread1 th1 = new Thread1();
for(int i=0; i<12; i++) {
new Thread(th1,"黄牛"+i).start();
}
}
}
运行结果:
黄牛0抢到了第10张票
黄牛2抢到了第8张票
黄牛1抢到了第9张票
黄牛3抢到了第7张票
黄牛4抢到了第6张票
黄牛5抢到了第5张票
黄牛6抢到了第4张票
黄牛7抢到了第3张票
黄牛8抢到了第2张票
黄牛9抢到了第1张票
黄牛10没有抢到票
黄牛11没有抢到票
2.实现Runnable类
public class Thread2 implements Runnable {
private String name;
private int tickets = 10;
public Thread2() {
}
public Thread2(String name, int tickets) {
this.name = name;
this.tickets = tickets;
}
@Override
public synchronized void run() {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets-- + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "没抢到票");
}
}
public static void main(String[] args) {
Thread2 th = new Thread2();
for(int i=0; i<12; i++) {
new Thread(th,"黄牛"+i).start();
}
}
}
运行结果:
黄牛0抢到了第10张票
黄牛2抢到了第9张票
黄牛3抢到了第8张票
黄牛6抢到了第7张票
黄牛1抢到了第6张票
黄牛7抢到了第5张票
黄牛4抢到了第4张票
黄牛5抢到了第3张票
黄牛8抢到了第2张票
黄牛10抢到了第1张票
黄牛9没抢到票
黄牛11没抢到票
1)从结果看,完美实现抢票机制;
2)通过构造方法Thread(Runnable target, String name)传递一个Runnable的实例以及为线程设置名字;
3)每个线程调用的都是同一个对象的run()方法,变量tickets是线程共享的。
3.实现Runnable接口比继承Thread的好处
1)实现Runnable接口可以避免单继承的限制;
2)实现Runnable接口便于资源共享。
线程的状态
线程状态转换图如下:
线程分为五个阶段:创建、就绪、运行、阻塞、终止,具体如下:
1.创建状态(new):新建一个线程对象。
2.就绪状态(runnable):调用start()方法后,线程变为可运行状态,此时线程位于线程池中,等待获取CPU的使用权。
3.运行状态(running):线程获得CPU的使用权,执行run()方法。
4.阻塞状态(blocked):线程因为某种原因放弃CPU的使用权,暂时停止运行,直到重新进入就绪状态,才有机会运行。
1)等待阻塞:线程执行wait()方法,线程进入等待池中并释放所有的锁,可以通过notify()和notifyAll()唤醒。
2)同步阻塞:线程在获取对象的同步锁时,如果锁被其它线程占用,则会进入锁池中。
3)其它阻塞:如线程执行sleep()或join()方法,会进入阻塞状态,sleep()时间到了或者join()等待的线程已经终止,线程会重新进入就绪状态。
常用函数
1.static void sleep(long millis)
Thread类中的方法,让当前线程站厅执行millis毫秒,线程不释放锁。
2.void join()
Thread类中的方法,等待该线程终止,通过例子来解释。
public class Thread3 extends Thread {
private String name;
public Thread3(String name) {
super(name);
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+ "--->" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程开始运行");
Thread3 th1 = new Thread3("A");
Thread3 th2 = new Thread3("B");
th1.start();
th2.start();
System.out.println(Thread.currentThread().getName()+"主线程结束运行");
}
}
运行结果:
main主线程开始运行
A--->0
main主线程结束运行
B--->0
B--->1
A--->1
A--->2
B--->2
A--->3
B--->3
A--->4
B--->4
从运行结果可以看到,main线程比子线程A和B结束的要早,而我们期望的是main线程在所有子线程结束后才能结束,这里就可以用到join()方法了,件如下代码。
public class Thread3 extends Thread {
private String name;
public Thread3(String name) {
super(name);
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+ "--->" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程开始运行");
Thread3 th1 = new Thread3("A");
Thread3 th2 = new Thread3("B");
th1.start();
th2.start();
try {
th1.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"主线程结束运行");
}
}
运行结果:
main主线程开始运行
A--->0
B--->0
A--->1
B--->1
A--->2
B--->2
A--->3
B--->3
B--->4
A--->4
main主线程结束运行
3.static void yield()
Thread类中的方法,可以让当前线程从运行状态进入到就绪状态,把CPU使用权交给相同或者更高优先级的其它线程,下一个运行线程可能仍然是自己。
sleep()方法在一段时间内是不可运行的,这段时间是其他线程在运行。
4.void setPriority(int newPriority)
Thread类中的方法,可以更改线程的优先级。java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY,值为10
static int NORM_PRIORITY,分配给线程的默认优先级,值为5
static int MIN_PRIORITY,值为1
线程的优先级有继承关系,如线程A继承了线程B,那么B和A的优先级是一样的。
JVM虽然提供了10个优先级,但与常见的操作系统都不能很好的映射。如果希望程序能够移植到各个操作系统中,应该仅仅使用三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
5.void interrupt()
Thread类中的方法,该方法可以用来终止一个线程,但调用该方法只是将线程的中断状态设置为true,并不是直接中断线程。
Thread类中s还定义了stop()方法,这种方法不提倡使用。该方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类,所以直接使用stop()方法就有可能造成对象的不一致性。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
看下面例子:
public class InterruptTest {
public static void main(String[] args) {
Runnable ra1 = new Runnable() {
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println("ra1:"+i);
if(Thread.currentThread().isInterrupted()) {
System.out.println("ra1 is interrupted...");
break;//中断线程
}
try {
//当interrupt()被调用后,中断状态为true,
//这时再调用sleep方法不会执行,会抛出异常InterruptedException,并清除中断状态
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("ra1 isInterrupted:"+Thread.currentThread().isInterrupted());
//重新设置中断状态为true
Thread.currentThread().interrupt();
//这里加return,可以直接中断线程
//return;
}
}
}
};
Thread th1 = new Thread(ra1);
Runnable ra2 = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
th1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread th2 = new Thread(ra2);
th1.start();
th2.start();
}
}
运行结果:
ra1:1
ra1:2
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at lifeixu.com.multithread.InterruptTest$1.run(InterruptTest.java:18)
at java.lang.Thread.run(Thread.java:745)
ra1 isInterrupted:false
ra1:3
ra1 is interrupted...
也可以自定义一个中断变量如“boolean stop = true/false”的方式来中断线程,这里不做介绍。
6.final void wait()
Object类中的方法,与notify()/notifyAll()必须要与synchronized一起使用。
7.final void notifyAll() & final void notify()
Object类中的方法,与wait()必须要与synchronized一起使用。
下面的代码是用来介绍wait()和notify()的,通过这两个方法来实现10次ABCD顺序输出。
public class Threadabc implements Runnable {
private boolean stop = false;
private String name;
private Object prev;
private Object self;
public Threadabc(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while(count>0) {
synchronized(prev) {
synchronized(self) {
System.out.print(name);
count--;
self.notify();//唤醒等待self的线程,同步块synchronized(self){}结束后释放self锁
}
if(count==0) {//循环结束后终止线程,否则线程一直处于wait状态
return;
}
try {
prev.wait();//本线程处于等待prev的状态,释放prev锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Object d = new Object();
Threadabc tha = new Threadabc("A",d,a);
Threadabc thb = new Threadabc("B",a,b);
Threadabc thc = new Threadabc("C",b,c);
Threadabc thd = new Threadabc("D",c,d);
new Thread(tha).start();
Thread.sleep(100);//保证输出的是ABCD的顺序
new Thread(thb).start();
Thread.sleep(100);
new Thread(thc).start();
Thread.sleep(100);
new Thread(thd).start();
}
运行结果:
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
关键字synchronized
推荐链接:Java中Synchronized的用法
线程间的通信
线程安全