黑马程序员 - 多线程

 

 

多线程
进程:一个正在执行中的程序,每一个进行执行,都有一个执行顺序,该顺序就是一个执行路径,或者加一个执行单元
线程:就是进程中的一个独立的执行路径,一个进程中至少有一个线程。
java vm启动的时候会有一个进程java.exe。
该进程中至少一个线程负责执行java程序的执行,
而且这个线程运行的代码存在于main方法中。
该线程称之为主线程。
扩展:其中更细节说明java vm启动不止一个线程,还有赋值垃圾回收机制的线程。

创建线程的实现步骤:
方法一
1.定义一个类继承Thread类
2.覆写Thread类中的run方法
3.在主函数中直接创建Thread的子类
4.运行start调用线程

方法二
1.定义一个类实现Runnable接口
2.在此类中写入需要多线程运用的代码
3.将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
4.运行start调用线程

以下代码简单演示创建线程的两种方式

class Demo1 extends Thread {                //第一种方式继承Thread类
    private String name;
    Demo1(String name){
        this.name = name;
    }
    public void run() {                     //覆写run方法    
        for (int i=1;i<10 ;i++ ){           //循环9次打印当前线程名字
            System.out.println(name+"...."+i+Thread.currentThread().getName());
            //Thread.currentThread().getName() 获取 当前 运行中 线程的名字
        }
    }
}
class Demo2 implements Runnable {           //第二种方式,实现Runnable接口
    private String name;
    Demo2(String name){
        this.name = name;
    }
    public void run(){                      //覆写run方法
        for (int i=1;i<10 ;i++ ){           //循环9次打印当前线程名字
            System.out.println(name+"...."+i+Thread.currentThread().getName());
            //Thread.currentThread().getName() 获取 当前 运行中 线程的名字
        }
    }
}
public class AThreadDemo {
    public static void main(String[] args) {
        /*
        创建线程的目的是为了开启一条新的执行路径,去运行指定的代码和其他代码实现同时运行。
        而运行的 指定代码就是这个执行路径的任物。
        jvm创建的主线程的任物都定义在主函数中。

        而自定义的新城用于描述线程,线程是需要任物的,所以Thread类也对任物的描述。
        这个任务就通过Thread类中的run方法来体现,也就是run方法就是封装自定义线程运行任务的函数。
        run方法中定义的就是线程要运行的任物代码。
        */
        Demo1 d1 = new Demo1("d1");
        Demo2 d2 = new Demo2("d2");
        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d2);
        t1.start();                   //开启多线程,调用run方法
        t2.start();
        //t1.run();
        //t2.run();                   //直接调用run方法,没有线程的随机性.        
    }
}

运行结果

由运行结果可知,d1线程和d2线程交替运行,两个线程均创建成功,并在主线程中调用正常。

线程安全

线程安全问题产生的原因:
1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算就会导致线程安全问题的产生。
问题解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不能参与运算。
必须当前线程吧这些代码都执行完毕后,其他线程才可以产于运算。
java中用同步代码块或同步函数解决该问题。
同步代码块的格式:
synchronized(对象) //此处对象为任意对象,可直接用Objct
{
需要被同步的代码
}
同步函数就是用synchronized修饰函数
注意:定义同步函数必须注意需要同步的代码是哪些。
同步的前提:同步中必须有多个线程,并使用同一个锁(即使用的同一个对象)
如果同步函数被静态修饰后,使用的锁是什么?
通过验证,发现不是this,因为静态方法不可以定义this
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该类对象的类型是Class。
静态的同步方法,使用的锁是该方法所在类的字节码所在对象。

多线程练习
需求:有20张票,需要在4个窗口卖完
编程注意:必须保证各个线程是共用的一个num数据

以下代码演示同步代码块解决安全问题的方法

步骤:
1:定义一个Thread类,包含了对卖票的代码
2:在定义Thread类时,将票数num定义为静态变量,从而保证不同线程运行时是对相同的100张票进行调用。
3:在主函数中定义4个线程对象,并用start方法开启线程。

