黑马程序员——Java基础---多线程<一>

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

7、多线程

7.1 概念

总览

一个程序同时执行多个任务。通常,每一个任务称为一个线程(thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为:多线程程序(multithreaded)。

多进程与多线程有哪些区别呢?
本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。

进程:正在进行中的程序(直译)。
线程:进程中一个负责程序执行的控制单元(执行路径)。

P.S.
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。

JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1. 执行main函数的线程,该线程的任务代码都定义在main函数中。
2. 负责垃圾回收的线程。

线程状态

线程可以有如下6种状态:
• New(新生)
• Runnable(可运行)
• Blocked(被阻塞)
• Waiting(等待)
• Timed waiting(计时等待)
• Terminated(被终止)

1.1、新生线程
当用new操作符创建一个新线程时,如new Thread(r),**该线程还没有开始运行。**这意味着
它的状态是new。当一个线程处于新生状态时,程序还没有开始运行线程中的代码。在线程运
行之前还有一些簿记工作要做。
1.2、可运行线程
一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有
运行,这取决于操作系统给线程提供运行的时间。(Java的规范说明没有将它作为一个单独状
态。一个正在运行中的线程仍然处于可运行状态。)
记住,在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么
将这个状态称为可运行而不是运行)。
1.3、 被阻塞线程和等待线程
当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。
直到线程调度器重新激活它。细节取决于它是怎样达到非活动状态的。
1.4 、被终止的线程
线程因如下两个原因之一而被终止:
• 因为run方法正常退出而自然死亡。
• 因为一个没有捕获的异常终止了run方法而意外死亡。
特别是,可以调用线程的stop方法杀死一个线程。该方法抛出ThreadDeath 错误对象,由此
杀死线程。但是,stop方法已过时,不要在自己的代码中调用它。

2、线程属性

线程的各种属性,包括:线程优先级、守护线程、线程组以及处理未捕获
异常的处理器。

1、线程优先级
在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线
程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置为
在MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值。
NORM_PRIORITY被定义为5。

注意

果确实要使用优先级,应该避免初学者常犯的一个错误。如果有几个高优先级
的线程没有进入非活动状态,低优先级的线程可能永远也不能执行。每当调度器决定运
行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管这样会使低优先级的
线程完全饿死。
2、守护线程
可以通过调用 t.SetDaemon(true);
将线程转换为守护线程(daemon thread)。这样一个线程没有什么神奇。守护线程的惟一用途
是为其他线程提供服务。计时线程就是一个例子,它定时地发送“时间嘀嗒”信号给其他线程
或清空过时的高速缓存项的线程。当只剩下守护线程时,虚拟机就退出了,
由于如果只剩下守护线程,就没必要继续运行程序了。
守护线程有时会被初学者错误地使用,他们不打算考虑关机(shutdown)动作。但是,
这是很危险的。守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚
至在一个操作的中间发生中断。
3、未捕获异常处理器
线程的run方法不能抛出任何被检测的异常, 但是,不被检测的异常会导致线程终止。
在这种情况下,线程就死亡了。
4、线程组
线程组是一个可以统一管理的线程集合。默认情况下,创建的所有线程属于相同
的线程组,但是,也可能会建立其他的组

7.2、创建多线程

例1:通过实现 Runnnable 接口 创建多线程

package XianChen;
/**
 * 
 *    1. 定义一个类实现Runnable接口
 *    2. 覆盖接口中的run方法。
 *    3. 通过Thread的引用对象创建线程。 
 *    4. 调用Thread 的方法 start()启动线程
 *    5. 系统并调用线程的任务run方法执行。
 *
 */
public class LiZi {

    public static void main(String[] args) {
//      开启新线程  将要运行的 对象传入
        Thread t1 = new Thread(new XianCh());
        Thread t2 = new Thread(new XianCh());
//      启动新线程
        t1.start();
        t2.start();
    }
}
class XianCh implements Runnable{

//  覆写 抽象方法 run()
    public void run() {
        for(int x=0;x<100;x++)
            System.out.println(Thread.currentThread().getName()+"……"+x);

    }

}
/*
 *  运行结果:
    Thread-1……1
    Thread-0……2
    Thread-1……2
    Thread-0……3
    Thread-1……3
    Thread-0……4
    Thread-1……4
    Thread-0……5
    Thread-1……5
*/

例2:通过 继承Thread类或其子类创建多线程

package XianChen;
/**
 * 
 *  1. 定义一个类继承Thread类。
 *  2. 覆盖Thread类中的run方法。
 *  3. 直接创建Thread的子类对象创建线程。
 *  4. 调用start方法开启线程并调用线程的任务run方法执行。
 *  
 */
public class LiZi {

