• java多线程(一)


    进程与线程

    进程是操作系统结构的基础,是一次程序的执行;简单理解,进程就是一个window操作系统中运行的exe程序。

    线程可以理解为进程中独立运行的子任务。例如,QQ.exe程序运行时,可以有很多的子任务同时运行,下载文件线程、文字聊天线程、好友视频线程等。

    多线程的优点

    同时处理多个任务可以最大限度地利用CPU资源,减少等待时间。

    注意:多线程是异步的,线程被调用的时机是随机的。

    线程状态

    新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

    就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

    运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就     绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

    阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

    2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

    3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    实现多线程编程的方式

    1继承Thread类(Thread类实现了Runnable接口)

    1 public class MyThread extends Thread {
    2     @Override
    3     public void run() {
    4         super.run();
    5         System.out.println("MyThread");
    6     }
    7 }
    1 public class MyRun {
    2     public static void main(String[] args) {
    3         MyThread myThread = new MyThread();
    4         myThread.start();
    5         System.out.println("end");
    6     }
    7 }
    运行结果:
      end
      MyThread

    上例可看出,多线程技术中代码的运行结果与代码的执行顺序或调用顺序无关。

    那么问题来了,对同一个线程,多次调用start()方法,为出现什么现象?

    1 public class MyRun {
    2     public static void main(String[] args) {
    3         MyThread myThread = new MyThread();
    4         myThread.start();
    5         myThread.start();
    6         System.out.println("end");
    7     }
    8 }

    运行结果如下:

    MyThread
    Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:705)
    at mult_thread.MyRun.main(MyRun.java:11)

    第一次调用start方法,线程正确执行,第二次调用start方法报错IllegalThreadStateException。

    线程的随机性

     1 public class MyThread extends Thread {                              
     2     @Override                                                       
     3     public void run() {                                             
     4         for (int i = 0 ; i < 5 ; i++){                              
     5             int sleepTime = (int) (Math.random()*1000);             
     6             try {                                                   
     7                 Thread.sleep(sleepTime);                            
     8             } catch (InterruptedException e) {                      
     9                 e.printStackTrace();                                
    10             }                                                       
    11             System.out.println(Thread.currentThread().getName());   
    12         }                                                           
    13     }                                                               
    14 }                                                                   
     1 public class MyRun {
     2     public static void main(String[] args) {
     3         MyThread myThread = new MyThread();
     4         myThread.setName("myThread");
     5         myThread.start();
     6         for (int i = 0 ; i < 5 ; i++){
     7             int sleepTime = (int) (Math.random()*1000);
     8             try {
     9                 Thread.sleep(sleepTime);
    10             } catch (InterruptedException e) {
    11                 e.printStackTrace();
    12             }
    13             System.out.println(Thread.currentThread().getName());
    14         }
    15         System.out.println("end");
    16     }
    17 }

    运行结果

    myThread
    myThread
    main
    myThread
    myThread
    main
    myThread
    main
    main
    main
    end

    Thread类中的start方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run方法。让系统安排一个时间来调用run方法。

    那么问题来了?若是不通过start方法,而是直接调用run方法会怎样呢?

     1 public class MyRun {
     2     public static void main(String[] args) {
     3         MyThread myThread = new MyThread();
     4         myThread.setName("myThread");
     5         myThread.run(); //注意直接调用run
     6         for (int i = 0 ; i < 5 ; i++){
     7             int sleepTime = (int) (Math.random()*1000);
     8             try {
     9                 Thread.sleep(sleepTime);
    10             } catch (InterruptedException e) {
    11                 e.printStackTrace();
    12             }
    13             System.out.println(Thread.currentThread().getName());
    14         }
    15         System.out.println("end");
    16     }
    17 }

    运行结果:

    main
    main
    main
    main
    main
    main
    main
    main
    main
    main
    end

    此时只有一个线程运行,那就是main线程。这里是同步执行,而不是异步的,也就是说代码会顺序执行。这里看不出来,小改一下run方法就可以看出来

    System.out.println(Thread.currentThread().getName());   
    //改成
    System.out.println("run="+Thread.currentThread().getName());   

    运行结果

     1 run=main
     2 run=main
     3 run=main
     4 run=main
     5 run=main
     6 main
     7 main
     8 main
     9 main
    10 main
    11 end

    看出来了吧!

    2实现Runna接口

    1 public class MyRunnable implements Runnable {
    2     @Override
    3     public void run() {
    4         System.out.println("运行中!");
    5     }
    6 }
    1 public class MyTest {
    2     public static void main(String[] args) {
    3         Runnable runnable = new MyRunnable();
    4         Thread thread = new Thread(runnable);
    5         thread.start();
    6         System.out.println("end");
    7     }
    8 }

    运行结果

    end
    运行中!

    通过实现Runna接口的方式实现多线程编程,是将一个Runnable实例作为一个target传给Thread对象,再通过调用Thread的start方法实现。

    那么问题来了,实现runnable接口比继承Thread类的方式有什么好处?

    java是单继承的,多实现的,如果采用继承Thread类的方式实现多线程,那么这个类就不可以继承其他的类了,这并不是人们所期待的,为了改变这种局限性,可以使用实现接口的方式。

    注意:Thread类实现了Runnable接口,是不是也就意味着构造函数Thread(Runnable target)不仅可以传入Runnable接口对象,还可以传入Thread类的对象。也就是说,可以将一个Thread对象中的run方法交由其他线程进行调用。

    1 public class MyTest {
    2     public static void main(String[] args) {
    3         //Runnable runnable = new MyRunnable();
    4         Thread myThread = new MyThread(); //这里是上文的MyThread类
    5         Thread thread = new Thread(myThread,"myThread");
    6         thread.start();
    7         System.out.println("end");
    8     }
    9 }

    构造函数Thread(Runnable target,String name),运行结果:

    1 end
    2 run=myThread
    3 run=myThread
    4 run=myThread
    5 run=myThread
    6 run=myThread

    实例变量与线程安全

    自定义线程类中,实例变量针对其他线程可以有共享与不共享之分。这是一个很重要的技术

    数据不共享的情况

     1 public class MyThread extends Thread {
     2 
     3     private int count = 5;
     4     public MyThread(String name){
     5         super();
     6         this.setName(name);//设置线程名字
     7     }
     8     @Override
     9     public void run() {
    10         super.run();
    11         while (count>0){
    12             count--;
    13             System.out.println("由"+Thread.currentThread().getName()+"计算:"+"count="+count);
    14         }
    15     }
    16 }
     1 public class MyRun {
     2     public static void main(String[] args) {
     3         MyThread a = new MyThread("A");
     4         MyThread b = new MyThread("B");
     5         MyThread c = new MyThread("C");
     6         a.start();
     7         b.start();
     8         c.start();
     9         System.out.println("end");
    10     }
    11 }

    运行结果

     1 end
     2 由B计算:count=4
     3 由C计算:count=4
     4 由A计算:count=4
     5 由A计算:count=3
     6 由C计算:count=3
     7 由B计算:count=3
     8 由C计算:count=2
     9 由A计算:count=2
    10 由A计算:count=1
    11 由C计算:count=1
    12 由B计算:count=2
    13 由C计算:count=0
    14 由A计算:count=0
    15 由B计算:count=1
    16 由B计算:count=0

    如果想实现三个线程对同一个count变量进行减法操作,该如何设计呢?

     1 public class MyThread extends Thread {
     2 
     3     private int count = 5;
     4     @Override
     5     public void run() {
     6         super.run();
     7         while (count>0){
     8             count--;
     9             System.out.println("由"+Thread.currentThread().getName()+"计算:"+"count="+count);
    10         }
    11     }
    12 }
     1 public class MyRun {
     2     public static void main(String[] args) {
     3         MyThread myThread = new MyThread();
     4         Thread a = new Thread(myThread,"a");
     5         Thread b = new Thread(myThread,"b");
     6         Thread c = new Thread(myThread,"c");
     7         a.start();
     8         b.start();
     9         c.start();
    10         System.out.println("end");
    11     }
    12 }

    运行结果

    1 end
    2 由b计算:count=3
    3 由b计算:count=1
    4 由b计算:count=0
    5 由a计算:count=3
    6 由c计算:count=2

    结果表明,不仅顺序是乱的,而且当count值为3时,a和b同时对count值进行处理。非线程安全问题产生了~

    在某些JVM中,i--并不是原子的,分为三步:1取得原有的i值;2计算i-1;3对i进行赋值。

    在上述三个步骤中,如果有多个线程同时访问,就会出现非线程安全问题。

    解决办法:在run方法前加synchronized关键字,其他不变

     1 public class MyThread extends Thread {
     2 
     3     private int count = 5;
     4     @Override
     5     public synchronized void run() {
     6         super.run();
     7         while (count > 0) {
     8             count--;
     9             System.out.println("由" + Thread.currentThread().getName() + "计算:" + "count=" + count);
    10         }
    11         System.out.println("由" + Thread.currentThread().getName() + "打印:for循环之后");
    12     }
    13 }

    运行结果

    1 end
    2 由a计算:count=4
    3 由a计算:count=3
    4 由a计算:count=2
    5 由a计算:count=1
    6 由a计算:count=0
    7 由a打印:for循环之后
    8 由c打印:for循环之后
    9 由b打印:for循环之后

    加synchronized关键字后,使多个线程在处理run方法时,以排队的方式进行处理。synchronized可以在任意对象或方法上加锁,而加锁的这段代码称为“互斥区”或者“临界区”。

    那么问题来了,当线程a进来持有锁后,线程b和c就等待,直到a运行完所有代码后,释放锁。这时b或c线程进来发现count的值等于0,不进while循环。相当于a完成减所有操作,b和c只能看着。怎么猜才能让a b c三个线程协作呢?

    一个简单解决办法:一个线程执行一次减操作后,让其释放锁并等待,让其他线程继续操作。

     1 public class MyThread extends Thread {
     2 
     3     private int count = 5;
     4     @Override
     5     public synchronized void run() {
     6         super.run();
     7         while (count > 0) {
     8             count--;
     9             System.out.println("由" + Thread.currentThread().getName() + "计算:" + "count=" + count);
    10             try {
    11                 wait(100);
    12             } catch (InterruptedException e) {
    13                 e.printStackTrace();
    14             }
    15         }
    16         System.out.println("由" + Thread.currentThread().getName() + "打印:for循环之后");
    17     }
    18 }

    运行结果

    1 end
    2 由a计算:count=4
    3 由b计算:count=3
    4 由c计算:count=2
    5 由a计算:count=1
    6 由b计算:count=0
    7 由c打印:for循环之后
    8 由a打印:for循环之后
    9 由b打印:for循环之后

    wait(100)就是让线程释放锁,并等待0.1s后,自动唤醒。

    currentThread()方法

    该方法可返回代码段正在被哪个线程调用的信息

    区别Thread.currentThread().getName()和this.getName()

    isAlive()方法

    判断当前线程是否处于活动状态。

    什么是活动状态?线程处于正在运行或者准备运行的状态。

    sleep()方法

    静态方法,Thread.sleep(long times)毫秒

    让正在执行的线程休眠(暂停执行),正在执行的线程是指this.currentThread()返回的线程

    getId()方法

    取得线程的唯一标识

    停止线程

    终止正在运行的线程的三种方法:

    使用对出标识,使线程正常退出,也就是当run方法运行完成后线程终止。

    使用stop()方法强行终止,该方法已被废弃,不推荐使用。

    使用interrupt方法中断线程(配合异常使用)

     1 public class MyThread extends Thread {
     2 
     3     private int count = 5;
     4     @Override
     5     public void run() {
     6         super.run();
     7         for (int i = 0 ; i < 1000 ; i++){
     8             System.out.println("i = " + (i+1));
     9         }
    10     }
    11 }
     1 public class MyRun {
     2     public static void main(String[] args) {
     3         MyThread myThread = new MyThread();
     4         myThread.start();
     5         try {
     6             Thread.sleep(10);
     7             myThread.interrupt();
     8             //System.out.println(myThread.interrupted());
     9             System.out.println(myThread.isInterrupted());
    10 
    11         } catch (InterruptedException e) {
    12             e.printStackTrace();
    13         }
    14         System.out.println("end");
    15     }
    16 }

    运行结果

    1 i = 0
    2 ....
    3 i = 892
    4 true
    5 end
    6 i = 893
    7 ...
    8 i = 1000

    interrupt方法后,线程并不是真的停止,而是在当前线程中打一个停止标记。所以for循环不会中断。

    注意isInterrupted()方法和interrupted()方法的区别,上述例子中,使用isInterrupted()方法返回true,而使用interrupted()方法返回false,why?

    isInterrupted():成员函数,myThread.isInterrupted()测试myThread线程是否中断

    interrupted():静态方法,测试当前线程是否中断

    上例中,interrupted()表示的是main线程,当然false,而isInterrupted()表示myThread线程,因为myThread调用了interrupt方法,标记为true。

    注意,问题来了!

    未完待续....

  • 相关阅读:
    【华为云技术分享】华为云HiLens全面升级,端云协同多模态AI应用开发利器
    【华为云技术分享】MongoDB经典故障系列四:调整oplog大小,引起从库宕机怎么办?
    使用体验神似VS Code?三步带你了解华为云CloudIDE前世今生
    nginx 下配置禅道
    如何在HTTPS 网页中引入HTTP资源: Mixed Content?
    stocket和webstocket的区别
    [转载]input[type=file]在移动端各浏览器无法适配打开相机的问题。
    视频和图片的存储(阿里云OSS还是七牛?)
    VUE 和AngularJS 开发SPA能支持APP内嵌么
    【PHP7.1】使用OpenSSL来代替Mcrypt加解密【原创】
  • 原文地址:https://www.cnblogs.com/ouym/p/6991816.html
Copyright © 2020-2023  润新知