线程篇

来源:互联网 发布:防止sql注入 编辑:程序博客网 时间:2024/05/20 19:59

一、概述
    线程的前提是有进程,所以说线程之前的了解进程的概念及其与线程的联系。
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:是进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程,每个独立线程代表一个独立操作。线程隶属于某个进程,它自身没有入口和出口;也不能自动运行,要由进程启动执行,进行控制。
两者的区别:
    1)进程在执行过程中拥有独立的内存单元,而多个线程共享内存。
    2)线程都有自己默认的名称。Thread-编号,该编号从 0 开始。

多线程:一个进程中有多个线程,称为多线程。例如:虚拟机启动的时候就是多线程,JVM 启动时至少有一个主线程和一个负责垃圾回收的线程。
主线程:在 JVM 启动时会有一个进程 Java.exe。该进程中至少一个线程负责 Java程序的执行,而这个线程运行的代码存在于main 方法中,则该线程称为主线程。
多线程的意义:多个程序同时执行,从而提高程序运行效率。
线程的弊端:线程太多会导致效率的降低,因为线程的执行依靠的是 CPU 的来回切换。
多线程原理:
    当运行多线程程序时发现运行结果每次都不同。因为多个线程都在获取CPU的执行权。CPU执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序在运行。(多核除外)CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程运行行为看做在互相抢夺CPU的执行权。这就是多线程的一个特性,随机性。谁抢到谁执行,至于执行多长时间,CPU说了算。后期可以控制,但是比较困难。系统可以同时执行多个任务,应用程序内的多任务并行就是依靠多线程实现的。
二、线程创建
创建线程有两种方式:继承Thread类、实现Runnable接口。
1、继承Thread类:
步骤:
    1)定义类继承 Thread。
    2)复写 Thread 中的 run()方法(将线程要运行的代码存放在该 run()方法中) 。
    3)调用线程的 start()方法启动线程,从而调用 run()方法。
Note:
    Thread 类用于描述线程。该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run()方法。因此,run()方法的目的就是将自定义的代码存储在 run()方法,让线程运行。在 main 方法中调用 start()方法作用是开启线程并执行线程的 run ()方法。而在 main 方法中直接调用 run ()方法,仅仅是对象调用方法,创建了线程,并没有运行。
2、实现Runnable接口:
步骤:
    1)定义类实现 Runnable 接口。
    2)覆盖 Runnable 接口中的 run()方法(将线程要运行的代码存放在该 run()方法中 )。
    3)通过 Thread 类建立线程对象。
    4)将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
    5)调用 Thread 类的 start 方法开启线程从而调用 Runnable 接口子类的 run()方法。
Note:
    创建(声明)一个实现 Runnable 接口的类对象。类必须定义一个称为 run 的无参方法。此外 Runnable 为 Thread 的子类的类提供了一种激活方式。然后该类实现 run()方法,可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
下面演示两种方式创建线程:

class Demo extends Thread {    public void run() {        for (int i = 0; i < 60; i++) {            System.out.println(Thread.currentThread().getName() + "::Demo run ---");        }    }}class Demo1 implements Runnable {    public void run() {        for (int i = 0; i < 60; i++) {            System.out.println(Thread.currentThread().getName() + "::Demo1 run ---");        }    }}public class ThreadDemo {    public static void main(String[] args) {        Demo d = new Demo();        d.start();                Demo1 d1 = new Demo1();        new Thread(d1).start();;        for (int i = 0; i < 60; i++) {            System.out.println(Thread.currentThread().getName() + "::Main run ---");        }    }}

结果如下所示:


3、两种创建方式的区别
    继承Thread:线程代码存放在Thread子类run()方法中。
    实现Runnable:线程代码存放在接口子类run()方法中。    
    实现方式的好处:避免了单继承的局限性。定义线程时,建议使用实现方式。
4、线程运行状态
    被创建:等待启动,调用start启动。
    运行状态:具有执行资格和执行权。
    临时状态(阻塞):有执行资格,但是没有执行权。
    冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep()方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
    消忙状态:stop()方法,或者run()方法结束。
图解如下:


三、线程同步机制
1、多线程的安全问题原因:当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完, 另一个线程参与进来执行。导致共享数据的错误。
2、解决办法:对多条操作共享数据的语句,只能让一个线程执行完。在执行过程中,其他线程不可以参与执行———同步。
Note:
    同步的前提:必须要有两个或者两个以上的线程,必须是多个线程使用同一个锁,必须保证同步中只能有一个线程在运行。
    同步优点:解决了多线程的安全问题。
    同步弊端:多个线程需要判断锁,较为消耗资源。
在多线程操作共享数据的运行代码中,需要加锁的两种情况:
    1)含有选择判断语句
    2)含 try(){}catch(){}语句
理解:对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU 执行权,也进不去,因为没有获取锁。Java 对于多线程的安全问题提供了专业的解决方式:同步代码块、同步函数。
3、同步代码块:
格式:
    synchronized(对象)
    {
          //需要被同步的代码
    }
