黑马程序员—线程

------- android培训java培训、期待与您交流! ----------


什么是线程:
     线程就是程序中单独顺序的流控制。线程本身不能运行,它只能用于程序中。
什么是多线程:
     多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务。

线程概念说明:
     线程是程序内的顺序控制流,只能使用分配给程序的资源和环境。
多线程编程的目的:
     多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。


单线程: 当程序启动运行时,就自动产生一个线程,主方法main就在这个主线程上运行。
多线程:
   •  一个进程可以包含一个或多个线程
   •  一个程序实现多个代码同时交替运行就需要产生多个线程
   •  CPU随机的抽出时间,让我们的程序一会做这件事情,一会做另外一件事情
   •  同其他大多数编程语言不同,Java内置支持多线程编程(multithreaded programming)。多线程程序包含两条或两条以上并发运行的部分,把程序中每个这样的部分都叫 作一个线程(thread)。每个线程都有独立的执行路径,因此多线程是多任务处理的一种特殊形式。
   •  多任务处理被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:

基于进程的和基于线程的。

       1.基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。
       2.而在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。

   •  多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.
   •  线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
   •  多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配给它们独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。
   •  多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很多。在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步—尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。
    •  Java多线程的优点就在于取消了主循环/轮询机制。一个线程可以暂停而不影响程序的其他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。
    •  Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,Java使用线程来使整个环境异步。这有利于通过防止CPU循环的浪费来减少无效部分。
    •  为更好地理解多线程环境的优势,我们可以将它与它的对照物相比较。单线程系统的处理途径是使用一种叫作轮询的事件循环方法。在该模型中,单线程控制在一无限循环中运行,轮询一个事件序列来决定下一步做什么。一旦轮询装置返回信号表明已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。直到事件处理程序返回,系统中没有其他事件发生。这就浪费了CPU时间。这导致了程序的一部分独占了系统,阻止了其他事件的执行。总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行。


在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。
      1.继承Thread类并重写run方法。

      2.通过定义实现Runnable接口的类进而实现run方法。

      a.继承Thread类并重载run方法。
        • Thread类:是专门用来创建线程和对线程进行操作的类。Thread中定义了许多方法对线程进行操作。
        • Thread类在缺省情况下run方法什么都不做。可以通过继承Thread类并重写Thread类的run方法实现用户线程。

      

      定义MyThread类:

    //定义MyThread类 
    public class MyThread extends Thread { 
        private String name; 
        public MyThread(String name) { 
            this.name = name; 
        } 
        public void run() { 
            for (int i = 0; i < 5; i++) { 
                System.out.println(name + "运行:" + i); 
            } 
        } 
    } 
TestThread类 调用:
    public class TestThread { 
        public static void main(String[] args) { 
            MyThread myThread1 = new MyThread("singsong1"); 
            MyThread myThread2 = new MyThread("singsong2"); 
            myThread1.run(); 
            myThread2.run(); 
        } 
    } 
运行结果:
运行结果: 
singsong1运行:0 
singsong1运行:1 
singsong1运行:2 
singsong1运行:3 
singsong1运行:4 
singsong2运行:0 
singsong2运行:1 
singsong2运行:2 
singsong2运行:3 
singsong2运行:4


       从结果可以发现,此时的执行非常的有规律,先执行完第一个对象,再执行第二个对象。(此时只是调用Thread类对象的普通方法run)

       把run()方法换成start();一旦调用start()方法,则会通过JVM找到run()方法

       Public void start()

       使用start()方法启动线程:

      

    public class TestThread { 
        public static void main(String[] args) { 
            MyThread myThread1 = new MyThread("singsong1"); 
            MyThread myThread2 = new MyThread("singsong2"); 
            myThread1.start(); 
            myThread2.start(); 
        } 
    } 
运行结果: 
singsong1运行:0 
singsong1运行:1 
singsong2运行:0 
singsong1运行:2 
singsong2运行:1 
singsong1运行:3 
singsong2运行:2 
singsong1运行:4 
singsong2运行:3 
singsong2运行:4


      从运行结果可以知道,程序时交互式的运行。因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的


     2.实现Runnable接口的类实现run方法。

          通过建立一个实现了Runnable接口的类,并以它作为线程的目标对象来创建一个线程。

    实现Runnable接口

    public class RunnableThread implements Runnable { 
        private String name; 
        public RunnableThread(String name) { 
            this.name = name; 
        } 
        public void run() { 
            for (int i = 0; i < 5; i++) { 
                System.out.println(name + "运行:" + i); 
            } 
        } 
    } 
    public class TestThread1 { 
        public static void main(String[] args) { 
             RunnableThread runnableThread1=new RunnableThread("singsong1"); 
             RunnableThread runnableThread2=new RunnableThread("singsong2"); 
            Thread thread1= new Thread(runnableThread1); 
            Thread thread2= new Thread(runnableThread2);      
            thread1.start(); 
            thread2.start();          
        } 
    } 

    运行结果: 
    singsong1运行:0 
    singsong1运行:1 
    singsong2运行:0 
    singsong2运行:1 
    singsong2运行:2 
    singsong2运行:3 
    singsong2运行:4 
    singsong1运行:2 
    singsong1运行:3 
    singsong1运行:4 

 Thread类和Runnable接口区别

      最大区别是在于资源的共享,如果一个类继承Thread,则资源共享不可共享。而实现Runable接口,能实现资源共享。

