java多线程相关的几个小总结

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方法。

扫描二维码关注公众号,回复: 1870875 查看本文章

代码示例:

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() 
interrupt方法用于中断线程。调用该方法的线程的状态位将被置为"中断"状态。
注意:线程中断仅仅是置线程的中断状态位, 不会停止线程。需要用户自己去监视线程的状态为并做处理。
interrupted() 和 isInterrupted()
首先看一下 API中该方法的实现:
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
该方法就是直接调用当前线程的 isInterrupted(true)的方法。

然后 再来看一下API中 isInterrupted的实现:
 public boolean isInterrupted() {
        return isInterrupted(false);
    }
该方法却直接调用当前线程的 isInterrupted( false )的方法。
因此这两个方法有两个主要区别:
  1. interrupted 是作用于当前线程,isInterrupted 是作用于调用该方法的线程对象所对应的线程。(线程对象对应的线程不一定是当前运行的线程。例如我们可以在A线程中去调用B线程对象的isInterrupted方法。)
  2. 这两个方法最终都会调用同一个方法-----isInterrupted( Boolean 参数),,只不过参数固定为一个是true,一个是false;               注意: isInterrupted( Boolean 参数)是isInterrupted( )的重载方法。
 
由于第二个区别主要体现在调用的方法的参数上,让我们来看一看这个参数是什么含义
 
先来看一看被调用的方法 isInterrupted(boolean arg) (Thread类中重载的方法)的定义:
private native boolean isInterrupted( boolean ClearInterrupted);
原来这是一个本地方法,看不到源码。不过没关系,通过参数名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做归纳总结,好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量
java并发Master-Worker模式

3.future模式


在提交请求之后先获得一个回馈但是并不是最终的结果,等待真实的数据处理之后再次返回。









猜你喜欢

转载自blog.csdn.net/u010365819/article/details/80810426