zoukankan      html  css  js  c++  java
  • 多线程-多线程基础

    1、进程和线程及相关

      进程是应用程序在处理机上的一次执行过程,是一个动态的概念。而线程是进程中的一部分,进程中包含多个线程在运行。

      区别:

        1、进程是资源分配和调度的独立单元,线程是cpu调度的基本单元。

        2、进程有自己独立的内存空间并且互不干扰,一个进程包括多个线程,它们共享同一进程的内存空间。

        3、线程是轻量级进程,创建和销毁时间比进程小很多。

        4、二者均可并发执行,进程让操作系统的并发成为了可能,线程让进程的内部并发成为了可能。

      多线程性能不一定优于单线程,需要根据具体任务及计算机配置决定。

      如:单核cpu中,解压文件多线程中的线程切换会导致额外开销,性能不如单线程。对于多核则相反。 


     2、线程的状态

      线程从创建到消亡,包括以下几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、睡眠或等待一定事件(time waiting)、等待被唤醒(waiting)、消亡(dead)

      一个新线程创建后,不会立即进入就绪状态。线程的运行需要一些条件(具体得了解jvm,以后会更新jvm相关博文),只有条件满足了,才进入就绪状态。

      就绪状态的线程,需要等待获得CPU时间片后,真正进入运行状态。

      在线程运行过程中,多种原因可能导致当前线程不继续运行,如:用户主动让线程睡眠、用户主动让线程等待,同步块阻塞等等。

      线程状态详情如下图:

      


     3、上下文切换。

       A线程在执行任务时,可能需要暂停转去执行B线程,当再次切换回执行A线程时,我们不希望从头执行,而是从A线程任务中断的地方继续执行。因此需要记录A线程的运行状态及一些数据。以便于切换后继续执行。这种情况就是上下文切换,简而言之就是:存储和恢复CPU状态的过程,它使得线程能够从中断点恢复执行。


     4、线程的实现方式

    //继承Thraed类,重写run方法
    class ThreadTest extends Thread{
        @Override
        public void run(){
            super.run();
            System.out.println("ThreadTest");
        }
    }
    //实现Runbale接口,重写run方法
    class ThreadTest1 implements Runnable{
        @Override
        public void run() {
            System.out.println("ThreadTest1");
        }
    }

    实现Runable接口和继承Thread类的区别:

      1、避免java中单继承的限制

      2、增加程序健壮性,代码可被多个线程共享,代码和数据独立

      3、适合多个相同的程序代码去处理同一个资源

      4、使用线程池时,只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

    实例:为何实现Runable可以资源共享而继承Thread类不行?

    //继承Thread类
    public
    class MyThread { public static void main(String[] args) throws InterruptedException{ Thread1 mTh1=new Thread1("A");//创建一个Thread对象就是开启一个新的任务 Thread1 mTh2=new Thread1("B"); mTh1.start(); mTh2.start(); } } class Thread1 extends Thread{ private int count=5; private String name; public Thread1(String name) { this.name=name; } public void run() {  while(count > 0){ System.out.println(name + "运行 count= " + count--); try { sleep((int) Math.random() * 10); } catch (InterruptedException e) { e.printStackTrace(); } } } } //结果 多个线程有独立的count 互不共享 B运行 count= 5 A运行 count= 5 B运行 count= 4 A运行 count= 4 B运行 count= 3 A运行 count= 3 B运行 count= 2 A运行 count= 2 B运行 count= 1 A运行 count= 1
    //实现Runnable
    public class MyThread {
        public static void main(String[] args) throws InterruptedException{
            Thread1 t = new Thread1();
            Thread a = new Thread(t,"A");
            Thread b = new Thread(t,"B");
            Thread c = new Thread(t,"C");
            a.start();
            b.start();
            c.start();
        }
    }
    class Thread1 implements Runnable{
        private int count=5;
        public void run() {
            while(count > 0) {
                System.out.println(Thread.currentThread().getName() + "运行  count= " + count--);
                try {
                    Thread.sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //结果,多个线程共享count
    B运行  count= 5
    A运行  count= 4
    C运行  count= 4
    B运行  count= 3
    A运行  count= 1
    C运行  count= 2

      每个线程只能启动一次,通过继承Thread时,线程和线程所要执行任务时捆绑在一起的。也就使得一个任务只能启动一个线程,不同线程执行任务不同,彼此任务中的资源就不会共享。而通过实现Runnable接口,实际上是创建一个线程,将任务传递进去,由此线程执行。可以实例化多个Thread对象。将同一任务传递进去,也就是一个任务可以启动多个线程来执行。这些线程执行的是同一个任务,所有它们资源共享。(个人所想,不代表一定正确)


     5、实例变量和线程安全

      在自定义线程类中的实例变量针对其他线程可以有共享与不共享之分。

      不共享数据,即对于同一个实例变量每个线程都有各自的独立一份,该实例变量在多线程下共享。以上面的代码为例,继承Thread类时,每个线程都是独立任务,count独立不共享。

      共享数据,多个线程可以访问同一个变量,如实现Runnable时,count成为了多个线程在同一个任务中的共享资源。结果中可以看出不同线程同时对count进行操作。产生了非线程安全问题。而我们想要的结果是依次递减的。问题产生原因在于,count--这个操作不是原子性的。看上去是一步操作,实际上分为了三步。取得count值,计算count-1,对count进行赋值。当多个线程同时访问时就会出现非线程安全问题。这种典型问题我们称之为竞态条件,当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。

      对于这个情况我们可以使用线程同步,来串行执行程序。重新运行更改后的代码,就不会出现竞态条件了。

      在run方法前加synchronized关键字,当一个线程调用run前,会判断run方法有没有被上锁。如果上锁,说明其他线程正在调用,必须等其他线程调用结束后才可以执行。这样也就实现了排队调用run方法的目的。synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。


     6、线程常用的几个api

      6.1、 currentThread()方法

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

    public class ThreadTest3 {
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName());
        }
    }
    结果:
    main

     6.2、isAlive()方法

      该方法可以判断当前的线程是否处于活动状态,活动状态指线程已经启动且尚未终止,处于正在运行或装备开始运行的状态。

    public class ThreadTest3 {
        public static void main(String[] args) throws InterruptedException {
            Test test = new Test();
            System.out.println("begin=" + test.isAlive());
            test.start();
            Thread.sleep(1000);
            System.out.println("end=" + test.isAlive());
        }
    }
    class Test extends Thread{
        @Override
        public void run(){
            System.out.println("run=" + this.isAlive());
        }
    }

    6.3、sleep()方法

      在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个正在执行的线程是this.currentThread()返回的线程。sleep方法不会释放锁

    6.4、getId()方法

      取得线程的唯一标识

    public class ThreadTest3 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() +"  "+ thread.getId());
        }
    }
    结果:
    main  1

    6.5、start()和run()方法

      start()方法是用来启动一个线程,调用该方法后,系统才会开启一个新的线程来执行用户定义的子任务。

      run()方法是不需要用户来调用的,当前线程调用start方法并获得cpu执行时间后,便进入run方法执行具体任务。


    7、停止线程

      停止一个线程意味着在线程处理完任务之前停掉正在做的操作,一般采用的方式有三种:

      1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

      2、使用stop方法强行终止线程,但是不推荐使用此方法,因为stop和suspend及resume一样,都是作废过期的方法,可能造成不可预料的结果。

      3、使用interrupt方法中断线程。

      下面我们开始了解线程停止的相关知识。

    7.1、interrupt、interrupted、isInterrupted方法及区别

      interrupt():在当前线程中打一个停止标记,并不是真正的停止线程。需要自己监视线程状态并处理。

      interrupted():判断当前线程是否已经中断,运行后会清除中断状态,是静态方法。

      isInterrupted():判断调用该方法的线程是否已经中断,是实例方法。

      interrupted和isInterrupted底层调用是同一个方法,只是参数不同。interrupted参数是true,代表要清除状态位,而isInterrupted是false,代表不清除状态位。

    7.2、异常法停止线程

    public class MyThread3 {
        public static void main(String[] args) throws InterruptedException {
            Demo demo =new Demo();
            demo.start();
            Thread.sleep(1000);
            demo.interrupt();
            System.out.println("end!");
        }
    }
    class Demo extends Thread{
        @Override
        public void run(){
            super.run();
            try {
                for (int i = 0; i < 500000; i++) {
                    if (this.interrupted()){
                        System.out.println("已经是停止状态了!我要退出了!");
                        throw new InterruptedException();
                    }
                    System.out.println("i="+(i+1));
                }
                 System.out.println("继续进行");
            } catch (InterruptedException e) {
                System.out.println("进入catch");
                e.printStackTrace();
            }
        }
    }
    //如果在if中使用break,会出现线程停止后,如果for语句后还有语句就会继续执行。为了完全的停止线程,我们采用异常法。            

    7.3、在沉睡中停止线程

      当线程处于sleep状态时,停止线程会抛出异常,与之相反的操作也会抛出异常。

    public class MyThread extends Thread{
        public static void main(String[] args) throws InterruptedException {
            Demo demo = new Demo();
            demo.start();
            Thread.sleep(2000);
            demo.interrupt();
            System.out.println("end");
        }
    }
    class Demo extends Thread{
        public void run() {
            super.run();
            try {
                System.out.println("run begin");
                Thread.sleep(2000000);
                System.out.println("run end");
            } catch (InterruptedException e) {
                System.out.println("在沉睡中被停止"+this.isInterrupted());
                e.printStackTrace();
            }
        }
    }
    //结果
    run begin
    end
    在沉睡中被停止false
    java.lang.InterruptedException: sleep interrupted

    7.4、暴力停止线程-stop方法的不可取

      使用stop方法停止线程,是非常暴力的。强制让线程停止可能使一些清理下工作得不到完成。另外可能对锁定对象进行解锁,导致数据不一致的问题。

    7.5、使用return停止线程

      使用interrupt()方法和return结合实现停止线程

    public class MyThread extends Thread{
        public static void main(String[] args) throws InterruptedException{
            Demo d = new Demo();
            d.start();
            Thread.sleep(2000);
            d.interrupt();
        }
    }
    class Demo extends Thread{
        public void run() {
            while(true) {
                if (this.isInterrupted()) {
                    System.out.println("停止了");
                    return;
                }    
                System.out.println("timer=" + System.currentTimeMillis());
            }    
        }
    } 

    8、暂停线程

      暂停线程意味着可线程还可以恢复运行,在Java多线程中,可以使用suspend()方法暂停线程,使用resume()方法恢复线程执行

      但这两个方法不建议使用,suspend在使线程暂停时,不会释放任何锁资源。其他线程无法访问被它占用的锁。直到对应线程的resume方法后,被暂停的线程才能继续。其他被阻塞在这个锁的线程可以继续执行。但是,当resume操作在suspend之前执行,那么线程会一直处于挂起状态,同时一直占用锁。这会产生死锁,并且被挂起的线程状态是Runnable(运行)

    public class MyThread3 {
        public static Object object = new Object();
        static TestThread t1 = new TestThread("线程1");
        static TestThread t2 = new TestThread("线程2");
        public static class TestThread extends Thread{
            public TestThread(String name){
                super.setName(name);
            }
            public void run(){
                synchronized (object){
                    System.out.println(getName()+"占用中..");
                    Thread.currentThread().suspend();
                    System.out.println(getName()+"结束了执行..");
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            t1.start();
            Thread.sleep(2000);
            t2.start();
            t1.resume();
            t2.resume();
            t1.join();
            t2.join();
        }
    }
    代码执行结果:
        线程1占用中..
        线程1结束了执行..
        线程2占用中..
    代码执行流程:
        线程1占用后,被暂停,然后执行恢复。此时t2.start执行,但resume执行在suspend之前,导致线程被永久挂起,t2结束无法执行。从虚拟机观察线程状态,会是Runnable

    9、yield

      该方法作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。放弃时间不确定,有可能刚放弃马上又获得CPU时间片

    public class MyThread3 {
        public static void main(String[] args) throws InterruptedException {
            TestThread t1 = new TestThread("线程A");
            TestThread t2 = new TestThread("线程B");
            t1.start();
            Thread.yield();
            t2.start();
        }
    }
    
    class TestThread extends Thread{
        public TestThread(String name){
            super.setName(name);
        }
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println(getName() + "在执行..");
            }
        }
    }
    运行结果:
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    线程B在执行..
    线程B在执行..
    线程B在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    线程A在执行..
    从结果中看出B线程放弃CPU时间片后,线程A并没有立马去获得,而是B线程又获得了时间片

    10、线程优先级

      线程划分优先级、优先级较高的线程得到的CPU资源较多,CPU会优先执行优先级别较高的线程对象中的任务。设置优先级使用setPriority()方法。

      关于优先级有一下几点需要考虑:

      1、当前线程优先级未指定时,所有线程都携带普通优先级

      2、优先级范围1-10。10最高,1最低,5是普通优先级

      3、优先级最高的线程在执行时被给予优先,但不能保证线程在启动时就进入运行状态

      4、与线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高优先级

      5、在线程开始方法被调用前,设定线程优先级

      6、优先级常量设置,MIN_PRIORITY(1)、MAX_PRIORITY(10)、NORM_PRIORITY(5)

      优先级的继承性:线程优先级具有继承性,A线程启动B线程,则B线程的优先级与A一致。

      优先级的规则性:CPU尽量将执行资源让给优先级较高的线程,但不代表高优先级的线程全部先执行完。

      优先级的随机性:优先级较高的线程并不一定每一次都先执行完run方法中的任务,线程优先级与打印顺序无关,两者之间具有随机性。

    11、守护线程

      java线程中有两种线程,一种是用户线程,一种是守护线程。当进程中不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程。

      setDaemon来创建守护线程,isDaemon来判断线程是否守护线程

      创建一个守护线程:

    public class MyThread {
        public static void main(String[] args) throws InterruptedException{
            try {
                Demo d = new Demo();
                d.setDaemon(true);
                d.start();
                Thread.sleep(5000);
                System.out.println("离开了d对象也不再打印,也就是停止了");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    class Demo extends Thread{
        private int i = 0;
        public void run() {
            try {
                while(true) {
                    i++;
                    System.out.println("i="+i);
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    结果:
    i=1
    i=2
    i=3
    i=4
    i=5
    离开了d对象也不再打印,也就是停止了

    注意:

      1、设置守护线程,必须在start之前设置,否则会有异常。不能把正在运行的常规线程设置为守护线程

      2、把Daemon线程中产生的新线程也是Daemon的

      3、守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断


    博文参考出处:

      Java多线程编程核心技术

      http://www.cnblogs.com/dolphin0520/  海子-博客园

      Java并发编程实战 

      谢谢各位行业前辈提供的技术分享

  • 相关阅读:
    redis---01
    mysql优化-----索引覆盖
    mysql优化-------Myisam与innodb引擎,索引文件的区别
    mysql优化-----多列索引的左前缀规则
    mysql---列的选取原则
    boogo08---中间件
    goroutine pool,WaitGroup,chan 示例
    Android开发 |常见的内存泄漏问题及解决办法
    Android中FragmentPagerAdapter对Fragment的缓存(二)
    Android中FragmentPagerAdapter对Fragment的缓存(一)
  • 原文地址:https://www.cnblogs.com/zhangbLearn/p/9761603.html
Copyright © 2011-2022 走看看