• JAVA多线程基础


    一:线程与进程

    1 线程:进程中负责程序执行的执行单元
    线程本身依靠程序进行运行
    线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

    2 进程:执行中的程序
    一个进程至少包含一个线程

    3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程

    4 多线程:在一个程序中运行多个任务
    目的是更好地使用CPU资源

    二:线程的实现

    1:继承Thread类

      

     1 package Threads;
     2 
     3 public class Thread_one extends Thread{
     4     
     5     @Override
     6     public void run() {
     7         // TODO Auto-generated method stub
     8         System.out.println(Thread.currentThread().getName());
     9     }
    10 }

    我们继承Thread类,然后重写run()方法,写入你要执行的操作;

    线程了类是创建好了,下面就是使用这个线程类:

    1 package Threads;
    2 
    3 public class Test1 {
    4     public static void main(String[] args) {
    5         for (int i = 0; i < 10; i++) {
    6             new Thread_one().start();
    7         }
    8     }
    9 }

    正确的启动线程的方法是start()方法,而不是使用run()方法,下面具体说明 ;

    运行的结果:

      

    我们可以发现多次运行结果是不一样的:

    下面我们把启动改为run()试一试;

    1 package Threads;
    2 
    3 public class Test1 {
    4     public static void main(String[] args) {
    5         for (int i = 0; i < 10; i++) {
    6             new Thread_one().run();
    7         }
    8     }
    9 }

     多次运行发现结果都是一样的,发现运行的都是main这个主线程,而没有创建新的线程:

      总结 run()和start()的区别:

      ①:首次正确启动一个线程是使用start()方法;

      ②:run()方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

      ③:start()在调用start()方法后,线程就进入了就绪的状态,其实也就是一个可运行的状态,什么时候运行是根据cpu的调度的,看这个线程是否抢到了cpu的资源,所以说我们执行多个线程多次执行的顺序结果是不一样的;

     2:实现Runnable

     1 package Threads;
     2 
     3 public class Thread_two implements Runnable{
     4 
     5     @Override
     6     public void run() {
     7         // TODO Auto-generated method stub
     8         System.out.println(Thread.currentThread().getName());
     9     }
    10 
    11 }

    继承Thread和实现Runnable实现一个线程过程都差不多;

    调用启动有些区别:

     1 package Threads;
     2 
     3 public class Test2 {
     4 
     5     public static void main(String[] args) {
     6         // TODO Auto-generated method stub
     7         for (int i = 0; i < 10; i++) {
     8             new Thread(new Thread_two()).start();
     9         }
    10     }
    11 }

    我们查看Thread源码发现,他需要一个Runnable类型的对象;

     

    我们附给他我们实现Runnable接口的对象

    运行发现实现了效果:

    总结:

    实现Runnable接口比继承Thread类所具有的优势:

    1):可以避免java中的单继承的限制

     四:线程的不同状态及转换:

     

    1、新建状态(New):新创建了一个线程对象。
    2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    解析:

    新建状态:

     

    只是创建了这个线程对象。

     就绪状态:

    该状态的线程位于可运行线程池中,变得可运行,等争抢到cpu资源后就才会真正的进入到可运行的状态;

     运行状态:就绪状态的线程获取了CPU,执行程序代码。

     阻塞状态:

     ①:wait();

     

    只能用 notify唤醒 或者notifyall

    ②:sleep

    定义毫秒数,到时间自动唤醒,继续执行任务;

     ③:yield

     线程的礼让,让正在执行的线程变成可运行状态,然后在重新争抢cpu。

    yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

     ④:join加入主线程;

    让正在运行的主线程停止,然后先运行这个线程。

     1 package Threads;
     2 
     3 public class Test3 {
     4     public static void main(String[] args) {
     5             System.out.println("开始:");
     6             Thread3 t2=new Thread3();
     7             Thread t=new Thread(t2);
     8             t.start();
     9             System.out.println("结束:");
    10     }
    11 }

     

    这明显不是我们想要的结果:

     使用join加入主线程:

     1 package Threads;
     2 
     3 public class Test3 {
     4     public static void main(String[] args) throws InterruptedException {
     5             System.out.println("开始:");
     6             Thread3 t2=new Thread3();
     7             Thread t=new Thread(t2);
     8             t.start();
     9             t.join();
    10             System.out.println("结束:");
    11     }
    12 }

     在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

     

    总结:

    sleep和wait的区别:

    1. 所属的层面不同:Thread类的方法:sleep() Object的方法:wait()。

    2.wait停止的线程则需要使用notifyall()和notifi()唤醒;
    3. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

    4. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 ;

    5. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

    五:线程常用的方法:

      sleep(): 强迫一个线程睡眠N毫秒。 
      isAlive(): 判断一个线程是否存活。 
      join(): 等待线程终止。 
      activeCount(): 程序中活跃的线程数。 
      enumerate(): 枚举程序中的线程。 
      currentThread(): 得到当前线程。 
      isDaemon(): 一个线程是否为守护线程。 
      setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
      setName(): 为线程设置一个名称。 
      wait(): 强迫一个线程等待。 
      notify(): 通知一个线程继续运行。 
      setPriority(): 设置一个线程的优先级。

    六:线程的同步:

    常用的同步方法就是使用synchronized关键字

    为什么要使用线程同步,因为当一个资源可能会有多个线程访问的时候,当第一个线程进来之后对资源进行了修改,但是在修改的过程中第二个线程也进来了,那第二个线程取到的数据就不是最新的数据,是个虚假的数据,那么这个线程所执行的操作都没有意义了;

    看个小例子:

     1 package Threads;
     2 
     3 public class Thread3 implements Runnable{
     4     private static int a=10;
     5     @Override
     6     public void run() {
     7         // TODO Auto-generated method stub
     8         try {
     9             Tong();
    10         } catch (InterruptedException e) {
    11             // TODO Auto-generated catch block
    12             e.printStackTrace();
    13         }
    14     }
    15     public static  void Tong() throws InterruptedException{
    16         for (int i = 0; i < 10; i++) {
    17             System.out.println(Thread.currentThread().getName()+"||||"+(--a));
    18             Thread.sleep(1000);
    19     }
    20     }
    21 }
    22 
    23 
    24 
    25 
    26 package Threads;
    27 
    28 public class Test3 {
    29     public static void main(String[] args) throws InterruptedException {
    30             System.out.println("开始:");
    31             Thread3 t2=new Thread3();
    32             Thread t=new Thread(t2);
    33             Thread t3=new Thread(t2);
    34             t.start();
    35             t3.start();
    36             t.join();
    37             t3.join();
    38             System.out.println("结束:");
    39     }
    40 }

    里面的sleep就是模拟让两个线程同时访问一个资源

    运行结果为:

    我们可以看到是有重复数据的,也就是虚假的数据   thread-0和thread-1同时访问了count:7这个数据;

    解决办法就是使用同步方法:

     1 package Threads;
     2 
     3 public class Thread3 implements Runnable{
     4     private static int a=10;
     5     @Override
     6     public void run() {
     7         // TODO Auto-generated method stub
     8         try {
     9             Tong();
    10         } catch (InterruptedException e) {
    11             // TODO Auto-generated catch block
    12             e.printStackTrace();
    13         }
    14     }
    15     public static synchronized void Tong() throws InterruptedException{
    16         for (int i = 0; i < 10; i++) {
    17             System.out.println(Thread.currentThread().getName()+"||||"+(--a));
    18             Thread.sleep(1000);
    19     }
    20     }
    21 }

    加上synchronized 关键字

    总结:其实同步就是对这个资源对象就行加锁,只能同时有一个线程能操作这个资源,如果有第二个线程要访问,那只有等第一个线程执行结束之后才能进行访问;

     七:实现经典多线程例子生产者,消费者:

    首先创建商品类

     1 package Threads;
     2 
     3 public class Piao {
     4     private int count=5;
     5     
     6     public synchronized void sheng() throws InterruptedException{
     7         if(count>=10){
     8             System.out.println("商品过多:"+count);
     9             wait();
    10         }else{
    11             System.out.println("商品生产成功:"+(++count));
    12             notify();
    13         }
    14     }
    15     public synchronized void mai() throws InterruptedException{
    16         if(count<=0){
    17             System.out.println("商品库存不够:"+count);
    18             wait();
    19         }else{
    20             System.out.println("商品卖出成功:"+(--count));
    21             notify();
    22         }
    23     }
    24 }

     在创建生产者和消费者:

     1 package Threads;
     2 
     3 public class Sheng implements Runnable{
     4     private Piao p;
     5     
     6     public Sheng(Piao p) {
     7         super();
     8         this.p = p;
     9     }
    10     @Override
    11     public void run() {
    12         // TODO Auto-generated method stub
    13         for (int i = 0; i < 10; i++) {
    14             try {
    15                 p.sheng();
    16             } catch (InterruptedException e) {
    17                 // TODO Auto-generated catch block
    18                 e.printStackTrace();
    19             }
    20         }
    21     }
    22 }
    23 
    24 
    25 
    26 
    27 package Threads;
    28 
    29 public class Mai implements Runnable{
    30     private Piao p;
    31     
    32     public Mai(Piao p) {
    33         super();
    34         this.p = p;
    35     }
    36     @Override
    37     public void run() {
    38         // TODO Auto-generated method stub
    39         for (int i = 0; i < 10; i++) {
    40             try {
    41                 p.mai();
    42             } catch (InterruptedException e) {
    43                 // TODO Auto-generated catch block
    44                 e.printStackTrace();
    45             }
    46         }
    47     }
    48 }

     测试类:

     1 package Threads;
     2 
     3 public class Test4 {
     4 
     5     public static void main(String[] args) {
     6         // TODO Auto-generated method stub
     7         Piao p=new Piao();
     8         new Thread(new Mai(p)).start();
     9         new Thread(new Sheng(p)).start();
    10     }
    11 
    12 }

     运行结果为:

  • 相关阅读:
    S5P4418iNand清空方法
    使用 GIT 获得Linux Kernel的代码并查看,追踪历史记录
    Linux3.4内核的基本配置和编译
    uboot---linux
    TestNG的简单使用
    java selenium webdriver处理JS操作窗口滚动条
    testNG入门详解
    零成本实现接口自动化测试 – Java+TestNG 测试Restful service
    Selenium Webdriver——操作隐藏的元素(二)display属性
    python selenium webdriver处理浏览器滚动条
  • 原文地址:https://www.cnblogs.com/GH0522/p/8177336.html
Copyright © 2020-2023  润新知