多线程编程

来源:互联网 发布:反恐数据库 编辑:程序博客网 时间:2024/05/22 14:54


主要内容:

1. 进程和线程概述

2. 实现多线程的两种方法

3. 多线程安全问题

4. 单例设计模式在多线程编程中的体现

5. 线程间的通信

6. 停止线程及优先级介绍

7. 线程中java1.5新特性

进程和线程的概述:

定义:

进程:是一个程序在一个数据集合上的一次运行过程,每个一进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。一个程序在不同数据集合上运行,乃至一个程序在同样数据集合上运行多次,都是不同的进程。

线程:就是进程中的一个实体, 线程在控制着进程的执行,是被系统独立调度和执行的基本单位。不同线程共享着所在进程内的数据集合。

区别:

进程和线程的主要区别是它们对系统资源的管理方式不同。一个进程内必至少得有一个线程。

· 进程之间是独立的,这表现在内存空间、上下文环境上,而线程运行在进程空间内。一般来讲(不使用特殊技术),进程无法突破进程边界存取其他进程内的存储空间,而线程牌进程空间内,所以同一进程所产生的线程共享同一内存空间。

· 同一进程 中的两段代码不能够同时执行,除非引入线程。

· 线程是属于进程的,当进程退出时,该进程所产生的线程都会被强制退出并清除。

· 线程占用的资源要少于进程所占用的资源。因此采用多线程技术。

· 进程和线程都可以有优先级,而进程间可以通过IPC通信,但线程不可以。

实现多线程的两种方法:

方法一:继承Thread类
步骤:
1. 定义类继承Thread。
2. 复写Thread类中的run方法。之所以要覆盖Thread类中的run方法,是因为Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,就是run方法。
3. 通过Thread类建立线程对象,将定义类的子类对象作为参数传递给Thread类的构造函数。
4. 调用Thread类的start方法。
该步骤两个作用:启动线程,调用run方法。如果只调用run方法,仅仅是创建了线程,并没有运行。所以得调用start方法,先启动线程,再调用run方法。
通过一个卖票小程序实例代码演示该方法:
/*需求:现有100张票,需要两个窗口同时售票思路:1. 定义一个Ticket类,继承Thread2. 覆盖run方法,打印卖票的线程和票数3. 创建两个线程,启动start方法。*/class Ticket extends Thread{private int ticket = 100;Object obj = new Object();//覆盖run方法public void run(){while(true ){if(ticket > 0){System.out.println(Thread.currentThread().getName()+"..."+ticket--);}elsebreak;}}}class ThreadDemo{public static void main(String[] args){Ticket t = new Ticket();//创建两个线程并启动start方法new Thread(t).start();new Thread(t).start();}}

方法二:实现Runnable
步骤:
1. 定义类实现Runnable接口
2. 覆盖Runnable接口中的run方法
3. 通过Thread类建立线程对象,将定义类的子类对象作为参数传递给Thread类的构造函数。
4. 调用Thread类的start()方法。
通过一个银行存储小程序演示该方法:
/*需求:银行有一个金库,有两个储户存300元,1次存100,分3次存。  打印金库总和。思路:1. 明确哪些代码是多线程运行代码:储户存储操作2. 明确共享数据:银行总额3. 明确多线种运行代码中哪些语句是操作共享数据的。  */class Bank{private int sum;public void add(int n){sum +=n;System.out.println("sum="+sum);}}class Custom implements Runnable{private Bank b = new Bank();//覆盖run方法,分3次存public void run(){for(int i = 0; i<3; i++){System.out.println(Thread.currentThread().getName());b.add(100);}}}class BankThreadDemo{public static void main(String[] args){Custom c = new Custom();//两线程模拟两储户new Thread(c).start();new Thread(c).start();}}

两种创建线程方法的区别:
1.  一种是继承方式,一种是实现方式。而实现方式避免了单继承的局限性。因此在创建线程的一般都选择实现方式。
2.  两种方法的线程代码存放位置不同,一种存放在继承Thread类子类的run方法中,别一种是实现Runnable类的子类run方法中。
3. 多线程的随机性:谁抢到cpu资源谁执行,至于执行多长,CPU说的算,当然也可以控制。

多线程安全问题:

在上面两个示例中都存在多线程安全问题。
如在run代码前,加上Thread.sleep(10);就会出现卖错票的情况 :


出现多线程的安全问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完另一个线程参与进来执行,导致了共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行。
java对于多线程的安全问题提供了专业的解决方案:线程同步 
synchronized(object)
{
//需要被同步的代码
}
object如同锁,持有锁的线程才可以在同步中进行,必须保证同步中只有一个线程在运行。
同步的前提:
1. 必须要有两个或两个以上的线程,才需要线程。
2. 必须是多个线程使用同一个锁。
下面是将卖票程序加入线程同步后的代码:
class Ticket extends Thread{private int ticket = 100;Object obj = new Object();//覆盖run方法public void run(){while(true){//让操作共享数据的代码同步。synchronized(obj){if(ticket > 0){try{<span style="white-space:pre"></span>Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"..."+ticket--);}elsebreak;}}}}class ThreadDemo{public static void main(String[] args){Ticket t = new Ticket();//创建两个线程并启动start方法new Thread(t).start();new Thread(t).start();}}

synchronized不光只能同步代码块,还能修饰函数,即同步函数。
同步函数中的锁:
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,因此同步函数的锁就是this。
下面将银行存储程序加上同步函数后,代码示例:
class Bank{private int sum;//同步函数public synchronized void add(int n){sum +=n;try{Thread.sleep(10);}catch (Exception e){}System.out.println("sum="+sum);}}class Custom implements Runnable{private Bank b = new Bank();public void run(){for(int i = 0; i<3; i++){System.out.println(Thread.currentThread().getName());b.add(100);}}}class BankThreadDemo{public static void main(String[] args){Custom c = new Custom();//两线程模拟两储户new Thread(c).start();new Thread(c).start();}}

线程同步解决了多线程的安全问题,但同时也增加了系统资源的消耗。

思考:静态函数被同步,使用的是什么锁?
通过验证不再是this,因为static中也不存在this。
静态进内存是随着类加载,内存中本没有类对象,但是一定有该类的字节码文件对象。
因此静态的同步方法使用的锁是:该方法所在类的字节码文件所在对象:类名.class
思考:死锁是一个什么样的状态?
死锁:同步中嵌套同步,而且两个同步的锁还不同。就会造成程序死锁。死锁将导致程序卡死。
代码示例:
class DeadLock implements Runnable{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("if lockb");}}}elsesynchronized(MyLock.lockb){System.out.println("else lockb");synchronized(MyLock.locka){System.out.println("else locka");}}}}class MyLock{static Object locka = new Object();static Object lockb = new Object();}class ThreadDeadLock{public static void main(String[] args){Thread t1 = new Thread(new DeadLock(true));Thread t2 = new Thread(new DeadLock(false));t1.start();t2.start();}}

单例设计模式在多线程中的体现:

单例设计模式中的延时加载(懒汉式)在多线程访问时容易出现安全隐患:创建不只一个对象,因此需加上同步。
这里如果用同步函数,程序会有些低效,因此用双判断加上同步代码块
代码示例:
class Single implements Runnable{private Single(){}//因为要多线程访问,因此对象得是静态,以便共享private static Single s = null;public static Single getInstance(){//双判断加上同步代码块。if(s==null){synchronized(Single.class){if(s==null)s = new Single();}}return s;}public void run(){for(int i = 0 ;i<60; i++)System.out.println(Thread.currentThread().getName()+"...Single run");}}class ThreadSingleDemo{public static void main(String[] args){Single s1 = Single.getInstance();new Thread(s1).start();new Thread(s1).start();}}

线程间的通信:

线程间通信:就是多个线程在操作同一个资源,但是操作的动作不同。
线程间通信是依靠等待唤醒机制,涉及到的函数:
wait(); //让线程释放执行权
notify(); //唤醒有同一 个锁的等待线程
notifyAll(); //唤醒所有等待线程
等待和唤醒是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object中。
代码示例:
/*需求:让两个线程交互执行。*/class Res{private String name;private String sex;private boolean flag = false;public synchronized void set(String name, String sex){if(flag)try{this.wait();}catch(Exception e){}this.name = name;this.sex = sex;flag = true;//notify()不可以唤醒不同锁的同步线程,//这里需要唤醒,另一个锁的线程,因此得用notifyAll()this.notifyAll();}public synchronized void print(){if(!flag)try{this.wait();}catch(Exception e){}System.out.println(name+"..."+sex);flag = false;this.notifyAll();}}class Input implements Runnable{private Res r;Input(Res r){this.r = r;}public void run(){int f = 0;while(true){synchronized(r){if(f == 0)r.set("Jack","male");elser.set("小丽", "女");f = (f+1) % 2;}}}}class Output implements Runnable{private Res r;Output(Res r){this.r = r;}public void run(){while(true){synchronized(r){r.print();}}}}class ThreadTransportDemo{public static void main(String[] args){Res r = new Res();new Thread(new Input(r)).start();//new Thread(new Input(r)).start();//new Thread(new Output(r)).start();new Thread(new Output(r)).start();}}
执行效果:


思考:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
这些方法存在与同步中。使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

停止线程及优先级介绍:

停止线程:
stop方法已经过时,只有让run方法结束,才能停止线程。
开启多线程运行,运行代码通常是循环结构。那么只要控制循环,就可以让run方法结束,线程即结束。

特殊情况:
当线程处于冻结状态,程序就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除。
强制让线程恢复到运行状态,这样就可以操作标记,让线程结束。
Thread类中提供了interrupt方法。可满足此条件。

守护线程:setDaemon();  将线程变成后台线程

暂停线程:static void yield(); 暂停当前正在执行的线程对象,并执行其他线程。让线程拥有资源的频率平均。

线程的优先级:
优先级:代表抢资源的频率。高的频率高,但是不代表代的就抢不到。
级数乐范围:1-10级,默认5级
MAX_PRIORITY 10
MIN_PRIORITY 1
NORM_PRIORITY 5
可以通过 函数void setPriority(int newPriority);设置线程优先级。

线程中JDK1.5新特性

线程中JDK1.5新特性是针对synchronized的代码优化。
将同步synchronized替换成了显示的lcok操作。
将Object中的wait,notify,notifyall替换成了Condition对象,该对象可通过Lock锁获取。
代码示例中,实现了本方只唤醒对方的操作:
/*需求:使用新特性,实现两个线程的交互执行*/import java.util.concurrent.locks.*;class Resource{private String name;private int num = 1;boolean flag = false;//设置锁private Lock lock = new ReentrantLock();private Condition condition_pro = lock.newCondition();private Condition condition_con = lock.newCondition();public void set(String name)throws InterruptedException{//获取锁lock.lock();this.name = name+"--" +num++;while(flag)condition_pro.await();//让producer处于等待状态try{System.out.println(Thread.currentThread().getName()+"+生产者_"+this.name);flag = true;condition_con.signal();//唤醒consumer等待线程}finally{//释放锁lock.unlock();}}public void show()throws InterruptedException{lock.lock();while(!flag)condition_con.await();try{System.out.println(Thread.currentThread().getName()+"+消费者+"+this.name);flag = false;condition_pro.signal();}finally{lock.unlock();}}}class Producer implements Runnable{Resource res;Producer(Resource res){this.res = res;}public void run(){while(true)try{res.set("商品");}catch (InterruptedException e){}}}class Consumer implements Runnable{Resource res;Consumer(Resource res){this.res = res;}public void run(){while(true)try{res.show();}catch (InterruptedException e){}}}class ThreadTransportDemo3{public static void main(String[] args){Resource r = new Resource();new Thread(new Producer(r)).start();//new Thread(new Producer(r)).start();//new Thread(new Consumer(r)).start();new Thread(new Consumer(r)).start();}}


0 0