//实现代码一:(应用同步代码块)
class Ticket1 extends Thread{
    private static int num = 20;    //将num储存在方法区,由4个线程共用。
    Object obj = new Object();       //对象必须定义在run外
    public void run(){
        while (num>0){
            synchronized(obj){       //此处需要被同步的代码存在多条,必须定义同步代码块        
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+"..."+num--);
                }
            }
        }
    }
}
public class BThreadTicket1{
    public static void main(String[] args){
        Ticket1 t1 = new Ticket1();
        Ticket1 t2 = new Ticket1();
        Ticket1 t3 = new Ticket1();
        Ticket1 t4 = new Ticket1();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

运行结果

由运行结果可以看出,4个线程交替运行,出售这20张票。

第二种实现方式

步骤:
1:定义一个包含票数,及买票功能的对象Ticket2,并对其扩展Runnable接口,在其中覆写run方法。
2:在主函数中创建票对象
3:在主函数中创建4个线程对象,并将票对象作为它们的构造函数参数
4:运行start方法开启线程

class Ticket2 implements Runnable  {
    private int num = 20;    
    public void run() {
        while (num>0){
            synchronized(Ticket2.class){
                if(num>0){
                    try{Thread.sleep(10);}catch(Exception e){}
                    System.out.println(Thread.currentThread().getName()+"..."+num--);
                }
            }
        }
    }
    //定义同步函数,函数需要被对象调用,那么函数都有一个所属对象引用,就是this
    //所以同步函数使用的锁是this    
}
public class BThreadTicket2 {
    public static void main(String[] args){
        //单独建立一个票的对象,4个线程均是对该票对象的调用
        Ticket2 t2 = new Ticket2();
        Thread th1 = new Thread(t2);
        Thread th2 = new Thread(t2);
        Thread th3 = new Thread(t2);
        Thread th4 = new Thread(t2);
        th1.start();
        th2.start();
        th3.start();
        th4.start();
    }
}

运行结果:

由运行结果可以看出,4个线程交替运行,出售这20张票。

多线程应用
需求:
生产者生成15个产品 ,并且每生产一个产品,消费者就消费一个。

此代码用了新的同步方式,定义锁;并通过定义标记,使消费线程和成产线程交替运行。

import java.util.concurrent.locks.*;
class Resource{                                  //定义商品
    private String name;                         //定义成员变量
    private int count = 1;                        
    private boolean flag = false;                //定义标记
    //Lock方法
    private Lock lock = new ReentrantLock();     //定义锁
    private Condition condition_pro = lock.newCondition();    
    private Condition condition_cou = lock.newCondition();
    public void set(String name) throws InterruptedException //定义生产动作
    {
        if (count<15){
            lock.lock();                            //获取锁
            try{
                while (flag)                        //注意此处用while是因为需要循环判断
                    condition_pro.await();          //使当前线程等待
                this.name = name;                   //生成一个产品
                System.out.println(Thread.currentThread().getName()+name+"生产商品"+(count++)+"----");
                flag = true;                        //改变标记,是下次循环时能够让生产者等待
                condition_cou.signal();             //唤醒另一个在等待的线程
            }
            finally {
                lock.unlock();                      //释放锁
            }
        }    
    }
    public void show() throws InterruptedException  //定义消费动作
    {
        if (count<15){
            lock.lock();                            //获取锁
            try{
                while (!flag)                      
                    condition_cou.await();          //让当前线程等待
                System.out.println(Thread.currentThread().getName()+"----消费商品"+name+(count-1));
                flag = false;                        //消费一个对象,并改变标记
                condition_pro.signal();                 //唤醒另一个正在等待的线程
            }
            finally {
                lock.unlock();                       //释放锁
            }
        }
    }
}
class Producter implements Runnable{           //定义生成者
    private Resource r;
    Producter (Resource r){                    //将商品传给生产者   
        this.r = r;
    }
    public void run(){                         //覆写run方法
        while (true){
            try{
                r.set("商品");                 //调用生产动作,生产商品
            }
            catch (InterruptedException e){
            }
        }
    }
}
class Coustomor implements Runnable{           //定义消费者
    private Resource r;                        
    Coustomor (Resource r){                       //将商品传给消费者
        this.r = r;
    }
    public void run(){                         //覆写run方法
        while (true){
            try{
                r.show();                      //调用消费动作消费商品
            }
            catch (InterruptedException e){
            }
        }
    }
}
public class CThreadRe{
    public static void main(String[] args) {
        Resource r = new Resource();            //定义商品对象
        Producter pro = new Producter(r);       //将商品传给生产者
        Coustomor cou = new Coustomor(r);        //将商品传给消费者
        Thread t1 = new Thread(pro);            //定义两个生产线程
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(cou);            //定义两个消费线程
        Thread t4 = new Thread(cou);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

运行结果

上面代码实际上是运用流多线程通讯的思想。

线程间通讯:
其实就是多个线程在操作同一个资源,但操作动作不同
实现核心:
多线程的等待唤醒机制

步骤:
1:设置flag标记变量
2: 在同步代码块中设置 锁名.wait() 而且锁名.wait()必须被 try{}catch{}(或是锁对象.await())

3: 在同步代码块中设置 锁名.notify().(或是锁对象.singal())
注意:设置wait() notify()时,运用flag变量做控制

posted @ 2015-08-20 21:29  koibiki  阅读(152)  评论(0编辑  收藏  举报