Java高并发学习(一)

Java高并发学习(一)

 

初始线程:线程的基本操作

进行java并发设计的第一步,就是必须了解Java虚拟机提供的线程操作的API。比如如何创建并启动线程,如何终止线程,中断线程等。

1.定义线程:

(1).继承Thread方法,形式如下

  1. public static class T1 extends Thread{

  2. @Override

  3. public void run(){

  4. System.out.println("fist.T1.run()");

  5. }

  6. }

(2) .实现Runnable接口创建线程方式,形式如下

 
  1. class MyThread implements Runnable{

  2. @Override

  3. public void run(){

  4. System.out.println("fist.T2.run()");

  5. }

  6. }

  7. public class fist {

  8. public static void main(String args[]){

  9. MyThread mythread = new MyThread();

  10. Thread t1 = new Thread(mythread);

  11. t1.start();

  12. }

  13. }

说明:Thread类有一个非常重要的构造方法public Thread(Runnable target),他传入一个Runnable实例,在start()方法调用时,新的线程会执行Runnable.run()方法。

实际上,默认的Thread.run()就是这么做的:

 
  1. Public void run(){

  2. if(target != null){

  3. Target.run();

  4. }

  5. }

 

 

2.启动线程:

启动线程很简单。只需要使用new关键字创建一个线程对象,并且将它start()起来即可。

 
  1. Thread t1 = new Thread();

  2. t1.start();

注意一下:

下面的代码能通过编译,也能正常执行。但是,却不能创建一个新的线程,而是在当前线程中调用run()方法,只是作为一个普通方法去调用。

 
  1. Thread t1 = new Thread();

  2. t1.run();

因此希望大家特别注意,调用start()方法和run()方法的区别。

 

 

3.终止线程:

如何正常的关闭一个线程呢?查阅JDK,不难发现Thread提供了一个stop方法。如果使用stop()方法,就可以立即将一个线程终止,非常方便。但是eclipse会提示你stop()方法为废弃方法。也就是说,在将来JDK会删除该方法。

为什么stop()方法会被废弃呢?原因是stop()方法太过暴力,强行把在执行的线程停止,可能会导致数据不一致问题。

那如果需要停止一个线程时,应该怎么做呢?其实方法很简单,只是需要由我们自行决定线程何时退出就可以了。

例如:

 
  1. class MyThread extends Thread{

  2. volatile boolean stopme = false;

  3. public void stopeMe(){

  4. stopme = true;

  5. }

  6. @Override

  7. public void run(){

  8. while(true){

  9. if(stopme){

  10. System.out.println("exit by stop me");

  11. break;

  12. }

  13. }

  14. }

  15. }

代码中定义了一个标记变量stopme,用于指示线程是否需要退出。当stopMe()方法被调用,stopme被设置为true,此时,线程会检测到这个改动,线程就自然退出了。

 

 

4.线程中断:

从表面上理解,中断就是让线程暂停执行的意思,实际上并非如此。在上一节中我们已经讨论了stop()方法停止线程的害处,并且提供了一套完善线程退出功能。那在JDK中是否提供更强大的支持呢?答案是肯定的,那就是线程中断。

与线程中断有关的有三个方法,这三个方法看起来很像,所以可能会引起混淆和误用,请大家注意。

 
  1. public void Thread.interrupt() //中断线程

  2. Public void boolean Thread.isInterrupted() //判断线程是否被中断

  3. Public static boolean Thread.interrupted() //判断是否被中断,并请除当前中断状态

Thread.interrupt()设置中断标志位,Thread.isInterrupted()检查中断标志位,Thread.interrupted()清除中断标志位。

下面这段代码对t1进行了中断,那么t1中断后,t1会停止执行吗?

 
  1. class MyThread extends Thread{

  2. @Override

  3. public void run(){

  4. while(true){

  5. System.out.println("Thread running");

  6. }

  7. }

  8. }

  9.  
  10. public class fist{

  11. public static void main(String args[]){

  12. Thread t1 = new MyThread();

  13. t1.start();

  14. t1.interrupt();

  15. }

  16. }

在这里虽然会t1进行了中断,但是t1并没处理中断的逻辑,因此,即使t1线程被设置了中断,但是这个中断不会发生任何作用。

如果希望t1在中断后退出,就必须为他增加相应的中断处理代码:

 
  1. class MyThread extends Thread{

  2. @Override

  3. public void run(){

  4. while(true){

  5. System.out.println("Thread running");

  6. if(Thread.currentThread().isInterrupted()){

  7. System.out.println("interrput");

  8. break;

  9. }

  10. }

  11. }

  12. }

  13.  
  14. public class fist{

  15. public static void main(String args[]) throws InterruptedException{

  16. Thread t1 = new MyThread();

  17. t1.start();

  18. Thread.sleep(2000);

  19. t1.interrupt();

  20. }

  21. }