例如:MyThread类

     

    public class MyThread extends Thread { 
        private int ticket = 5; 
        private String name; 
        public MyThread(String name) { 
            this.name = name; 
        } 
        public void run() { 
            for (int i = 0; i < 15; i++) { 
                if (ticket > 0) { 
                    System.out.println(name + "正售出第 " + (ticket--) + "票"); 
                } 
            } 
        } 
    } 
     
    调用: 
    public class TestThread { 
        public static void main(String[] args) { 
            MyThread myThread1 = new MyThread("singsong1"); 
            MyThread myThread2 = new MyThread("singsong2"); 
            myThread1.start(); 
            myThread2.start(); 
        } 
    } 
    运行结果: 
    singsong1正售出第 5票 
    singsong1正售出第 4票 
    singsong1正售出第 3票 
    singsong1正售出第 2票 
    singsong1正售出第 1票 
    singsong2正售出第 5票 
    singsong2正售出第 4票 
    singsong2正售出第 3票 
    singsong2正售出第 2票 
    singsong2正售出第 1票 

     重复售出了一次,再来看看Runnable接口是怎么实现资源共享的。

     例程序RunnableThread类:

   

    public class RunnableThread implements Runnable { 
        private int ticket = 5; 
        private String name; 
        public RunnableThread(String name) { 
            this.name = name; 
        } 
        public void run() { 
            for (int i = 10; i > 0; i--) { 
                if (ticket > 0) { 
                    System.out.println(name + "正售出第 " + (ticket--) + "票"); 
                } 
            } 
        } 
    } 
    public class TestThread1 { 
        public static void main(String[] args) { 
            RunnableThread runnableThread1 = new RunnableThread("singsong1"); 
            Thread thread1 = new Thread(runnableThread1); 
            Thread thread2 = new Thread(runnableThread1); 
            thread1.start(); 
            thread2.start(); 
        } 
    } 
    运行结果: 
    singsong1正售出第 5票 
    singsong1正售出第 4票 
    singsong1正售出第 3票 
    singsong1正售出第 2票 
    singsong1正售出第 1票 
    从运行结果可以得出实现Runnable接口的优点。

总结一下:
      1.两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。
      2.在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。
      3.线程的消亡不能通过调用一个stop()命令。而是让run()方法自然结束。

线程的生命周期
     线程的生命周期:一个线程从创建到消亡的过程。
     线程的生命周期可分为四个状态:
         1.创建状态
         2.可运行状态
         3.不可运行状态
         4. 消亡状态

    1.创建状态
       •  当用new操作符创建一个新的线程对象时,该线程处于创建状态。
       •  处于创建状态的线程只是一个空的线程对象,系统不为它分配资源
    2. 可运行状态
       •  执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行( Runnable )状态。
       •  这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。
    3.不可运行状态
      当发生下列事件时,处于运行状态的线程会转入到不可运行状态。
     •  调用了sleep()方法;
     •  线程调用wait方法等待特定条件的满足
     •  线程输入/输出阻塞
    返回可运行状态:
     •  处于睡眠状态的线程在指定的时间过去后
     •  如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变
     •  如果线程是因为输入/输出阻塞,等待输入/输出完成
   4. 消亡状态
      当线程的run方法执行结束后,该线程自然消亡。

   停止线程我们一般不同stop()方法,而是run()方法自然结束。推荐方式如实例程序:

  

public class MyThread implements Runnable
{ 
    private boolean flag=true;
    public void run(){   
		while (flag)
        {…}
    }
    public void stopRunning()
    { 
		flag=false;
	}
}
public class ControlThread
{ 
	private Runnable r=new MyThread();
    private Thread t=new Thread(r);
    public void startThread()
    { 
		t.start(); 
	}
    publi void stopThread()
    { 
		r.stopRunning();
	}
}

