Java的多线程

Java的多线程

多线程是Java里一个非常重要的技术,几乎所有用Java开发的软件都会用到这个技术,所以它非常的重要。

1. 多线程是什么?

我们必须先来说另一个概念,那就是进程,大家应该都还比较熟悉,打开我们的window的任务管理器,打开进程栏,里面排列的就是一个个进程,进程也就是我们正在执行的程序,包括系统程序和我们自己运行的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径或者说是控制单元,而线程就是进程中的一个独立的控制单元,它控制着进程的执行,每一个进程中都至少有一个线程,就拿我们的java虚拟机来说,每次启动都会开启我们的主线程,还有底层的垃圾回收器也是会开启的,垃圾回器它也是一个线程,垃圾回收器和主线都在同时运行这就是多线程。

2. 多线程的的作用

多线程主要就是提高运行效率,想一想如果一个应用程序一次只能做一件事情那该是有多么痛苦,就像我们在用杀毒软件的时候,我们可以一边杀毒,一边清理垃圾,这就是多线程的好处,我们可以同时做很多事情,效率大大增加,在这里我要说一点就是,我们看到的计算机多程序同时运行,其实它们并不是同时在运行,只是因为计算机在各个程序之间进行着高速的切换动作,以至于我们觉得它们是同时再运行。

3. 那我们该怎样来创建多线程呢?

//创建方式一
class demo extends Thread //继承Thread类
{   
    public void run()//实现run方法
    {
        for (int i=0;i<100 ;i++ )
        {
            System.out.println("i="i);
        }
    }
}

class demo01 
{
    public static void main(String[] args) 
    {
        Thread t = new demo();//建立线程    
        t.start();//开启线程
    }
}

这是第一种创建方式
创建一个Demo类去继承Thread类,并复写Thread类中run方法,我们需要运行的程序就放到run方法中,然后在主函数中用Thread方法创建Demo类的对象,最后用对象调用start方法启动线程。

//创建方式二
class demo implements Runnable// 实现Runnable接口
{
    private int i=100;
    public void run()//复写run方法
    {
        for (int i=0;i<100 ;i++ )
        {
            System.out.println("i="+i);
        }
    }
}
class demo02 
{
    public static void main(String[] args) 
    {
        Runnable r = new demo();//创建Runnable对象
        Thread t1 = new Thread(r);//Runnable对象做参数
        Thread t2 = new Thread(r);  
        t1.start();//启动线程
        t2.start(); 
    }
}

这是第二种创建方式
创建一个threadDemo类去实现Runnable接口,并复写Runnable接口中run方法,我们需要运行的程序就放到run方法中,然后在主函数中创建threadDemo类的对象,再Thread方法创建对象,把threadDemo类的对象作为参数的形式传到Thread的构造函数的参数里,最后用Thread对象调用start方法启动线程。


两种线程创建方式的比较
这两种方式都可以创建多线程,继承Thread类创建多线程就不能去继承别的类,而且Thread类创建多个线程需要创建多个对象。而去实现Runnable接口的方法创建的时候,可以用这个类创建一个对象用Thread类同时开启多个线程,为了提高程序的拓展性,我们一般会用第二种方法创建线程。


4.多线程存在的安全问题和解决方案(同步和锁)
通过一个买票程序来说明安全问题。

class demorunnable implements Runnable
{
    private int i=100;//卖100张票
    public void run()
    {
        while(true)
        {
            if(i>0)//判断票是否卖完
            {
                try{Thread.sleep(10);}catch(Exception e){}
                //为了看到安全问题 用手动的方式让线程在这里停10毫秒
                      System.out.println(Thread.currentThread().getName()+"---- "+(i--));
    //每个线程都带着自己的编号和票号一起打印,打印之后票号递减
            }
            else//卖完就结束
                return;
        }
    }
}
class demo03 
{
    public static void main(String[] args) 
    {
        Runnable r = new demorunnable();//创建对象
        Thread t1 = new Thread(r);//用3个窗口来买票
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();//3个线程启动
        t2.start();
        t3.start();
    }
}

程序运行的结果

我们通过程序的运行结果来说明问题,运行的结果里出现了0和-1号票,在现实中是不能出现种问题的!
现在我就来分析一下,这里除了主线程之外我们另外开启了3条买票线程,这3条线程是怎么运作的呢?cup只能执行一个线程(我们这里不考虑多核CPU),所以在程序运行的时候,cup就是在不停的切换这3条线程执行,它们都有可能被cup执行到。这个程序我们做了一个睡眠sleep动作,让每个线程从这里过的时候就停一会,当到最后0号线程拿着1号票进来,然后就停在那里,cup把执行权给了 1号线程,1号线程判断票号为1,也进来了,同样的2号线程也进来了,然后它们又一个个打印了,第一个打印的就输出1号票,接下来这个线程就输出了0号票,最后的就输出了-1号票!

怎么来解决这个问题呢?

那我们就要说到另外一个概念就是同步和锁,还是刚刚那段代码,我们加一个同步再看看!