    public static void main(String[] args) {

        XianCh2 x1 = new XianCh2();
        XianCh2 x2 = new XianCh2();
        x1.start();
        x2.start();
    }
}
class XianCh implements Runnable{

//  覆写 抽象方法 run()
    public void run() {
        for(int x=0;x<100;x++)
            System.out.println(Thread.currentThread().getName()+"……"+x);

    }

}
class XianCh2 extends Thread{

//  覆写 抽象方法 run()
    public void run() {
        for(int x=0;x<100;x++)
            System.out.println(Thread.currentThread().getName()+"……"+x);

    }

}
/**
 *      运行结果
        Thread-0……0
        Thread-1……0
        Thread-0……1
        Thread-1……1
        Thread-0……2
        Thread-1……2
        Thread-0……3
        Thread-1……3
        Thread-1……4

 */

注:

1、可以通过Thread的getName方法获取线程的名称,名称格式:Thread-编号(从0开始)。
2、Thread在创建的时候,该Thread就已经命名了。
3、run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。
4、开启线程是为了运行指定代码,所以需要实现Runnable(),并复写run方法,将运行的代码定义在run方法中即可。
5、一个类继承Thread,不适合资源共享。但是如果实现了Runable,则能实现资源共享。
6、public void start()Java 虚拟机调用该线程的 run 方法。 

7.3、等待唤醒

1、涉及的方法:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程

注意:

1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

2、wait和sleep区别:

分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。

3、代码演示
package XianChen;


public class LiZi{

    public static void main(String[] args) {
        Rec r = new Rec();
//      开启新线程  将要运行的 对象传入
        Sell s= new Sell(r);
        Get g = new Get(r);
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(g);
//      启动新线程
        t1.start();
        t2.start();
        new LiZi().run();
    }