线程的优先级:
      1. 线程的优先级及其设置
      设置优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。
      一个线程的优先级设置遵从以下原则:
       •  线程创建时,子继承父的优先级
       •  线程创建后,可通过调用setPriority()方法改变优先级。
       •  线程的优先级是1-10之间的正整数。
        1 - MIN_PRIORITY,
        10 – MAX_PRIORITY
        5- NORM_PRIORITY(默认)
      2. 线程的调度策略
       线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。
      •  线程体中调用了yield()方法,让出了对CPU的占用权。
      •  线程体中调用了sleep()方法, 使线程进入睡眠状态。
      •  线程由于I/O操作而受阻塞
      •  另一个更高优先级的线程出现。
      •  在支持时间片的系统中,该线程的时间片用完。

     

public class PriorityTest { 
    public static void main(String[] args) { 
        System.out.println("Thread.MAX_PRIORITY : " + Thread.MAX_PRIORITY); 
        System.out.println("Thread.NORM_PRIORITY: " + Thread.NORM_PRIORITY); 
        System.out.println("Thread.MIN_PRIORITY : " + Thread.MIN_PRIORITY); 
    } 
}

运行结果:

    Thread.MAX_PRIORITY : 10 
    Thread.NORM_PRIORITY: 5 
    Thread.MIN_PRIORITY : 1 

查看Main线程的优先级:
    public class RunStart implements Runnable { 
        public void run() { 
            switch (Thread.currentThread().getPriority()) { 
            case 1: 
                System.out.println(Thread.currentThread().getName() 
                        + "线程优先级:Thread.MIN_PRIORITY=" 
                        + Thread.currentThread().getPriority()); 
                break; 
            case 5: 
                System.out.println(Thread.currentThread().getName() 
                        + "线程优先级:Thread.NORM_PRIORITY=" 
                        + Thread.currentThread().getPriority()); 
                break; 
            case 10: 
                System.out.println(Thread.currentThread().getName() 
                        + "线程优先级:Thread.MAX_PRIORITY=" 
                        + Thread.currentThread().getPriority()); 
                break; 
            default: 
                System.out 
                        .println(Thread.currentThread().getName() + "线程优先级:ERORR"); 
                break; 
            } 
        } 
    } 
调用:

    public class TestRunStart { 
        public static void main(String[] args) { 
            RunStart runStart = new RunStart(); 
            Thread thread1 = new Thread(runStart, "thread1"); 
            thread1.run(); 
        } 
    } 
运行结果:

    main线程优先级:Thread.NORM_PRIORITY=5 

看看创建新的线程的默认优先级:
    public class TestRunStart { 
        public static void main(String[] args) { 
            RunStart runStart = new RunStart(); 
            Thread thread1 = new Thread(runStart, "singsong"); 
            thread1.start(); 
            } 
    } 

运行结果:
    singsong线程优先级:Thread.NORM_PRIORITY=5 

在优先级相同的情况下,就出现了CPU资源的抢占现象了从以上的运行结果可以知道main方法其实也是一个线程。在java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

线程的中断

   实例:

    public class SleepInterrupt implements Runnable { 
        public void run() { 
            System.out.println("1、进入run方法体线程正常启动!!"); 
            try { 
                System.out.println("2、线程休眠3秒"); 
                Thread.sleep(3000); 
                System.out.println("3、线程正常休眠3秒"); 
            } catch (InterruptedException e) { 
                System.out.println("4、线程被中断了!!!"); 
            } 
            System.out.println("5、正常结束run方法"); 
            System.out.println(Thread.currentThread().getName() + "线程正在运行"); 
        } 
    } 

