www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

并发基础知识,Java并发编制程序基础www.2527.com

2019-05-24 23:04 来源:未知

写在前面的话

从本篇文章开始,我将开始并发编程系列文章,线程是并发编程的基础,理解线程的基本概念和线程状态的转换是并发编程最基本的要求。

1、在Java中什么是进程,什么是线程

从这篇文章开始就正式进入了并发主题,该主题相关知识较前面的集合主题会比较晦涩难懂,需要不断回顾、整理,才能构建出自己的知识网络。

一、进程与线程

  • 进程:进程是程序执行的一个实例,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

首先推荐一本讲并发的好书:JAVA并发编程实战。这本书非常完整的讲解了关于并发的知识点,是一本不可多得的好书,如果有时间一定要多看几遍。

进程是内存中运行的应用程序,每个进程有自己独立的内存空间,一个进程包含了多个线程,所以线程又成为轻量级进程,这些线程有自己的计数器、栈空间和、局部变量等实现,而各个线程又共享进程的内存和资源。
进程是操作系统进行资源分配和调度的单位,而线程是是CPU调度和分派的基本单位,CPU通过对线程的高速切换让我们感觉这些线程在同时进行。

在java中,进程大致等于JVM,线程属于JVM,线程可以被java应用程序在运行时动态的创建和停止。

下面放一张并发主题的思维导图(受限于网页大小,请童鞋们自行下载图片浏览):

每个Java进程从main方法开始执行,这个时候启动的线程就是我们常说的主线程。其实在我们运行Java进程的时候,jvm给我们创建了很多其他线程,比如gc线程。我们可以通过ThreadMxBean查看线程的具体信息。

2、Java程序中至少有几个线程?
每个java程序在main线程内执行,因此每个java程序至少有一个线程

www.2527.com 1

public class MultiThreads {

    public static void main (String[] args) {
        //获取线程管理类
        ThreadMXBean mThreadMXBean = ManagementFactory.getThreadMXBean();
        //获取所有的线程
        ThreadInfo[] threadInfos = mThreadMXBean.dumpAllThreads(true, true);

        if (threadInfos != null && threadInfos.length > 0) {
            for (ThreadInfo ti : threadInfos) {
                System.out.println(String.format("[%s] %s", ti.getThreadId(), ti.getThreadName()));
            }
        }
    }

}
运行结果:
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main

3、每一个Java线程都有哪些属性

从这张图中可以看到并发相关的知识点非常多并且非常杂,我们这个主题不可能把这里边所有罗列的知识点全部深入讲解一遍,所以我在这里只能挑选一些相对比较重要的知识点(大家可以认为是主干知识 : ))进行深入分析,至于剩下的一些枝枝叶叶就需要童鞋们自己去实践掌握啦。

二、如何创建并运行Java线程

  • 线程的标识符id
  • 线程的名称
  • 线程优先级
  • 线程的状态
  • 线程所属的线程组

OK,介绍就到这里,接下来正式开始知识点剖析,当然首先从基础知识开始(图中右下角线程部分)。

在Java中有两种方式创建线程:
1、继承Thread并覆写父类的run方法
2、实现Runnable接口

4、线程有哪些状态

 

启动线程都是执行Thread的start方法

  • NEW:初始状态,线程被构建,但是还没有调用start方法
  • RUNNABLE:运行状态,Java线程将被操作系统中的就绪和运行两种状态笼统的称作运行中
  • BLOCKED:阻塞状态,表示线程阻塞于锁
  • WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其它线程做出一些特定的动作(通知或中断)
  • TIME_WAITING: 超时等待状态,该状态不同于WAITING,它可以在指定的时间内自行返回的
  • TERMINATED:终止状态,表示当前线程已经执行完毕

并发基础知识

public class ThreadInit {

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

        //通过继承Thread类的方式新建线程
        Thread t1 = new Thread(){
            @Override
            public void run () {
                System.out.println("继承Thread类的线程运行中..");
            }

        };

        //通过实现Runnable接口的方式新建线程
        Thread t2 = new Thread(new MyRunnable());

        //多线程执行开始,实际是调用的run方法
        t1.start();
        t2.start();

        //主线程等待直到子线程执行完毕
        t1.join();
        t2.join();