//一个多线程卖票的程序(已经加了同步)
class demorunnable implements Runnable
{
    private int i=500;//卖100张票
    public void run()
    {
        while(true)
        {
            synchronized(this)//这个就是同步代码块,括号里的this就是锁
            {
                if(i>0)//判断票是否卖完
                {   
                        try{Thread.sleep(10);}catch(Exception e){}
                    //为了看到安全问题 用手动的方式让线程在这里停10毫秒
                    System.out.println(Thread.currentThread().getName()+"---- "+(i--)); 
                }

                else//卖完就结束
                    return;
            }       
        }
    }
}
class demo03
{
    public static void main(String[] args) 
    {
        Runnable r = new demorunnable();//创建对象
        Thread t1 = new Thread(r);//用3个窗口来买票
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}

同步代码块的格式就是这样子:
synchronized(对象)
{
需要同步的代码
}
大括号里写我们需要同步的代码,小括号里的对象就是锁,他必须是一个对象,每一个线程执行到这里时,会先拿锁进去,然后进去读同步代码块里面的代码,如果这时别的线程夺取到执行权也来到同步代码这里,它也会判断锁,判断这个锁已经被拿进去了,它就会释放执行权,让别的线程执行,就这样一直要等到同步代码里面的线程执行完里面的语句出来之后,别的线程才能拿到锁,进到同步代码块里去执行。

还有一点要说明的就是,要想达到同步的效果,这几个线程必须用同一个锁(也就是同一个对象),以我们这个买票程序为例:我用的是一个this锁,就是demorunnable类的一个对象,因为我3条线程用的都是这一个demorunnable对象,所以可以达到同步的效果,特别注意的是同步代码块如果是在静态函数中;还有就是同步代码块中的代码一定是需要同步的代码,也就是那些操作共同变量的语句,不需要同步的代码也放在里面的话会影响效率。

同步的另一种写法:同步函数!
如果一个函数里面所有的语句都是需要同步的话,就把synchronized关键字直接写到函数上,如public synchronized void(){}这样子整个函数都会变成同步的。
有人就会问这个同步函数怎么没有锁?有锁,是有锁的,所就是调用这个函数的对象,也就是this。有一点要注意,静态函数的锁不是对象,因为静态是随着类的加载而加载,那个时候还没有对象,只有类,所以锁就是用了该类的字节码文件对象,类名.class。

5.线程的五种状态和等待唤醒机制

5.1这五种状态分别是:创建、运行、临时阻塞、冻结(又分为睡眠和等待两种状态)、消亡五种状态,我就结合下面这张图来说一说五种状态并介绍一些线程的方法。
这里写图片描述
首先是创建就不多说了,运行状态就是线程抢到了cpu的执行权运行了,消亡就是线程直接被干掉了,用的是stop方法,这种干掉就是结束了线程不能再被运行;

冻结状态是用sleep和wait方法,sleep方法里面一般会加一个long类型的参数,就是冻结的时间,时间一结束就会去到临时状态等着cpu的执行权;

wait方法和notify方法是一对,当线程用了wait方法等待之后就会冻结,这个时候必须要notify方法来把它唤醒 ,唤醒之后又会回到临时状态去等待cpu的执行权;因为cpu(单核)一次只能运行一个线程,所以如果程序里有3条线程,那么肯定只有一条线程在运行,另外两条线程如果没有在冻结状态的话,肯定就在临时阻塞状态里等待执行!

5.2线程的等待唤醒机制

为什么会出现这个机制?这个机制有什么用?

我们通过一个生产消费手机的程序来说明这个问题,程序是这样子的,我们会创建多个线程去生产手机,然后又会创建多个线程去消费手机,生产方法和消费方法我都用不同的代码去实现,它们都是各自同步的,但是这里也会出现一个安全问题,就是 生产和消费有多个,它们都在抢夺cpu的执行权,这个时候可能就是生产出一个手机之后,消费线程去消费,消费一次之后呢,消费线程又抢到的执行权再去消费一次,这样子就是变成一个手机卖给了两个人,这时我们就需要用等待唤醒机制了,让生产线程生产一个手机之后就等待,等到消费线程消费之后让消费线程唤醒生产线程,消费线程自己等待,就是这样子让它们相互等待唤醒以确保安全问题。

下面就是代码

/*手机的生产与消费*/
class demo04 
{
    public static void main(String[] args) 
    {
        product p = new product();//产品对象
        runDemo r1 =new runDemo(p,"苹果公司");//两个生产者
        runDemo r2 =new runDemo(p,"小米公司");
        conDemo r3 =new conDemo(p,"土豪");//两个消费者
        conDemo r4 =new conDemo(p,"屌丝");
        Thread t1 = new Thread(r1);//加入
        Thread t2 = new Thread(r2);
        Thread t3= new Thread(r3);
        Thread t4 = new Thread(r4);
        t1.start();//4个线程开启
        t2.start();
        t3.start();
        t4.start();
    }
}
class runDemo implements Runnable//生产者
{
    private product p;
    private String name;
    runDemo(product p,String name)
    {
        this.name = name;
        this.p = p;
    }
    public void run()//复写run方法
    {
        while(true)//消费
        p.pro(name);//生产手机
    }
}
class conDemo implements Runnable//消费者
{
    private product p;
    private String name;
    conDemo(product p,String name)
    {
        this.name = name;
        this.p = p;
    }
    public void run()//复写run方法
    {
        while(true)//循环
        p.con(name);//消费手机
    }
}
class product// 产品
{
    private String name;//生产者和消费者的名字
    private int i = 0;//商品的个数
    private boolean b=false;//判断标记,用来判断是否等待和唤醒

    public void pro(String name)//产品生产的方法
    {
        synchronized(this)//同步代码
        {
            while(b)//判断是否要等待 生产和消费的标记相反        
                try{wait();}catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+name+"生产了商品---"+(++i));
                b=true;//修改标记
                notifyAll();        
        }

    }

    public void con(String name)//产品消费的方法
    {
        synchronized(this)//同步代码
        {
            while(!b)//判断是否要等待 生产和消费的标记相反       
                try{wait();}catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+name+"消费了商品---"+i);
                b=false;//修改标记
                notifyAll();    
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值