• 一、线程的初步认识


          在上周去面试的时候,面试官问了我一个关于线程池的问题,当时没有答上来。所以,回来了之后决定重新学习一下多线程。
          这也是本人第一次写博客,之前一直想写,但是,一直没有搞清楚博客园的博客系统是怎么玩的。这次,到网上看到了一些关于博客园的小技巧,正好最近在学习多线程,拿来练练手。

    1.创建线程

     1 public class Demo {
     2     public static void main(String [] args){
     3         //方法一:通过继承Thread类,当调用start()方法时,就会启动一个线程去执行run方法里面的代码。这里直接使用内部类。
     4         new Thread(){
     5             public void run() {
     6                 System.out.println(Thread.currentThread().getName());
     7             };
     8         }.start();
     9 
    10         //方法二:实现Runnable接口
    11         Runnable r = new Runnable() {
    12             @Override
    13             public void run() {
    14                 System.out.println(Thread.currentThread().getName());
    15             }
    16         };
    17         new Thread(r).start();
    18 
    19         /**
    20          * Runnable的作用,实现多线程。我们可以定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程。
    21          * Thread 是一个类。Thread本身就实现了Runnable接口。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。使用Runnable接口可以实现代码重用。
    22          */
    23     }
    24 }

    Thread类run方法源码:

    public void run() {
        if (target != null) {
            target.run();
        }
    }

    当调用start()方法时,线程最终会调用Thread的run(),当我们继承Thread类复写run(),线程会调用run()方法里的代码。

    而在Thread的run()方法,如果target不为null,就执行target.run(),不用说这里target就是Runnable对象。

    线程的5种状态:

    新建状态(new):线程对象被创建后,就进入了新建状态。

    就绪状态(Runnable):可执行状态,随时可能被cpu调度执行。当调用Thread的start()方法后线程进入就绪状态。

    运行状态(Running):线程被cpu调度运行。线程只能从就绪状态进入运行状态。

    阻塞状态(Blocked):线程因为某种原因放弃cpu使用权,暂时停止运行。知道线程再次进入就绪状态,才能被cpu调度运行。

      -等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。

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

      -其它阻塞。

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

    这5种状态涉及到的内容包括Object类, Thread和synchronized关键字。
    Object类,定义了wait(), notify(), notifyAll()等休眠/唤醒方法。
    Thread类,定义了一些列的线程操作方法。例如,sleep()休眠方法, interrupt()中断方法, getName()获取线程名称等。
    synchronized,是关键字;它区分为synchronized代码块和synchronized方法。synchronized的作用是让线程获取对象的同步锁。

    2.线程安全

     1 public class Demo2 {
     2     private int value = 1;
     3 
     4     public int getNext(){
     5         return value++;
     6     }
     7 
     8     public static void main(String[] args) {
     9         final Demo2 demo = new Demo2();
    10         Runnable r = new Runnable() {
    11             @Override
    12             public void run() {
    13                 System.out.println(demo.getNext());
    14             }
    15         };
    16         /**
    17          * 当少量线程同时访问getNext()方法时,程序不会有什么问题。
    18          * 但是如果并发量较大(这里模拟500个线程同时访问),得到结果往往不是想要的结果。
    19          */
    20         for(int i = 0 ;i <500;i++){
    21             new Thread(r).start();
    22         }
    23     }
    24 
    25 }

    出现问题的原因是:value++不是一个原子操作,或者说该操作不具备原子性。
    原子性:原子是世界上最小的单位,具有不可分割性。在我们编程的世界里,某个操作如果不可分割我们就称之为该操作具有原子性。i=0不可能再分割,所以该操作具有原子性。但是getNext()方法的value++,是可以分割的,这实际上有个“读取-修改-写入”的操作序列。如果一个操作具有原子性,那也就不会有线程安全问题。做完了就做完了!但是一个操作不具有原子性,那么,在这个操作在执行的过程中,有可能被外部所改变。

    解析:这个类有一个字段value,并且提供了可改变字段的方法,所以该对象的状态是可变的。上面程序多个线程同时访问getNext()方法时。如果线程是按顺序执行,正确的执行方式是第一条线程读取value的值并打印出来,然后对齐值进行自增操作。然后下一条线程再读取value值,接着打印、自增...,结果值应该是 1、2、3、4、5...500  按顺序打印出来,我们希望的结果也是这样。但是,多线程之间的操作是交替执行的,当第一个线程读取value值时,第二个线程也有可能在做读取value值的操作,第三个也可能做着同样的操作。这样,各个线程之间得到的值就有可能相同,而不是理想中按照顺序对value的值进行递增。

    解决办法:在run方法上加synchronized(当然也可以在getNext方法上加,只是锁对象不一样而已)

     1 public class Demo2 {
     2     private int value = 1;
     3 
     4     public int getNext(){
     5         return value++;
     6     }
     7 
     8     public static void main(String[] args) {
     9         final Demo2 demo = new Demo2();
    10         Runnable r = new Runnable() {
    11             @Override
    12             public synchronized void run() {
    13                 System.out.println(demo.getNext());
    14             }
    15         };
    16         /**
    17          * 当少量线程同时访问getNext()方法时,程序不会有什么问题。
    18          * 但是如果并发量较大(这里模拟500个线程同时访问),得到结果往往不是想要的结果。
    19          */
    20         for(int i = 0 ;i <500;i++){
    21             new Thread(r).start();
    22         }
    23     }
    24 
    25 }

    Thread 和 Runnable 的相同点:都是“多线程的实现方式”。
    Thread 和 Runnable 的不同点:
    Thread 是类,而Runnable是接口;Thread本身是实现了Runnable接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。
    此外,Runnable还可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。
    通常,建议通过“Runnable”实现多线程!

  • 相关阅读:
    Scrapy shell调试返回403错误
    android 获取 imei号码
    查找目录下的所有文件中是否含有某个字符串
    自动生成和配置ES的安全证书
    docker构建本地私有镜像
    ELK容器化部署
    Rancher使用基础知识1
    jenkins自动打包部署流水线
    ansible管理windows主机
    jenkins集成gitlab自动构建打包
  • 原文地址:https://www.cnblogs.com/futiansu/p/5611770.html
Copyright © 2020-2023  润新知