您的位置:首页 > 职场人生

黑马程序员——多线程

2015-12-11 14:23 281 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

概述:

        要理解多线程,首先就要明白什么是进程,然后再明白什么是线程,就可以理解多线程了。

        1、进程:就是正在执行的应用程序,比如:QQ,360等等。

        2、线程:是进程的执行单元和进行路径,如QQ不只可以聊天,还可以访问空间或者邮箱等等。

 

当我们理解了进程和线程,就要把进程和线程进行细分:

进程:

     1、单进程:计算机只能做一个事情,只能操作一个任务。

     2、多进程:计算机能做多个事情,同一时间段能执行多个任务,如边玩游戏边听音乐。

多进程的意义:是为了提高CPU的使用率。

     

    线程:

         1、单线程:一个应用程序只有一条执行路径,只能执行一个任务。

         2、多线程:一个应用程序有多条执行路径,可以执行多个任务。

多线程的意义:线程的意义是为了提高应用程序的使用率。

 

那么Java程序的运行原理及JVM的启动是多线程的吗?

1:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。

2:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

 

注意事项:

      线程是程序(进程)使用CPU的最基本单位,程序(进程)的执行其实都是在抢CPU的执行权。多个进程在抢CPU的执行权,而其中的某一个进程的执行路径比较多,就会有更高的机率抢到执行权。但哪一个线程在哪个时刻抢到执行权,是无法保证的,所以线程的执行有“随机性”。

      

 

内容:

 一、多线程的实现方案:

     先要知道启动一个线程是start()方法,而真正要运行的是继承Thread类或者实现Runnable接口的run()方法里面的代码(注:不推荐第三种方法,实现Callable接口)。

 

1、继承Thread类,重写run()方法

public class MyThread extends Thread {

public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}


2、实现Runnable接口,重写run()方法

public class MyThread implements Runnable {

public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}

 

二、线程的调度和优先级:

1、调度:

        A、分时调度,就是每一个线程都在规定的时间内执行,执行完毕轮到下一个线程,以此循环,这是一种公平的调度模式。

        B、抢占式调度,没有什么规定可言,是看哪个线程的生命力强大,哪个就能抢到执行权,这是弱肉强食的调度模式,而Java就是采用这种调度模式。

2、优先级:

            A、所有线程默认都是5。

            B、范围是1-10。

            C、程序员可以根据自己的需要设置线程的优先级。

 

三、线程的控制(常见方法):

    

1、休眠线程:sleep();

       意义:是让线程在执行过程中,按规定的时间休眠,时间一结束,就会继续往下执行。

   方法调用:因为是静态方法可以类名直接调用,调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。要使线程休眠,就要放在run()方法里面。

 

2、加入线程:join();

       意义:让一个线程先独立执行完之后,再让其它的线程加入进来抢执行权。

   调用方法:是非静态方法,哪个线程要先独立执行,那么就该线程对象调此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。

 

3、礼让线程:yield();

       意义:只是让线程看起来更和谐的轮流获得执行权,但并不能完全保证每一次多个线程都轮流执行。

   调用方法:静态方法,直接类名调用。要放在run()方法里面,因为是每个线程都要礼让。 

 

4、后台线程:setDaemon(boolean on);

       意义:当一个线程被设置为守护线程时,只要主线程执行完之后,那么该线程就会被消灭。

   调用方法:非静态方法,哪个线程要被设置为守护线程,那么就该线程对象调此方法。

 

5、终止线程:

   两种方法:

           1、stop()方法:该方法已过时,不推荐使用。因为只要被stop()后,程序后面的代码就无法继续执行,具有不安全性。

           2、interrupt()方法:此方法是具有安全性的,当前线程被终止后,程序后面的代码仍将继续执行。

   方法调用:两种方法都非静态,要终止哪个线程,那么就该线程对象调用此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。

 

6、等待唤醒机制:

       此机制有wait()、notify()和notifyAll()三个方法,虽然都是Object类里的方法,

但是都可以对线程造成控制,所以都是与线程相关的方法。

           1、wait()方法:等待获取执行权

           2、notify()方法:唤醒单个线程

           3、notifyAll()方法:唤醒全部线程

 

下面以生产者与消费者来理解等待唤醒机制比较容易:

           

            生产者-->先看是否有数据,有就等待,没有就生产数据

 

            消费者-->先看是否有数据,有就消费数据,没有就等待

 

四、多线程的安全问题:

 

      我们在判断线程是否会出现安全问题,那就要依据以下三点:

                  1、是否有多线程环境。

                  2、是否有共享数据。

                  3、是否有多条语句操作共享数据。

     

      那么当条件满足其中一点或者全部都满足的时候,我们要怎么处理呢?

      

第一种办法,加synchronized同步关键字:

方法1:        

       创建同步代码块,将需要被同步的代码包裹在代码块中:

public class MyThread implements Runnable {
// 共性数据
private int num = 00;

public void run() {
while (num > 0) {
//同步代码块,这里的锁对象可以任意对象,为了方便使用this对象
synchronized (this) {
//把线程对数据进行操作的语句放到同步代码块中
System.out.println(Thread.currentThread().getName() + (num--));
}
}
}
}

 

方法2:

       创建同步方法,将需要被同步的代码放到方法中:

public class MyThread implements Runnable {
// 共性数据
private int num = 00;

public void run() {
while (num > 0) {
// 调用同步方法
method();
}
}
//同步方法,把同步关键字加在方法申明中,这里的锁对象是this
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}

 

方法3:

      创建静态同步方法,将需要被同步的代码放到方法中:

public class MyThread implements Runnable {
// 共性数据,也要是静态的
private static int num = 00;

public void run() {
while (num > 0) {
method();
}
}
//静态同步方法,锁对象是当前类的字节码文件对象,xxx.class
public static synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}


      

       观察上面三个方法,我们并没有直接看到在哪里加上锁,在哪里释放锁,为了更清晰的表达加锁和释放锁,JDK5以后提供了一个新的锁对象:Lock。

        加锁:在被同步的代码前面加锁。

      释放锁:在同步代码结束后释放锁。          

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread implements Runnable {
// 共性数据
private int num = 00;
// 创建Lock锁对象,Lock是接口,接收的是子类对象
private Lock lock = new ReentrantLock();

public void run() {
while (num > 0) {
// 在操作共享数据前加锁
lock.lock();

System.out.println(Thread.currentThread().getName() + "--" + (num--));

// 在结束后释放锁
lock.unlock();
}
}
}

 

五、线程的生命周期:

    

线程常见的状态:

            1、新建-->就绪-->运行-->死亡

            2、新建-->就绪-->运行-->就绪-->运行-->死亡

            3、新建-->就绪-->运行-->其它阻塞-->就绪-->运行-->死亡

            4、新建-->就绪-->运行-->同步阻塞-->就绪-->运行-->死亡

            5、新建-->就绪-->运行-->等待阻塞-->同步阻塞-->就绪-->运行-->死亡

 

具体结构如下图:



 

六、线程池:

     因为程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

      

好处:

     线程池里的没有哥线程代码结束后并不会死亡,而是再次会到线程池中称为空闲状态,等待下一个对象来使用。                                                                                                                                                                                                        
                                                                                                                                                                                                                                                               
                                                        

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: