Java多线程(一)

什么是多线程?

什么是线程呢?了解线程之前得先知道什么是进程。
进程:操作系统进行资源分配的基本单位。当一个应用程序运行起来,那么会在内存中开辟使用空间,即创建了一个进程。
线程:操作系统中使用资源的最小单位,一个进程可以有多个线程。
进程就好像一个小工厂,总公司(操作系统)给他分配资源,小工厂的工人(线程)对这些资源进行使用。

多线程是Java的一大特性。顾名思义,多线程就是一个进程中有多个线程运行,这多个线程可以并发的执行。

多线程的好处

提高效率

一个工厂有多个工人的话,那么在好的管理模式(线程功能的分配)下效率自然而然提高了。
在操作系统(单核单线程)中,所有的线程并发执行,但在微观上,由于在同一时间CPU只能处理一条指令,所以它们是分开运行的,但是由于CPU的运行速度十分的快速,难以想象的快,CPU极其快速的轮换执行不同的指令,使得从宏观上,它们是同时执行的。
使用多线程,那么从宏观上,多个线程同时执行即提高了效率。

在Java中如何使用多线程

在Java中有两种方法可以实现多线程。
1.继承Thread类

class ThreadDemo extends Thread{

    public void run(){
		//通过Thread的静态方法currentThread()获取当前线程,并通过getName()获取线程的名字
        System.out.println("我是继承Thread实现的线程:"+Thread.currentThread().getName());
    }
}
    public static void main(String[] args) {
		//通过Thread的静态方法currentThread()获取当前线程,并通过getName()获取线程的名字
        System.out.println("这是主线程:"+Thread.currentThread().getName());
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
    }

运行结果:
在这里插入图片描述
通过重写父类Thread的run方法,将此线程执行的任务写在run()中。然后通过他的start()(从父类Thread中继承的方法)运行线程,此线程执行的内容即为run()方法的内容。
在这里通过start()方法运行的线程,那么能否对象直接调用run()方法来开启线程呢?我在Main函数中直接调用对象的run()方法。

        threadDemo.run();

运行结果:
在这里插入图片描述
显然:直接调用run()方法运行,还是在Main线程中执行的,并未开启新的线程。
通过对象直接调用run()方法并不是开启新的线程,而是普通的对象调用方法,即在当前线程中执行。

2.通过实现runable接口。
上面实现runable的方法是继承Thread类,如果我准备多线程执行的A类需要继承另外一个B类的话,那么A类就需要继承B类和Thread类,但是Java是单继承,不能继承多个类。李华:“这种需要继承多个类的情景应该会很多吧?Java难道没有解决这样的问题吗?”。当然有,java不能多继承,于是有另外一种机制,实现接口,Java可以实现多个接口。这时候第二种实现多线程的方法就就起了作用,实现runable接口。

        System.out.println("这是主线程:"+Thread.currentThread().getName());

        RunableDemo r = new RunableDemo();
        r.start();//报错
class RunableDemo extends Father implements Runnable{

    public void run(){

        System.out.println("我是Runnable实现的线程:"+Thread.currentThread().getName());
    }
}

当我们调用r.start()的时候报错了。。。。因为runable没有这个方法。
那么应该怎么启动呢?

        Thread thread = new Thread(r);
        thread.start();

通过new 一个Thread,将实现runable接口的对象传入构造函数即可。
在调用Thread对象的start()方法即可,这个时候start()方法会开启新线程并在其中执行RunableDemo 的run方法
运行结果:
在这里插入图片描述
如果线程中的内容比较简单,也可以直接只实现runable接口。

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //线程执行内容
            }
        };
        Thread t = new Thread(runnable);
        t.start();

多线程的一个例子:多窗口卖票

需求:
为了提高卖票的速度,车站在原有一个卖票窗口的基础上,增加了2个卖票窗口,共3个卖票窗口。总共有100张票,每个人只能买一张票,请用程序模拟将票卖完。

public class Window extends Thread {

    private static int num = 100;//因为总共100张票,所以静态共享。
    private String name;