        System.out.println("主线程运行完成..");

    }

    static final class MyRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("实现Runnable接口的线程运行中..");
        }
    }

}
运行结果:
继承Thread类的线程运行中..
实现Runnable接口的线程运行中..
主线程运行完成..

JDK安装目录下bin目录下执行命令jstack工具可以查看代码运行时的线程信息。

这部分知识是相对来说比较简单并且容易理解的,我从这里开始写也是为了巩固童鞋们学习并发知识的信心,让大家能够更有动力的去学习。

对于这两种方式哪种好并没有一个确定的答案,它们都能满足要求。就我个人意见,我更倾向于实现Runnable接口这种方法。因为线程池可以有效的管理实现了Runnable接口的线程,如果线程池满了,新的线程就会排队等候执行,直到线程池空闲出来为止。而如果线程是通过实现Thread子类实现的,这将会复杂一些。(关于线程池的原理,后续会专门介绍)

5、线程的创建方式

 

三、线程的状态

  • 继承Thread类创建线程类
  • 实现Runnable接口

1.什么是线程?什么是进程?它们之间有何联系?

通过查看Thread.State可以知道线程一共有6个状态:
1、NEW - 新建,表示线程构建了但没有调用start方法
2、RUNNABLE - 运行/就绪,线程就绪和运行统称为‘运行中’
3、BLOCKED - 阻塞,表示线程阻塞于锁
4、WAITING - 等待,表示线程进入等待状态,需要被其他线程通知或者中断
5、TIME_WAITING - 超时等待
6、TERMINATED - 终止,表示线程的run方法执行完毕
其中4和5属于一种类型,差别就是5指定了等待的最长时间,超时后状态会自动恢复。

一般实现Runnable接口的方式更好一些,关于两者的对比,Thread和Runnable的区别

关于这个问题,我准备用一个形象的例子来描述,这样比较容易理解:大家都有用过杀毒软件吧?当你打开杀毒软件,那么你就启动了一个进程。而现代的杀毒软件功能已不仅仅局限于查杀病毒了,比如还有清理垃圾文件、工具箱、修复漏洞、扫描驱动等等功能,而这一个个功能对应的其实就可以认为是一个个线程在执行任务(当然实际情况可能更复杂)。

public class ThreadStates {

    private static final Object lock = new Object();

    private static final Object lock2 = new Object();

    public static void main (String[] args) {

        Thread timeWaiting = new Thread(new TimeWaiting(), "TIME_WAITING_THREAD");
        timeWaiting.start();

        Thread waiting = new Thread(new Waiting(), "WAITING THREAD");
        waiting.start();

        //如下两个线程中,一个线程获取了锁未释放,另外一个线程阻塞于锁
        Thread blocked1 = new Thread(new Blocked(), "BLOCING_THREAD1");
        blocked1.start();
        Thread blocked2 = new Thread(new Blocked(), "BLOCING_THREAD2");
        blocked2.start();

    }
    //调用sleep(long)进入超时等待
    static final class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //调用wait方法进入等待状态
    static final class Waiting implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //一个线程获取锁之后,未释放锁时其他线程阻塞于锁
    static final class Blocked implements Runnable {