示例:

/* * 创建4个线程同时卖100张票 *  */class Ticket implements Runnable {private int num = 100;public void run() {while (true) {//同步代码块,利用this作为锁,或者用Class对象作为锁,只要保证锁唯一即可synchronized (this) {if (num > 0) {try {//延时是为了让4个线程执行权趋于均衡Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Ticket" + num--);}}}}}public class ThreadDemo3 {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);Thread t4 = new Thread(ticket);t1.start();t2.start();t3.start();t4.start();}}

4、同步函数
格式:在函数上加上synchronized修饰符即可。
Note:那么同步函数用的是哪一个锁呢?
    函数需要被对象调用。那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
示例:

/* * 创建4个线程同时卖100张票 *  */class Ticket implements Runnable {private int num = 100;public synchronized void run() {while (true) {// 同步代码块,利用this作为锁,或者用Class对象作为锁,只要保证锁唯一即可// synchronized (this) {if (num > 0) {try {// 延时是为了让4个线程执行权趋于均衡Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Ticket" + num--);}// }}}}public class ThreadDemo3 {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);Thread t4 = new Thread(ticket);t1.start();t2.start();t3.start();t4.start();}}
上述两个示例结果都会随着运行而改变,但是基本完成100张车票的售卖,结果如下:


5、静态函数的同步
    如果同步函数被静态修饰后,使用的锁是什么呢?
    通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:类名.class 该对象的类型是Class这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象——类名.class
示例:

class Single {private static Single s = null;private Single() {}public static Single getInstance() {//解决效率问题if (s == null) {//加锁synchronized (Single.class) {//判断s是否有非空指向if (s == null) {s = new Single();}}}return s;}}
6、死锁
多线程程序中,当同步中嵌套同步时,就有可能出现死锁现象。
示例:
class DeadLock implements Runnable {private boolean flag;DeadLock(boolean flag) {this.flag = flag;}public void run() {if (flag) {synchronized (MyLock.locka) {System.out.println("...if locka");synchronized (MyLock.lockb) {System.out.println("...else lockb");}}} else {synchronized (MyLock.lockb) {System.out.println("...else lockb");synchronized (MyLock.locka) {System.out.println("...else locka");}}}}}//自定义锁类class MyLock {static MyLock locka = new MyLock();static MyLock lockb = new MyLock();}public class ThreadDemo7 {public static void main(String[] args) {DeadLock dlt = new DeadLock(true);DeadLock dlf = new DeadLock(false);Thread t1 = new Thread(dlt);Thread t2 = new Thread(dlf);t1.start();t2.start();}}
结果如下所示:

四、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
示例:

/* * 练习:一个线程存取姓名和性别,另一个线程打印出该两项 *  *///资源class Res {String name;String sex;boolean flag;//判断资源是否存在的标识符public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}//存放线程class Input implements Runnable {Res r = new Res();Input(Res r) {this.r = r;}public void run() {int x = 0;while (true) {synchronized (r) {//判断有资源就等待if (r.flag) {try {r.wait();} catch (InterruptedException e) {e.printStackTrace();}}//交替存入caven和琴琴这两个人的信息if (x == 0) {r.name = "caven";r.sex = "man";} else {r.name = "琴琴";r.sex = "女女女女女";}x = (x + 1) % 2;r.flag = true;r.notify();//唤醒其他线程}}}}//取出线程class Output implements Runnable {private Res r;Output(Res r) {this.r = r;}public void run() {while (true) {synchronized (r) {//判断资源为空就等待if (!r.flag) {try {r.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(r.name + "...." + r.sex);r.flag = false;r.notify();}}}}public class ThreadDemo1 {public static void main(String[] args) {Res r = new Res();Input in = new Input(r);Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(out);t1.start();t2.start();}}
部分结果如下所示:

五、等待唤醒机制
1、原因:因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
2、说明:Object 中的方法 notify()、notifyAll()、wait()等都在同步中使用。为什么这些操作线程的方法要定义 Object 类中呢?
    答:因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上 notify 唤醒。不可以对不同锁中的线程进行唤醒。 等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义 Object 类中。
3、wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行wait(0)调用一样。当前线程必须拥有此对象锁(监视器)。该线程发布对此监视器的所有权并等待,直到其他线程通过调用notify()方法,或notifyAll()方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
Note:
    wait()方法在使用时只能 try(处理异常)不能抛异常。当出现多个线程同时操作一个对象时时,要用 while 循环和 notifyAll();比较通用的方式。定义while判断标记是为了让被唤醒的线程再一次判断标记。定义 notifyAll(), 是因为需要唤醒对方线程。只用 notify(),容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

六、JDK5.0 升级版线程功能
1、Lock 接口:
    实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构, 可以具有差别很大的属性, 可以支持多个相关的 Condition对象(可以理解为 lock 替代了 synchronized)。
2、ReentrantLock类:
    Lock 的子类。一个可重入的互斥锁,它具有与使用  synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
Note:JDK1.5  中提供了多线程升级解决方案:显示的锁机制,以及显示的锁等待唤醒机制。
    1)将同步 Synchronized 替换成现实 Lock 操作。
    2)将 Object 中的 wait,notify notifyAll,替换了 Condition 对象。该对象可以 Lock 锁进行获取。
    3)释放锁的动作一定要执行。
JDK5.0新特型改写后的生产者消费者案例:

/* * JDK5.0 升级后的生产者消费者案例演示 */import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;//资源class Resources {private String name;private int count = 1;private boolean flag = false;<span><span class="comment">//创建两Condition对象,分别来控制等待或唤醒本方和对方线程</span><span> </span></span>private Lock lock = new ReentrantLock();private Condition conSet = lock.newCondition();private Condition conOut = lock.newCondition();public/* synchronized */void set(String name) {lock.lock();//上锁try {while (flag) {try {/<span><span class="comment">/本方等待</span><span>  </span></span>conSet.await();/* wait(); */} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + "---" + count++;System.out.println(Thread.currentThread().getName() + "生产者"+ this.name);flag = true;conOut.signal();// <span><span class="comment">//唤醒对方</span><span>  </span></span>/* this.notifyAll(); */} finally {lock.unlock();//解锁动作,一定要执行}}public/* synchronized */void out() {lock.lock();try {while (!flag) {try {conOut.await();/* wait(); */} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "--消费者--"+ this.name);flag = false;conSet.signal();/* this.notifyAll(); */}} finally {lock.unlock();}}}// 生产者class Producers implements Runnable {private Resources res;public Producers(Resources res) {this.res = res;}public void run() {while (true) {res.set("商品");}}}// 消费者class Comsumers implements Runnable {private Resources res;public Comsumers(Resources res) {this.res = res;}public void run() {while (true) {res.out();}}}public class ThreadDemo3 {public static void main(String[] args) {Resources res = new Resources();Producers pro = new Producers(res);Comsumers con = new Comsumers(res);Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}
部分结果如下所示:

3、停止线程(JDK5.0之后)
原理:只有让run()方法结束。
两种方式:
    1)定义循环结束标记:因为线程运行代码一般都是循环,只要控制了循环,就可以结束 run(),也就线程结束。
    2)使用 interrupt(中断)方法:该方法结束线程的冻结状态,使线程回到运行状态中来。
Note:stop()方法已过时,不再使用。
示例:

class StopThread implements Runnable {private boolean flag = true;// 改变flagpublic void changeFlag(boolean flag) {this.flag = flag;}public synchronized void run() {int i = 0;// 利用flag来标记线程是否继续// 特殊情况:// 当线程处于冻结状态的时候就不会读取到标记,即线程结束不了while (flag) {try {wait();} catch (Exception e) {System.out.println(Thread.currentThread().getName()+ "Exception...");changeFlag(false);}System.out.println(Thread.currentThread().getName() + "...." + i++);}}}public class ThreadDemo4 {public static void main(String[] args) {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.start();t2.start();int num = 0;while (true) {if (num++ == 60) {st.changeFlag(false);// 利用interrupt来结束线程t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName() + "...." + num);}}}
结果如下所示:

七、其他方法
1、守护线程
    setDacmon(Boolean on)    //将该线程标记为守护线程或用户线程。
Note:Thread 类该方法必须在启动线程前调用,当正在运行的线程都是守护线程时,java 虚拟机退出。该方法首先调用 checkAccess()方法,且不带任何参数,可能抛出SecurityException(在当前线程中)。主线程为前台线程,守护线程可以理解为后台线程。后台线程特点:开启后,与前台线程共同抢劫 CPU 的执行权运行。当所有的前台线程结束后,后台线程会自动结束。
2、join()方法
1)概述:Thread 类的final修饰的方法,等待线程终止。该方法抛异常 InterruptedException抢夺 CPU 执行权(主线程要等待 join ()执行的线程执行结束,再恢复继续运行)。一般临时加入线程时,使用 join()。
2)特点:当 A 线程执行到了 B 线程的.join()方法时,A 就会等待。等 B 线程都执行完,A才会执行。join 可以用来临时加入线程执行。
3、优先级和 yield()方法
在Thread类覆盖了toString()方法。
    String toString()    //返回该线程的字符串表现形式。 包括线程名称、优先级和线程组。
    static void yield()  //暂停当前正在执行的线程对象,并执行其他线程。减少线程的执行频率。
ThreadGroup 类:线程组。表示一个线程的集合。
优先级:代表抢资源的频率。范围:1——10。只有 1(MIN_PRIORITY)、5(NORM_PRIORITY)、10(MAX_PRIORITY)最明显。所有线程(包括主线程)默认优先级是 5。
    setPriorty(int newPriority)    //更改线程的优先级。
    eg:   t1.setPriority(Thread.MAX_PRIORITY);    //设置 t1 的线程优先级为 10

   本篇幅所描述的仅代表个人看法,如有出入请谅解。

------  Java培训Android培训IOS培训.Net培训、期待与您交流! ------

0 0
原创粉丝点击