特别注意,如果在循环体中出现了wait()或者sleep()这样操作的时候,中断可能会被忽略。

Thread.sleep()方法会让当前线程休眠若干时间,他会抛出一个interruptException中断异常。interruptException不是运行时异常,也就是程序必须捕获并处理他。当线程在休眠时,如果被中断,这个异常就会产生。

 
  1. class MyThread extends Thread{

  2. @Override

  3. public void run(){

  4. while(true){

  5. System.out.println("Thread running");

  6. if(Thread.currentThread().isInterrupted()){

  7. System.out.println("interrput");

  8. break;

  9. }

  10. try {

  11. Thread.sleep(2000);

  12. } catch (InterruptedException e) {

  13. System.out.println("when sleep interrupt");

  14. Thread.currentThread().interrupt();

  15. }

  16. System.out.println("Thread end");

  17. }

  18. }

  19. }

  20.  
  21. public class fist{

  22. public static void main(String args[]) throws InterruptedException{

  23. Thread t1 = new MyThread();

  24. t1.start();

  25. Thread.sleep(1000);

  26. t1.interrupt();

  27. }

  28. }

如果线程运行到了sleep()代码段,主程序中断线程,线程这这时候抛出异常,进入catch的异常处理。在catch代码段中,由于捕获到了中断,我们可以立即退出线程。在这里我们并没有这么做,因为也许在这段代码中,我们还必须进行后续处理,保证数据的一致性和完整性,因此,执行了Thread.interrupt()方法在次中断自己,设置中断标志位。只有这么做才能当线程休眠时响应中断。

注意:Thread.sleep()方法由于中断而抛出异常,此时,它会清除中断标志位,如果不加处理,那么在下一次循环开始前,就无法捕获这个中断,故在异常处理中,在次设置中断标记位。

 

 

5.等待(wait)和通知(notify)

为了支持多线程之间的协作,JDK提供了两个非常重要的接口,线程等待wait()方法和线程通知方法notify()。这两个方法不是在Thread类中的,而是Object类的。这也意味着任何对象都能调用者这两种方法。

 
  1. public final void wait() throws InterruptException

  2. public final native void notify()

那wait()和notify()是怎么工作的呢?如果一个线程调用了object.wait()方法,那么他就会进入object对象的等待队列。这个队列中可能会有多个等待线程,因为系统运行同时等待某个对象。当object.notify()被调用时,他就会从这个等待队列中随机选择一个线程唤醒。这里希望大家注意,这个唤醒过程是不公平的,并不是先等待的线程会优先选择。

除了object.notify()之外还有object.notifyAll()方法,他会唤醒等待队列中的所有线程。

这里还需要注意一点,object.wait()方法和object.notify()方法并不是可以随便调用的。他必须包含在对应的synchronized语句中,无论是wait还是notify都需要首先获得目标对象的一个监视器。

这里给出简单实用wait和notify的案例:

 

 
  1. import java.util.Objects;

  2. public class fist{

  3. final static Object object = new Object();

  4.  
  5. public static class MyThread_1 extends Thread{

  6. @Override

  7. public void run(){

  8. synchronized (object) {

  9. System.out.println(System.currentTimeMillis()+"T1 start");

  10. try {

  11. System.out.println(System.currentTimeMillis()+"T1 wait");

  12. object.wait();

  13. } catch (InterruptedException e) {

  14. e.printStackTrace();

  15. }

  16. System.out.println(System.currentTimeMillis()+"T1 end");

  17. }

  18. }

  19. }

  20. public static class MyThread_2 extends Thread{

  21. @Override

  22. public void run(){

  23. synchronized (object) {

  24. System.out.println(System.currentTimeMillis()+"T2 start and notify");

  25. object.notify();

  26. try {

  27. Thread.sleep(2000);

  28. } catch (InterruptedException e) {

  29. e.printStackTrace();

  30. }

  31. }

  32. }

  33. }

  34. public static void main(String args[]){

  35. Thread t1 = new MyThread_1();

  36. Thread t2 = new MyThread_2();

  37. t1.start();

  38. t2.start();

  39. }

  40. }

上述代码中,开启了两个线程t1和t2。t1执行了object.wait()方法。注意,在wait方法执行前,t1申请了object对象锁。因此在执行object.wait()时,他说持有锁的。Wait()方法执行后,t1会进行等待,并且释放object对象的锁。t2在执行notify之前也会获得object对象锁,在notify执行后释放object对象的锁。

输出结果如下:

 

 

6.挂起(suspend)和继续执行(resume):