        @Override
        public void run() {
            synchronized (lock2) {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}
通过jps查看java pid,使用jstack pid查看线程状态:
"BLOCING_THREAD2" #14 prio=5 os_prio=0 tid=0x0000000018ba9000 nid=0x256c waiting for monitor entry [0x0000000019a7e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.orange.threads.ThreadStates$Blocked.run(ThreadStates.java:64)
        - waiting to lock <0x00000000d5d8b008> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"BLOCING_THREAD1" #13 prio=5 os_prio=0 tid=0x0000000018ba8800 nid=0x1ff4 waiting on condition [0x000000001997f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.orange.threads.ThreadStates$Blocked.run(ThreadStates.java:64)
        - locked <0x00000000d5d8b008> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"WAITING THREAD" #12 prio=5 os_prio=0 tid=0x0000000018ba5800 nid=0x187a0 in Object.wait() [0x000000001987f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d8aff8> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.orange.threads.ThreadStates$Waiting.run(ThreadStates.java:49)
        - locked <0x00000000d5d8aff8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"TIME_WAITING_THREAD" #11 prio=5 os_prio=0 tid=0x0000000018ba3000 nid=0x207e0 waiting on condition [0x000000001977f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.orange.threads.ThreadStates$TimeWaiting.run(ThreadStates.java:36)
        at java.lang.Thread.run(Thread.java:745)

6、线程为什么不能通过Stop方法停止
stop方法过于暴力,会直接终止线程,并且立即释放这个线程所持有的锁,而这些锁是用来维持对象一致性的,如果此时,写线程写入的数据正写入一半,并强行终止,那么对象就会被写坏,同时由于锁已经被释放,另外一个等待该锁的读线程就顺利成章的读到了这个不一致的问题。 --- 《Java高并发程序设计》. 并行程序基础

从上面这个例子可以得出结论:进程是正在运行的程序的实例,而线程是程序中一个单一的顺序控制流程,它是程序执行流的最小单元。一个进程可以拥有许多个线程,这些线程可以共享该进程的全部资源,并且可以并发执行(比如你可以同时进行扫描病毒和修复漏洞的操作)。

四、线程状态转换

7、下面的程序会输出什么

 

如下是线程状态转换图

public class MultiThreading {
    private static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
        public static void main(String[] args) {
            MyThread myThread = new MyThread("myThread");
            myThread.run();
            myThread.start();
        }
    }
}

2.如何创建并启动一个线程?

www.2527.com 2

调用run方法时,是在main线程内部执行的。

java中创建线程有两种方式:继承Thread基类 以及  实现Runnable接口。

Java线程状态转换.png

main
myThread

继承Thread基类的方式:

从图中可以看出线程创建之后,
通过调用start()开始运行。
当主动调用wait()或者join()时线程进入等待状态。等待状态需要被其他线程的通知才能返回。
超时等待相当于在等待状态的基础上增加了一个超时限制,如果线程在限制时间内没有收到其他线程的通知,会主动回到运行态。
当现场执行同步方法时,如果没有获取到锁,此时线程会阻塞在临界区的入口。
线程执行run方法或者被中断之后进入终止状态。

8、什么是Daemon线程
守护线程是一种特殊的线程,在后台默默的完成一些系统性的服务,比如垃圾回收,JIT线程就可以理解为守护线程。与之对应的是用户线程,用户线程可以理解为系统的工作线程,它会完成程序应该要完成的业务操作,如果用户线程全部结束,就意味着这个程序无事可做了,当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出。

1 class ExtendsThread extends Thread{
2 
3     @Override
4     public void run() {
5        System.out.println("create thread by extends Thread class.........");
6     }
7 }

总结

守护线程必须在线程start之前设置,否则会抛出IllegalThreadStateException

 

1、线程是轻量级的进程,线程是操作系统调度的最基本单位。
2、可以通过继承Thread并覆写run()方法构建一个线程,也可以实现Runnable接口初始化一个线程(推荐使用这种方式),通过调用start()方法执行线程。
3、线程存在6种状态:new、runnable、waiting、time_waiting、blocked、terminated
4、线程随着程序运行和系统调度,状态会不断发生变化。

9、在Java中,那些操作是原子性的?

实现Runnable接口的方式:

  • 读和写的操作变量是 主类型的(primitive variables)除了long 和 double
  • 读和写的变量声明了volatile
1 class ImplementsThread implements Runnable{
2 
3     @Override
4     public void run() {
5         System.out.println("create thread by implements Runnable interface.........");
6     }
7 }

 

创建线程后,就要启动线程去执行它的工作,两种创建线程方式有不同的启动方法:

 1 public class CreateThread {
 2 
 3     public static void main(String[] args) {
 4         //使用 extends Thread 创建并启动线程
 5         ExtendsThread et = new ExtendsThread();
 6         et.start();
 7 
 8         //使用 implements Runnable 创建并启动线程
 9         Thread runThread = new Thread(new ImplementsThread());
10         runThread.start();
11 
12     }
13 }

这里有一个很古老的面试题:线程创建有哪两种方式?哪种方式比较好?为什么?

这个问题的答案要根据具体业务情况来回答,如果确实是非常非常简单的业务场景,我只需要一个线程就能完成任务,那么当然是implements Runnable接口的方式比extends Thread类的方式好。

因为大家知道java是单继承的,如果用extends的方式创建了线程,那么这个线程类就无法再继承别的类,而implements的方式则不会有这个问题,相对来说扩展性更好。

但是,现代的企业(尤其是互联网企业)中,基本不可能看到用这两种方式来创建线程的,而是使用Executors类提供的许多类型的线程池来创建并管理一组线程,为什么?

因为在复杂的、对性能和实时性要求非常高的业务场景下,用这两种创建线程的方式会造成大量资源的消耗,并且线程的上下文切换也会浪费大量的时间,所以java很贴心的为我们提供了线程池,以便于更好的管理许许多多的线程以适应业务需要。

 

这里再说一个额外的小知识点,大家有没有想过,如果我对同一个线程调用两次start()方法,会出现什么情况呢?

www.2527.com 3

可以看到java会抛出异常:非正常的线程状态,在java中是不允许调用两次同一个线程的start方法的。

 

3.线程有哪几种状态?

既然上面提到了“非正常的线程状态”,那么接下来就讲一下java中线程的状态,其实共有6种:

1.NEW 新建状态。

顾名思义,在此状态下的线程就是刚刚创建出来的新线程。

2.RUNNABLE  可运行状态。

调用线程的start()方法并获取到资源锁(关于锁的知识后文会详细讲解)的线程就会进入此状态。注意,此时的线程可能正在虚拟机中执行任务,也有可能在等待CPU时间分片。

3.BLOCKED  阻塞状态。

某些代码可能会被加锁,而需要执行这些代码的线程首先需要获取到锁对象,如果线程因为没有获取到监视器锁而无法执行任务,则其就处于阻塞状态。

4.WAITING  等待状态。

如果线程执行了不带超时时间的wait方法或者join方法,那么线程就会让出执行权,并进入等待状态。

5.TIME_WAITING  超时等待状态

如果线程执行了带超时时间的wait方法或者join方法或者sleep方法,那么线程就会进入超时等待状态

6.TERMINATED  终止状态

无论是响应中断而强制结束的线程或者是正常执行任务完成后退出的线程都会处于这个状态。处于终止状态的线程不具备继续运行的能力。

 

www.2527.com,线程的六种状态需要好好理解,这对于后面知识的理解会有一定帮助。

 

4.如何优雅的结束线程?

最后我们来讲一下如何优雅的结束一个线程。有的同学可能会说这还不简单,Thread相关api中不是有个stop方法嘛,只要调用这个方法线程不就结束了吗?

这里我先明确一下结论:stop方法确实有,但是这个方法具有不确定性,jdk1.5及以后这个方法已经被标示为“Deprecated(废弃)”状态,即不赞成在代码中继续使用该方法。

然而java中并没有提供能够立即使一个线程转变为TERMINATED状态的方法,取而代之的是提供了一种叫做“中断”的协调机制,可以使一个线程终止另一个线程当前的工作。

 1 /**
 2  * 优雅终止线程的方式
 3  */
 4 public class TerminateThread {
 5 
 6     public static void main(String[] args){
 7         NeedStopThread nst = new NeedStopThread();
 8         try {
 9             nst.start();
10             Thread.sleep(3000);
11             nst.cancel();   //线程运行3秒后由主线程发出中断请求
12         } catch (Exception e) {
13             System.out.println("Exception in Main Thread.....");
14         }
15     }
16 }
17 
18 class NeedStopThread extends Thread{
19 
20     private AtomicInteger count = new AtomicInteger();
21 
22     @Override
23     public void run() {
24         try {
25             while(!Thread.currentThread().isInterrupted()){   //当前线程未接收到中断信号
26                 System.out.println("i'm running.........count now is "   count.getAndIncrement());
27             }
28         } catch (Exception e) {
29             System.out.println("Exception in NeedStop Thread.....");
30         }finally {
31             System.out.println("try to close io or sql resource here.........");
32         }
33     }
34 
35     public void cancel(){
36         interrupt();  //由另一个线程调用发出中断信号,请求终止当前线程
37     }
38 }

大家可以多次执行这段代码,会发现结果肯定都是不一样的,这也证明了中断操作并不会让线程立即进入终止状态,最终进入终止状态的时间还是由当前线程自己判断的。

 

关于线程的基础知识讲解到这里就结束了。基础知识大家一定不能小看它,地基要扎稳,楼房才能盖的好。下篇文章就开始讲解线程间的通信和协作的几种方式,也是非常重要的内容。OK,我们下篇文章见。

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于www.2527.com,转载请注明出处:并发基础知识,Java并发编制程序基础www.2527.com