TestSleepInterrupt类调用:

    public class TestSleepInterrupt { 
        public static void main(String[] args) { 
            SleepInterrupt sleepInterrupt = new SleepInterrupt(); 
            Thread thread = new Thread(sleepInterrupt, "singsong"); 
            thread.start(); 
            try { 
                Thread.sleep(3000); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            thread.interrupt(); 
        } 
    } 
运行结果:
    1、进入run方法体线程正常启动!! 
    2、线程休眠3秒 
    3、线程正常休眠3秒 
    5、正常结束run方法 
    singsong线程正在运行 
在启动线程时,要若不加此句“ Thread.sleep(3000); ”程序进入执行中断操作,

则执行结果:

    1、进入run方法体线程正常启动!! 
    2、线程休眠3秒 
    4、线程被中断了!!! 
    5、正常结束run方法 
    singsong线程正在运行 

执行线程睡眠操作,抛出中断异常,因此要有异常处理

public static void sleep(long millis, int nanos) throws InterruptedException

线程的强制执行jion()方法:

    public class JoinDemo implements Runnable { 
        public void run() { 
     
            for (int i = 0; i < 10; i++) { 
                System.out.println(Thread.currentThread().getName() + i); 
            } 
        } 
     
        public static void main(String[] args) { 
            Thread thread = new Thread(new JoinDemo()); 
            thread.setName("singsong"); 
                thread.start(); 
     
            for (int i = 0; i < 10; i++) { 
                if (i == 5) { 
                    try { 
                        thread.join(); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                } 
                System.out.println(Thread.currentThread().getName() + i); 
            } 
        } 
    } 
执行结果为:
    main0 
    main1 
    main2 
    main3 
    main4 
    singsong0 
    singsong1 
    singsong2 
    singsong3 
    singsong4 
    singsong5 
    singsong6 
    singsong7 
    singsong8 
    singsong9 
    main5 
    main6 
    main7 
    main8 
    main9 

从运行结果中:本程序启动了两个线程:一个是main线程,另一个是singsong线程当main线程中循环执行到第五次时,就强制执行singsong线程。Join()方法就是用来强制某一线程的运行。


多线程的同步
     1.为什么要引入同步机制
       在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
     2. 怎样实现同步
        对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
        例如:
             synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }
        如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。调用synchronized方法时,对象就会被锁定。
        说明:
            •当synchronized方法执行完或发生异常时,会自动释放锁。
            •被synchronized保护的数据应该是私有(private)的。

    实例:

      同步代码块

    public void run() { 
            for (int i = 0; i < 10; i++) { 
                synchronized (this) {//同步代码块 
                    if (ticket > 0) { 
                        try { 
                            Thread.sleep(1000); 
                        } catch (InterruptedException e) { 
                            e.printStackTrace(); 
                        } 
                        System.out.println(Thread.currentThread().getName() 
                                + "在售出第" + ticket-- + "票"); 
                    } 
                } 
            } 
        } 
       同步方法
    public class Synchronized implements Runnable { 
        private int ticket = 5; 
        public void run() { 
            for (int i = 0; i < 10; i++) { 
                sale(); 
            } 
        } 
    //同步方法sale() 
        public synchronized void sale() { 
     
            if (ticket > 0) { 
                try { 
                    Thread.sleep(1000); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
                System.out.println(Thread.currentThread().getName() + "在售出第" 
                        + ticket-- + "票"); 
            } 
        } 
    } 
运行结果:
    singsong在售出第5票 
    singsong在售出第4票 
    singsong在售出第3票 
    singsong在售出第2票 
    singsong1在售出第1票 

线程组:
     所有线程都隶属于一个线程组。那可以是一个默认线程组,亦可是一个创建线程时明确指定的组。
     说明:
        在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组。
        若创建多个线程而不指定一个组,它们就会与创建它的线程属于同一个组。
     Timer和TimerTask
     • Java 2的1.3版在java.util中增加了一个有趣又有用的功能部件:提供了提前安排将来某时间要执行任务的能力。支持这项功能的类是Timer和TimerTask。使用这些类可以创建一个工作于后台的线程,该线程等待一段指定的时间。当指定的时间到来时,与该线程相连的任务被执行。不同的选项允许安排一个任务重复执行,或安排一个任务在指定的时间运行。尽管永远都可能使用Thread类利用手工方法创建一个在指定的时间执行的任务,但是使用Timer和TimerTask却大大简化了这一过程。
     • Timer和TimerTask一起工作。Timer是一个用于安排一个将来执行的任务的类。被安排的任务必须是TimerTask的一个实例。因此,为了安排一个任务,首先应该创建一个TimerTask对象,然后使用Timer的一个实例安排执行它。
     • TimerTask实现了Runnable接口;因此它可以被用于创建一个执行线程。它的构造函数如下所示:TimerTask( )
     • TimerTask的run( )是一个抽象方法,这意味着它可以被覆盖。由Runnable接口定义的run( )方法包含了将被执行的程序代码。因此创建一个定时器任务的最简单的办法是扩展TimerTask和重写run( )
     • 一旦任务被创建,它将通过一个类型Timer的对象被安排执行。Timer的构造函数如下:–Timer( )–Timer(boolean DThread)
     • 第一种形式创建一个以常规线程方式运行的Timer对象。第二种形式当DThread为true时,使用后台线程。只要剩下的程序继续运行,后台线程就会执行。
     • 一旦Timer被创建,将可以通过调用创建的Timer的schedule( )方法来安排任务。有几种schedule( )方法的形式,这些形式允许用各种办法来安排任务。


   

------- android培训java培训、期待与您交流! ----------


黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值