Java多线程实现的方式有四种
- 1.继承Thread类,重写run方法
- 2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
- 3.通过Callable和FutureTask创建线程
4.通过线程池创建线程
java结束线程的三种方式
线程属于一次性的消耗品,在执行完run()之后就i会正常结束并销毁,不能再次start,只能重新创建新的线程。但是有的时候run方法是永远不会结束的,我们必须人为的结束。
有三种方法可以结束线程:
1.设置退出标志,使线程正常退出,也就是当run()方法完成后线程终止
2.使用interrupt()方法中断线程
3.使用stop方法强行终止线程(不推荐使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃,使用它们是极端不安全的!)
使用退出标志
public class ThreadSafe extends Thread {
public volatile boolean exit = false; //利用volatile 定义一个退出的标志位
public void run() {
while (!exit){
//do something
}
}
}
使用interrupt方法中断当前线程
线程处于阻塞状态,如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。
代码示例:
public class ThreadSafe extends Thread {
public void run() {
while (true){
try{
Thread.sleep(5*1000);//阻塞5妙
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行break跳出循环。
}
}
}
}
线程未处于阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
代码示例:
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){
//do something, but no throw InterruptedException
}
}
}
为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:
代码示例:
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行break跳出循环。
}
}
}
}
程序中可以直接使用thread.stop()来强行终止线程,但是stop方法是很危险的
为什么启用stop和suspend方法
其实stop方法天生就不安全,因为它在终止一个线程时会强制中断线程的执行,不管run方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象。这一现象会被其它因为请求锁而阻塞的线程看到,使他们继续向下执行。这就会造成数据的不一致,我们还是拿银行转账作为例子,我们还是从A账户向B账户转账500元,这一过程分为三步,第一步是从A账户中减去500元,假如到这时线程就被stop了,那么这个线程就会释放它所取得锁,然后其他的线程继续执行,这样A账户就莫名其妙的少了500元而B账户也没有收到钱。这就是stop方法的不安全性。
suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。
假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在或者相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。这就是suspend被弃用的原因。
java线程中interrupt、interrupted和isInterrupted方法的区别
interrupt()return currentThread().isInterrupted(true);
}
return isInterrupted(false);
}
- interrupted 是作用于当前线程,isInterrupted 是作用于调用该方法的线程对象所对应的线程。(线程对象对应的线程不一定是当前运行的线程。例如我们可以在A线程中去调用B线程对象的isInterrupted方法。)
- 这两个方法最终都会调用同一个方法-----
isInterrupted( Boolean 参数
),
,只不过参数固定为一个是true,一个是false; 注意:isInterrupted( Boolean 参数
)是
isInterrupted(
)的重载方法。
|
private native boolean isInterrupted( boolean ClearInterrupted); |
如果这个参数为true,说明返回线程的状态位后,要清掉原来的状态位(恢复成原来情况)。这个参数为false,就是直接返回线程的状态位。
这两个方法很好区分,interrupted方法内部调用是currentThread().isInterrupted(true),也就是说只能是返回当前线程的中断位,你在主线程中使用子线程名点interrupted方法也是返回的是主线程的中断标志位,还有interrupted这个方法是static,所以一般我们用Thread点的方式来调用,也就是说一般只是用来查看当前线程的中断标志位。只有当前线程才能清除自己的中断位(对应interrupted()方法),也就是说interrupted方法可以返回中断标志位,但是返回之后会直接清除中断标志位为false,当你第一次调用的时候返回true,第二次调用就会返回false,因为在第一次调用的时候已经清除了,但是isinterrupted方法不会清除。isinterrupted不像interrupted方法那样,可以用对象名点的方式直接查看指定线程对象的中断标志位。即使在其他线程中也可以查看。
这里再看一个例子
public static void main(String[] args) {
Thread aThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程开始工作");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程结束工作");
System.out.println(Thread.currentThread().isInterrupted());//输出的是false
}
});
aThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
aThread.interrupt();
}
上面这个例子按理说在中断之后调用
Thread.currentThread().isInterrupted()
应该是返回的true,但是最终返回的还是false,为啥呢上面已经解释过了,意思就是线程由wait、sleep以及jion三个方法引起的阻塞,之后使用interrupt方法中断当前阻塞的线程将会抛出一个InterruptedException的异常,但是在这个异常抛出之前虚拟机会将中断标志为重新设置为false。所以也就可以解释了上面为什么我们输出的标志位还是false。
如何实现java主线程等待子线程结束之后再执行。
1.join()方法。之前的文章里有说过join。
public class JoinDemo {
public static void main(String[] args) throws Exception {
//创建子线程,并启动子线程
Thread subThread = new Thread(new SubThread());
subThread.start();
//主线程处理其他工作,让子线程异步去执行
mainWork();
//主线程其他工作完毕,等待子线程的结束, 调用join系列的方法即可(可以设置超时时间)
subThread.join();
System.out.println("Now all thread done!");
}
private static class SubThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
System.out.println("Sub thread is starting!");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Sub thread is stopping!");
}
}
}
2.使用future 之前也 说过,future.get()方法是一个阻塞方法,在线程没有执行完返回结果之前,会阻塞调用线程。
public class FutureDemo {
//创建一个容量为1的线程池
static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws Exception {
//创建线程并提交线程,同时获取一个future对象
Thread subThread = new Thread(new SubThread());
Future future = executorService.submit(subThread);
//主线程处理其他工作,让子线程异步去执行
mainWork();
//阻塞,等待子线程结束
future.get();
System.out.println("Now all thread done!");
//关闭线程池
executorService.shutdown();
}
private static class SubThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
System.out.println("Sub thread is starting!");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Sub thread is stopping!");
}
}
3.CountDownLatch 闭锁,之前的文章也说过。这种方式我觉得是最好的,上面两种情况在线程数为一两个的时候,还可以,如果需要控制的线程数很多的话,再采取这种方式就有点过意不去了。
第一种方法, 你要调用很多个线程的join, 特别是当你的线程不是for循环创建的, 而是一个一个创建的时候.
第二种方法, 要调用很多的future的get方法, 同第一种方法.
public class CountDownDemo {
public static void main(String[] args) throws Exception {
//定义线程数
int subThreadNum = 5;
//取得一个倒计时器,从5开始
CountDownLatch countDownLatch = new CountDownLatch(subThreadNum);
//依次创建5个线程,并启动
for (int i = 0; i < subThreadNum; i++) {
new SubThread(2000*(i+1), countDownLatch).start();
}
//主线程工作
mainWork();
//等待所有的子线程结束
countDownLatch.await();
System.out.println("Main Thread work done!");
}
private static class SubThread extends Thread{
private CountDownLatch countDownLatch;
private long workTime;
public SubThread(long workTime,CountDownLatch countDownLatch){
this.workTime = workTime;
this.countDownLatch = countDownLatch;
}
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("Sub thread is starting!");
Thread.sleep(workTime);
System.out.println("Sub thread is stopping!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
//线程结束时,将计时器减一
countDownLatch.countDown();
}
}
}
}
4.CyclicBarrier栅栏,其实从栅栏的定义上来说是不太适合这种状况下使用的,但是如果我们把子线程到达栅栏的条件设置在子线程结束的位置,把主线程到达栅栏的条件设置在主线程一开始的位置,那么就可以达到子线程结束之后,主线程再执行。具体的栅栏 的工作方式,以前的文章也说过。
public static void main(String[] args) {
final CyclicBarrier barrier=new CyclicBarrier(2);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("子线程开始工作");
Thread.sleep(1000);
System.out.println("子线程结束工作");
barrier.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
try {
barrier.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程工作");
System.out.println("。。。。。");
}
多线程的设计模式
1.生产者消费者模式
生产者和消费者也是一个非常经典的多线程模式,我们在实际中开发应用非常广泛的思想理念。在生产-消费模式中:通常由两类线程,即若干个生产者和若干个消费者的线程。生产者负责提交用户数据,消费者负责具体处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。
2.master-worker模式
Master-Worker模式是常用的并行计算模式。他的核心思想是系统由两类进程协作工作:Master进程和Worker进程.Maseter负责接收和分配任务, Worker负责处理子任务。当各个Worker子进行处理完成后,会将结果返回给Master,由Msster做归纳总结,好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量
3.future模式
在提交请求之后先获得一个回馈但是并不是最终的结果,等待真实的数据处理之后再次返回。