    public synchronized void run() {
        while(true){
            try {
                System.out.println(Thread.currentThread().getName()+"我要sleep 1s ……");
//              sleep 是半阻塞方式,等待一段时候会自动启动,不需要唤醒
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"我醒了……");
                System.out.println(Thread.currentThread().getName()+"我要等待1s ===");
//              wait(time) 类似 sleep 在等待一段时间未被唤醒会自动启动
                wait(1000);
                System.out.println(Thread.currentThread().getName()+"等待结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class Rec {
//  定义锁对象
    private Object objA=new Object();

    public  void sell() {
        synchronized(objA){
            try {
                System.out.println(Thread.currentThread().getName()+"我将要开始等待");
//              唤醒当前对象其他等待的线程
//              用当前锁的对象 调用 线程方法,否则会爆IllegalMonitorStateException 异常             
                objA.notify();
                Thread.sleep(1000);
//              自身线程等待
                objA.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"我被唤醒");

        }
    }
    public void get(){ 
        synchronized(objA){
            try {
                System.out.println(Thread.currentThread().getName()+"我将要开始等待");
//              唤醒当前对象其他等待的线程
                objA.notify();
                Thread.sleep(1000);
//              自身线程等待
                objA.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName()+"我被唤醒");

        }
    }           

}

class Sell implements Runnable{
    private Rec r=null;
    Sell(Rec r){
        this.r = r;
    }
    public void run() {
        System.out.println(Thread.currentThread().getName()+"run");
        while(true){
            r.sell();               
        }
    }
}

class Get implements Runnable{
    private Rec r=null;
    Get(Rec r){
        this.r=r;
    }
    public void run() { 
       System.out.println(Thread.currentThread().getName()+"run");
       while(true){

            r.get();                
       }
    }
}
/*
    运行结果:
    Thread-0run
                        main我要sleep 1s ……
    Thread-1run
    Thread-0我将要开始等待
                        main我醒了……
    Thread-1我将要开始等待
                        main我要等待1s ===
                        main等待结束
    Thread-0我被唤醒
    Thread-0我将要开始等待
                        main我要sleep 1s ……
    Thread-1我被唤醒
    Thread-1我将要开始等待
                        main我醒了……
                        main我要等待1s ===
                        main等待结束
    Thread-0我被唤醒
    Thread-0我将要开始等待
 */
4、注意事项:

1、wait 、notify、notifyAll b必须与锁 配套使用,不能单独存在
否则会报:IllegalMonitorStateException 异常
2、wait()可以被当前对象,或者锁对象调用,但要解锁必须用相同对象才行
比如: objA .wait(); 必须用 objA.natify() 或objA.notifyAll()解锁
3、notify(),唤醒的是当前对象所调用的线程池中,最早wait()的线程,具有不确定性。
4、notifyAll(),唤醒的是当前对象所调用的线程池中所有的线程

6、Lock

概述:

解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法: await()、signal()、signalAll()体现新版本对象的好处。
涉及内容包括

ReentrankLock():创建一个 ReentrantLock 的实例
await():        所在线程等待
signal():       配合Condition,唤醒指定的单个线程
signalAll():    配合Condition,唤醒所有指定线程
Lock():         获取锁
unLock():       释放锁
newCondition:   返回绑定到此 Lock 实例的新 Condition 实例。   
ReentrantLock示范
package XianChen;

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

public class LiZi {
//  锁
    Lock lockdemo = new ReentrantLock();
//  锁状态
    Condition con_0 =lockdemo.newCondition();
    Condition con_1 =lockdemo.newCondition();
    Boolean flag =true;
//  建立唯一数据对象
    ArrayList<String> li = new ArrayList<String>();
//  利用内部类的形式,存放线程代码
    Runnable xian_0= new Runnable(){
        public void run() {
            while(flag){

//              上锁
                lockdemo.lock();
                try{
//                  将字符串存入指定集合
                    String str = li.size()+"by"+Thread.currentThread().getName()+"\r\n";
                    Thread.sleep(100);
                    li.add(str);
//                  判断集合容量,超出时停止
                    if(li.size()>=10){
                        flag =false;
                        System.out.println(li.toString());                      
//                      停止前,唤醒等待所有线程
                        con_1.signalAll(); 
//                      con_0.signalAll();
                        return;
                    }
//                  唤醒指定状态的线程
                    con_1.signal();
//                  挂起自己
                    con_0.await();
                }catch(Exception e){}
                //最终动作
                finally{
                    lockdemo.unlock();
                }
            }
        }   
    };
    Runnable xian_1= new Runnable(){
        public void run() {
            while(flag){
                lockdemo.lock();
                try{
                    String str = li.size()+"by"+Thread.currentThread().getName()+"\r\n";
                    Thread.sleep(100);
                    li.add(str);
                    if(li.size()>=10){
                        flag =false;
                        System.out.println(li.toString());
                        con_0.signalAll();
                        return;
                    }
                    con_0.signal();
                    con_1.await();
                }catch(Exception e){}
                finally{
                    lockdemo.unlock();
                }
            }
        }   
    };

    public static void main(String[] args) {
        new LiZi().test();
    }
    public void test(){
//      开启新线程
        new Thread(xian_0).start();
        new Thread(xian_1).start();
    }
}
/*
    输出结果:
    [0byThread-0
    , 1byThread-1
    , 2byThread-0
    , 3byThread-1
    , 4byThread-0
    , 5byThread-1
    , 6byThread-0
    , 7byThread-1
    , 8byThread-0
    , 9byThread-1
    ]

 */
ReentrantReadWriteLock 示范(转)
package XianChen;

import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReadWriteLock;  
import java.util.concurrent.locks.ReentrantLock;  
import java.util.concurrent.locks.ReentrantReadWriteLock;  

/** 
 * Lockers 
 * 在多线程编程里面一个重要的概念是锁定,如果一个资源是多个线程共享的,为了保证数据的完整性, 
 * 在进行事务性操作时需要将共享资源锁定,这样可以保证在做事务性操作时只有一个线程能对资源进行操作, 
 * 从而保证数据的完整性。在5.0以前,锁定的功能是由Synchronized关键字来实现的。
 *  
 */  
public class LockDemo{
    public static void main(String[] args) throws Exception{  
        Lockers.testLockTest();  
        System.out.println("---------------------");  
        Lockers.testReadWriteLockTest();  
    }  
}
class Lockers {  

    /** 
     * 测试Lock的使用。在方法中使用Lock,可以避免使用Synchronized关键字。 
     */  
    public static class LockTest {  

        Lock lock = new ReentrantLock();// 锁  
        double value = 0d; // 值  
        int addtimes = 0; 

        /** 
         * 增加value的值,该方法的操作分为2步,而且相互依赖,必须实现在一个事务中 
         * 所以该方法必须同步,以前的做法是在方法声明中使用Synchronized关键字。 
           */
        public void addValue(double v) {  
            lock.lock();// 取得锁  
            System.out.println("LockTest to addValue: " + v + "   "  
                    + new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
            }  
            this.value += v;  
            this.addtimes++;  
            lock.unlock();// 释放锁  
        }  

        public double getValue() {  
            return this.value;  
        }  
    }  
    public static void testLockTest() throws Exception{  
        final LockTest lockTest = new LockTest();  
        // 新建任务1,调用lockTest的addValue方法  
        Runnable task1 = new Runnable(){  
            public void run(){  
                lockTest.addValue(55.55);  
            }  
        };  
        // 新建任务2,调用lockTest的getValue方法  
        Runnable task2 = new Runnable(){  
            public void run(){  
                System.out.println("value: " + lockTest.getValue());  
            }  
        };  
        // 新建任务执行服务  
        ExecutorService cachedService = Executors.newCachedThreadPool();  
        Future future = null;  
        // 同时执行任务1三次,由于addValue方法使用了锁机制,所以,实质上会顺序执行  
        for (int i=0; i<3; i++){  
            future = cachedService.submit(task1);  
        }  
        // 等待最后一个任务1被执行完  
        future.get();  
        // 再执行任务2,输出结果  
        future = cachedService.submit(task2);  
        // 等待任务2执行完后,关闭任务执行服务  
        future.get();  
        cachedService.shutdownNow();  
    }  

    /** 
     * ReadWriteLock内置两个Lock,一个是读的Lock,一个是写的Lock。 
     * 多个线程可同时得到读的Lock,但只有一个线程能得到写的Lock, 
     * 而且写的Lock被锁定后,任何线程都不能得到Lock。ReadWriteLock提供的方法有: 
     * readLock(): 返回一个读的lock  
     * writeLock(): 返回一个写的lock, 此lock是排他的。 
     * ReadWriteLockTest很适合处理类似文件的读写操作。 
     * 读的时候可以同时读,但不能写;写的时候既不能同时写也不能读。 
     */  
    public static class ReadWriteLockTest{  
        // 锁  
        ReadWriteLock lock = new ReentrantReadWriteLock();  
        // 值  
        double value = 0d;  
        int addtimes = 0;  

        /** 
         * 增加value的值,不允许多个线程同时进入该方法 
         */  
        public void addValue(double v) {  
            // 得到writeLock并锁定  
            Lock writeLock = lock.writeLock();  
            writeLock.lock();  
            System.out.println("ReadWriteLockTest to addValue: " + v + "   "  
                    + new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
            }  
            try {  
                // 做写的工作  
                this.value += v;  
                this.addtimes++;  
            } finally {  
                // 释放writeLock锁  
                writeLock.unlock();  
            }  
        }  
        /** 
         * 获得信息。当有线程在调用addValue方法时,getInfo得到的信息可能是不正确的。 
         * 所以,也必须保证该方法在被调用时,没有方法在调用addValue方法。 
         */  
        public String getInfo() {  
            // 得到readLock并锁定  
            Lock readLock = lock.readLock();  
            readLock.lock();  
            System.out.println("ReadWriteLockTest to getInfo   "  
                    + new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
            }  
            try {  
                // 做读的工作  
                return this.value + " : " + this.addtimes;  
            } finally {  
                // 释放readLock  
                readLock.unlock();  
            }  
        }  
    }  

    public static void testReadWriteLockTest() throws Exception{  
        final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();  
        // 新建任务1,调用lockTest的addValue方法  
        Runnable task_1 = new Runnable(){  
            public void run(){  
                readWriteLockTest.addValue(55.55);  
            }  
        };  
        // 新建任务2,调用lockTest的getValue方法  
        Runnable task_2 = new Runnable(){  
            public void run(){  
                System.out.println("info: " + readWriteLockTest.getInfo());  
            }  
        };  
        // 新建任务执行服务  
        ExecutorService cachedService_1 = Executors.newCachedThreadPool();  
        Future future_1 = null;  
        // 同时执行5个任务,其中前2个任务是task_1,后两个任务是task_2  
        for (int i=0; i<2; i++){  
            future_1 = cachedService_1.submit(task_1);  
        }  
        for (int i=0; i<2; i++){  
            future_1 = cachedService_1.submit(task_2);  
        }  
        // 最后一个任务是task_1  
        future_1 = cachedService_1.submit(task_1);  
        // 这5个任务的执行顺序应该是:  
        // 第一个task_1先执行,第二个task_1再执行;这是因为不能同时写,所以必须等。  
        // 然后2个task_2同时执行;这是因为在写的时候,就不能读,所以都等待写结束,  
        // 又因为可以同时读,所以它们同时执行  
        // 最后一个task_1再执行。这是因为在读的时候,也不能写,所以必须等待读结束后,才能写。  

        // 等待最后一个task_2被执行完  
        future_1.get();  
        cachedService_1.shutdownNow();  
    }  
}  
/*
    输出结果:
    LockTest to addValue: 55.55   05:12:40
    LockTest to addValue: 55.55   05:12:41
    LockTest to addValue: 55.55   05:12:42
    value: 166.64999999999998
    ---------------------
    ReadWriteLockTest to addValue: 55.55   05:12:43
    ReadWriteLockTest to addValue: 55.55   05:12:44
    ReadWriteLockTest to getInfo   05:12:45
    ReadWriteLockTest to getInfo   05:12:45
    info: 111.1 : 2
    info: 111.1 : 2
    ReadWriteLockTest to addValue: 55.55   05:12:46
 */

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值