这两个操作是一对相反的操作,被挂起的线程必须等到resume()操作后才能继续执行。但如果仔细阅读文档后会发现,他们早已被标注为废弃方法,不推荐使用。

不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何资源锁。此时,任何线程想访问它占用的锁时,都会被牵连,无法正常运行。

如果,resume操作意外的再suspend前就执行了,那么被挂起的线程就很难有机会继续执行了。并且更严重的是他的锁并不会被释放,因此可能会导致整个系统无法正常工作。而且,对于被挂起的线程,从线程状态上看,居然还是runnable,这也会严重的影响到我们对系统当前状态的判断。

 
  1. import java.util.Objects;

  2. public class fist{

  3. final static Object object = new Object();

  4.  
  5. public static class MyThread extends Thread{

  6. public MyThread(String name){

  7. super.setName(name);

  8. }

  9. @Override

  10. public void run(){

  11. synchronized (object) {

  12. System.out.println("in "+getName());

  13. Thread.currentThread().suspend();

  14. }

  15. System.out.println("end "+getName());

  16. }

  17. }

  18. public static void main(String args[]) throws InterruptedException{

  19. Thread t1 = new MyThread("t1");

  20. Thread t2 = new MyThread("t2");

  21. t1.start();

  22. Thread.sleep(2000);

  23. t2.start();

  24. t1.resume();

  25. t2.resume();

  26. }

  27. }

输出:

这段代码的程序不会退出。而是会挂起。使用jstack命令打印系统线程信息可以看到。

这时我们需要注意,当前系统中,t2其实是被挂起的。但是他的线程状态是runnable,这很有可能对导致我们误判当前系统的状态。同时,虽然主程序已经调用了resume(),但是由于时间先后顺序的缘故,resume并没有生效!这就导致了线程t2被永久挂起,并且永久占用了对象object的锁。这对于系统来说极有可能是致命的。

没有输出”end t2”的原因是t2.resume()在t2.suspend()前就运行了,导致t2永远被挂起,如果把代码改写成如下,才会输出”end t2”。

 
  1. import java.util.Objects;

  2.  
  3. public class fist{

  4. final static Object object = new Object();

  5.  
  6. public static class MyThread extends Thread{

  7. public MyThread(String name){

  8. super.setName(name);

  9. }

  10. @Override

  11. public void run(){

  12. synchronized (object) {

  13. System.out.println("in "+getName());

  14. Thread.currentThread().suspend();

  15. }

  16. System.out.println("end "+getName());

  17. }

  18. }

  19. public static void main(String args[]) throws InterruptedException{

  20. Thread t1 = new MyThread("t1");

  21. Thread t2 = new MyThread("t2");

  22. t1.start();

  23. Thread.sleep(2000); //设置延迟

  24. t2.start();

  25. t1.resume();

  26. t2.resume();

  27. }

  28. }

输出:


 

 

7.等待线程结束(join)和谦让(yield):

 

很多时候,一个线程的输入可能会非常依赖另一个或多个线程的输出,此时,这个线程就需要等待依赖线程的执行完毕,才能继续执行。JDK提供了join()操作来实现这个功能,如下所示,显示了两个join()方法:

public final void join() throws InterruptedException

public final synchronized void join() throws InterruptedException

第一个join方法表示无限等待,他会一直阻塞线程,直到目标线程执行完毕。第二个给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会“等不及了”,而继续往下执行。

这里提供一个简单的join实例,以供参考:

 
  1. import java.util.Objects;

  2.  
  3. public class fist{

  4. public volatile static int i = 0;

  5. public static class MyThread extends Thread{

  6. @Override

  7. public void run(){

  8. for(i=0;i<10000000;i++);

  9. }

  10. }

  11.  
  12. public static void main(String args[]) throws InterruptedException{

  13. Thread t = new MyThread();

  14. t.start();

  15. t.join();

  16. System.out.println(i);

  17. }

  18. }

主函数中,如果不使用join等待MyThread线程,那么得到的i可能是0或者是一个很小的数值。因为MyThread还没开始执行,i的值就被打印出来了。但在使用join方法后,表示主线程愿意等待MytThread线程执行完毕,跟着MyThread一起往前走,故在join返回时,MyThread已经执行完毕,故i总是10000000。

另外一个比较有趣的方法,Thread.yield()。

这是一个静态方法,一旦执行,它会使当前线程让出cpu。但要注意让出cpu不代表不在执行了。当前线程在让出cpu后,还会进行cpu的资源争夺,但是能否被在次分配到,就不一定了。

如果你觉得一个线程不那重要,或者优先级非常低,而且又害怕它会占用太多的cpu资源,那么可以在适当的时候调用yield,给予其他重要线程更多工作机会。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值