java并行程序基础
一、线程与线程的状态
在学习java的过程中已经接触过很多有关线程的概念了,主要记录一下线程的机中状态,以及状态之间的切换。
线程的状态主要分为线程的初始化(New),可运行(Runnable),运行(Running),阻塞(Blocking),死亡(Dead)
线程新建(new)之后线程没有立即得到执行,等线程调用start()方法时,线程才开始执行。当线程执行时,先处于Runnable状态,当线程获取到cpu资源之后进入运行状态,如果线程在运行的过程中遇到synchronized同步块等,就会进入到同步阻塞状态,此时线程暂停执行,直到获取所需要的锁,再继续运行。运行中的线程调用wait()方法后进入等待阻塞,此时线程期待notify()方法唤醒线程,然后等待获取所需的锁,然后再继续运行。运行中的线程,调用sleep()方法或者调用io请求或者调用join()方法时,当sleep()方法结束,join()方法线程运行结束或者终止,io处理完毕,然后进入就绪状态。这里对join()方法理解不清晰,自己做了实践
public class JoinTest {
static class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.err.println(name+"-------"+i);
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread("AAA"));
Thread t2 = new Thread(new MyThread("BBB"));
Thread t3 = new Thread(new MyThread("CCC"));
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t3.start();
}
}
当不调用join()方法的时候t1,t2,t3交替执行。运行结果如下
BBB-------0
CCC-------0
AAA-------0
CCC-------1
CCC-------2
CCC-------3
CCC-------4
CCC-------5
..........
代码中join()方法取消注释后
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t3.start();
运行结果如下
AAA-------0
AAA-------1
BBB-------0
AAA-------2
BBB-------1
AAA-------3
BBB-------2
BBB-------3
AAA-------4
AAA-------5
AAA-------6
BBB-------4
BBB-------5
BBB-------6
BBB-------7
BBB-------8
BBB-------9
AAA-------7
AAA-------8
AAA-------9
CCC-------0
CCC-------1
CCC-------2
CCC-------3
CCC-------4
CCC-------5
CCC-------6
CCC-------7
CCC-------8
CCC-------9
这里join()方法对已经处于运行状态的线程并没有影响,join()方法的作用是在t1运行结束完成后t3才开始执行,之前t3.start()一直没有执行,直到t1执行完成后才开始执行。之后再去看下源码深入理解一下。
再记一下sleep()和wait()的区别,两者都是让线程暂停处于休眠的方法,但是sleep()不释放锁,执行sleep()的线程在休眠期间一直持有锁,其他需要该锁的线程无法得到锁,而wait()方法会释放线程占用的锁,其他的线程有可能获取该锁,并开始运行,wait()方法最典型的例子就是生产者-消费者模型
public class ProAndCon {
static class Box{
private int ball = 0;
private int Max = 5;
public synchronized void produce() {
if(ball == Max) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ball++;
System.err.println("AAA生产了一个ball,当前的库存为:"+ball);
notifyAll();
}
public synchronized void consum() {
if(ball == 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ball--;
System.err.println("BBB消费了一个ball,当前的库存为:"+ball);
notifyAll();
}
}
static class Producer implements Runnable{
private String name;
private Box box;
public Producer(String name,Box box) {
this.name = name;
this.box = box;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
box.produce();
}
}
}
static class Consumer implements Runnable{
private String name;
private Box box;
public Consumer(String name,Box box) {
this.name = name;
this.box = box;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
box.consum();;
}
}
}
public static void main(String[] args) {
Box box = new Box();
Thread t1 = new Thread(new Producer("AAA",box));
Thread t2 = new Thread(new Consumer("BBB",box));
t1.start();
t2.start();
}
}
输出结果为:
AAA生产了一个ball,当前的库存为:1
BBB消费了一个ball,当前的库存为:0
AAA生产了一个ball,当前的库存为:1
AAA生产了一个ball,当前的库存为:2
AAA生产了一个ball,当前的库存为:3
AAA生产了一个ball,当前的库存为:4
AAA生产了一个ball,当前的库存为:5
BBB消费了一个ball,当前的库存为:4
BBB消费了一个ball,当前的库存为:3
BBB消费了一个ball,当前的库存为:2
BBB消费了一个ball,当前的库存为:1
BBB消费了一个ball,当前的库存为:0
AAA生产了一个ball,当前的库存为:1
AAA生产了一个ball,当前的库存为:2
AAA生产了一个ball,当前的库存为:3
AAA生产了一个ball,当前的库存为:4
BBB消费了一个ball,当前的库存为:3
BBB消费了一个ball,当前的库存为:2
BBB消费了一个ball,当前的库存为:1
BBB消费了一个ball,当前的库存为:0
二、线程的基本操作
(1)新建线程
新建一个线程只需要new 一个Thread的实例就行了
Thread t = new Thread()
t.start();
start()方法创建一个线程并让线程进入就绪状态,start()内部调用了run()方法,在这里,如果直接写t.run()也是能通过编译正常执行的,但是却只是执行了一个相当于执行了一个普通类的run方法,并不会创建一个线程。
线程的创建有三种方法,继承Thread类,实现Runnable接口,实现Callable接口,前两者需要重写run()方法,后者需要重写call()方法。
1.public class MyThread extends Thread{}
2.public class MyThread implements Runnable{}
3.public class MyThread implements Callable{}
(2)终止线程
一般线程执行完毕之后就会结束,无须手动关闭。然而有些线程可能执行着大量循环用于提供某种服务。Thread类提供了一个stop()方法,但是该方法在eclipse等编写时会标注该方法已被废弃。原因是stop()方法会强行终止一个线程,并释放锁,这样的话可能在写数据写到一半是强行退出,由另一线程获取锁并向同一个对象写入其他数据,导致数据损坏。终止线程的实现方法可以在类中增加一个stopMe()方法,在run()方法中加上while判断。
boolean stop = false;
public void stopMe(){
stop = ture;
}
public void run(){
while(stop = ture){
break;
}
.....
....
...
}
(3)线程中断
线程中断是一种重要测线程协作机制,线程中断之后不会立即退出,而是给线程一个退出的通知,至于线程接受到通知之后怎么操作,由线程自己决定。与线程中断有关的有三个方法
public void Thread.interrupt(); //中断线程
public boolean Thread.isInterrupted(); //判断线程是否中断
public static boolean Thread.interrupted(); //判断是否中断,并清除当前中断状态
记录一下这三个方法的具体使用
public class ThreadInterrupt {
static class MyThread implements Runnable{
@Override
public synchronized void run() {
while(true) {
if(Thread.currentThread().isInterrupted()) {
System.err.println("线程被中断了");
System.err.println(Thread.interrupted()); //interrupted()重置了中断状态
System.err.println(Thread.interrupted());
if(Thread.currentThread().isInterrupted() == false) {
System.err.println("线程未被中断");
break;
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.err.println("睡眠时执行了线程中断");
//设置中断状态
Thread.currentThread().interrupt();
}
for(int i = 0;i<5;i++) {
System.err.println(i);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new MyThread());
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
输出结果为
睡眠时执行了线程中断
0
1
2
3
4
线程被中断了
true
flase
线程未被中断
线程运行后直接sleep两秒钟,如果在这期间执行了interrupt()来中断线程,那么就会抛一个InterruptException中断异常,因此要进行后续的处理再次提醒线程是时候该中断了;但是这只是一个通知,接下来直接是for循环, 并没有指定收到中断通知之后的操作,因此打印了5个数字,然后再次进入while循环,if语句判断是否有中断线程的通知,isInterrupted()返回true执行if内代码,并输出现场被中断了,此时线程实际上还是没有中断运行。然后输出了interrupted(),返回结果为ture,说明线程收到中断通知要准备中断了,但是interrupted()方法重置了,再次执行相同的语句时,返回的就是false了,接下来的if语句判断interrupted()返回为false时打印语句,并人为控制的跳出了循环,注意前面的interrupted()已经重置了中断状态,此时的线程并未收到中断的通知而做出的操作。
(4)等待(wait)和通知(notify)
为了支持多线程之间的合作,JDK提供了两个重要的接口,线程等待wait()方法和线程唤醒notify()方法;这两个方法是包含在Object类中的,因此所有的对象都能使用它们。当线程A调用了wait()方法之后,线程A就会停止继续执行,转为等待状态,进入等待队列,一直到其他线程执行notify()方法或者notifyAll()方法后线程A被唤醒。需要注意的是,如果等待队列中有很多处理等待的进程的话,notify()方法会随机选取一个线程唤醒,并不是按照先来后到的顺序。wait()方法虽然是每个对象都拥有的方法,但是却不是可以随时随地就调用的,wait()方法一定要在synchronized代码块或方法中才可以使用,而且,wait()和notify()都需要一个目标对象的监视器。线程A在执行wait()方法之前先获得object对象的监视器,wait()正确执行之后释放该监视器;线程B在调用notify()之前,也必须获得这个object监视器,然后再motify()方法正确运行之后也释放该监视器。在线程A被唤醒之后,做的第一件事是重新获取object监视器,如果暂时没有获得这个监视器就必须要等到获得这个监视器才能继续往下运行。
有关wait()方法和notify()方法最好的理解就是上面的生产者-消费者模型。这列不再重复写一遍了。
(5)挂起(suspend)和继续执行(resume)线程
线程挂起和继续执行就是字面表面的意思,当一个线程被挂起后就暂停了,必须等到resume()操作之后才能继续运行,但是现在这两个方法已经被废弃了,因为在挂起的期间并不会释放锁,而且resume()意外的在suspend()方法之前被执行了的话,它所占有的锁可能不会释放,导致系统出问题。
(6)等待线程结束(join)和谦让(yield)
join在上面实践中已经差不多理解清晰了。主要记一下yield()方法,该方法一旦进行,线程会自动让出cpu资源,但是让出资源之后,该线程还是会参与cpu资源的竞争,但是能否被分配到就不一定了。该方法可以用于对一些不怎么重要的线程,在占用cpu资源时,完成了比较重要的工作就把cpu资源让给那些优先级高或者比较重要的线程。