    Window(String name) {

        this.name = name;
    }

    public void run() {

        while (num>0){

            System.out.println("窗口"+this.name+"--------------卖出了第"+this.num+"张票");
            num--;
        }

    }

}
    public static void main(String[] args) {

        //三个窗口
        Window w1 = new Window("1");
        Window w2 = new Window("2");
        Window w3 = new Window("3");
		//开启线程
        w1.start();
        w2.start();
        w3.start();
    }

运行结果:可以多运行几次,发现每次结果都不相同,这是正常的,因为线程随机的被CPU执行。

查看结果,发现竟然卖出了第0张票,而且两个窗口都卖出了。这显然是不正确的,因为根本没有第0张票。
多次运行发现在此次运行中,第15张票被卖出了多次,这显然也是不被允许的。
在这里插入图片描述
但在有些时候,又是正确卖出了所有票。

同步锁(synchronized )

为什么会出现上面的问题呢?
由于采用了多线程,所以每个线程会随机的被CPU执行,执行一段时间(非常短的时间)后,如果此线程未结束,那么CPU会保存现场,然后去执行另外一个线程。
比如在刚才的w1线程中,CPU执行到了如下语句,并执行完此语句,假设此时num=15,那么卖出了第15张票。注意,此时num–未执行。
在这里插入图片描述
此时CPU暂停w1线程的执行,转而执行w2,并执行到了w2的如下语句,并执行完此语句。此时num依然为15,那么第15张票就被卖出 两次。
在这里插入图片描述
同理,上面的其他问题也是这种原因。多个线程操作同一资源,不同步,而导致很可能发生错误。

那应该怎么解决此问题呢?
Java提供了一种机制,synchronized关键字,中文译为同步,也可以叫锁。
它的用法长这样。

        synchronized (obj){//参数可以是obejct类型或者其子类

            //同步的内容
        }

大括号{}里面的内容就是需要同步的内容。当进入大括号 { 时,先拿到锁再进入,出大括号 } 释放锁。如果拿不到锁则等待不进入。
比如上面的卖票,将他改造一下。

public class Window extends Thread {

    private static int num = 100;//因为总共100张票,所以静态共享。
    private static Object obj = new Object();//新加的,静态共享
    private String name;

    Window(String name) {

        this.name = name;
    }

    public void run() {

        while (true) {

            synchronized (obj) {//锁一定要是这多个线程共享的变量,新加的静态

                if(num>0){
                    System.out.println("窗口" + this.name + "--------------卖出了第" + this.num + "张票");
                    num--;
                }
                else break;
            }
        }
    }
    
}

当CPU执行到w1线程的如下位置时,卖出第num张票,此时未num–;w1线程拿到了obj锁,未释放。
在这里插入图片描述
如果此时CPU暂停w1线程,转而执行w2,w2走到了如下位置,想拿obj锁,但是由于锁在w1手上,于是等待,直到w1释放后,才有可能拿到锁,这样就避免了不同步而导致的错误。
运行结果:
完美!太长就不截图了
李华:“为什么不能直接在之前的基础上,直接用synchronized把代码包起来呢?之前是while(num>0)现在变为了while(true)”。李华应该想的是将之前的变成如下样子。

    public void run() {

        while (num > 0) {

            synchronized (obj) {

                System.out.println("窗口" + this.name + "--------------卖出了第" + this.num + "张票");
                num--;
            }
        }

    }

如果用如上代码,那么当w1拿到obj锁后,假设此时num=1,执行到如下语句
在这里插入图片描述
此时CPU暂停执行w1,转而执行w2,由于w2没有obj锁,则执行到如下位置等待
在这里插入图片描述
当w1执行完后,num–;num=0,释放锁。CPU转而执行w2,由于w2在如上位置等待,拿到锁后,卖票,卖出了第0张票。但是由于在w1执行完后,num=0,票已经买完,所以不应该卖票,发生错误。因此条件判断一定要放在锁里面判断,不然会发生错误。
未完待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值