首页 » 技术分享 » 对多线程的理解

对多线程的理解

 

首先,什么是多线程。我的理解是多线程就是多个线程同时进行。比如说买火车票,好几个人同时购买火车票,每一个人购买火车票都是一个线程,多个人同时购买自然就是多线程了。
说到线程,有一个与之相关的概念叫做进程。所谓进程,就是对于程序的一次执行的过程。还是以买火车票为例,买火车票就是进程,因为他要执行买火车票的这个程序。然后呢,一个进程可以有多个线程,但至少得有一个线程,不然就没有意义了。你可以多个人同时买火车票,但起码要有一个人买,不然没人买票跑程序干嘛呢?
对于线程,一般来说有三种创建的方法。第一是继承Thread类,第二种是实现Runnable接口,最后一种是实现Callable接口。
首先是继承Thread类,这个有三步。
第一步是自定义线程继承Thread类
第二步是重写run()方法
第三步是创建线程对象,调用start()方法启动线程
以我写的这个为例

package Demo02;

public class ThreadExample extends Thread{
    @Override
    public void run() {
        Print print=new Print();
        for (int i = 0; i < 3; i++) {
            print.print(i);
        }
    }

    public static void main(String[] args) {
        ThreadExample t1=new ThreadExample();
        ThreadExample t2=new ThreadExample();
        ThreadExample t3=new ThreadExample();
        t1.start();
        t2.start();
        t3.start();
    }
}
class Print{
    public void print(int i){
        System.out.println(i);
    }
}

运行以后的结果如图
在这里插入图片描述
然后是实现Runnable接口
同样是分为三步
第一步是自定义一个类实现Runnable接口
第二步是实现run()方法
第三步是创建线程对象,调用start()方法启动线程
以此为例:

package Demo02;

public class RunnableExample implements Runnable{
    private String name;

    public RunnableExample(String name){
        this.name=name;
    }

    @Override
    public void run() {
        Print1 print=new Print1();
        print.print(name);
    }

    public static void main(String[] args) {
        RunnableExample t1=new RunnableExample("小红");
        RunnableExample t2=new RunnableExample("小蓝");
        RunnableExample t3=new RunnableExample("小黄");
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

class Print1{
    public void print(String name){
        System.out.println(name);
    }
}

总的来说,我觉得实现Runnable接口是比继承Thread类要好的,其中有一个很关键的点就是实现Runnable接口可以避免单继承局限性,可以同一个对象被多个线程使用,如下例子:

package Demo02;

public class TestThread implements Runnable{
    private int num=10;
    @Override
    public void run() {
        while (true){
            if (num<=0){
                break;
            }else{
                System.out.println(Thread.currentThread().getName()+"获得第"+num--+"张票");
            }
        }
    }

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        new Thread(testThread,"小红").start();
        new Thread(testThread,"小橙").start();
        new Thread(testThread,"小黄").start();
        new Thread(testThread,"小绿").start();
        new Thread(testThread,"小青").start();
        new Thread(testThread,"小蓝").start();
        new Thread(testThread,"小紫").start();
    }
}

但是在线程并发上他会出问题,所以后面会有一个线程同步的解决方法
在这里插入图片描述
有一个非常经典的多线程的例子,就是龟兔赛跑,为了符合故事内容,我设置了兔子在跑到50米的时候会睡上2秒

package Demo02;

public class Race implements Runnable{
    private String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if(i==50 && Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
            boolean flag = result(i);
            if(flag){
                break;
            }
        }
    }
    private boolean result(int i){
        if (winner!=null){
            return true;
        }
        if(i==100){
            winner = Thread.currentThread().getName();
            System.out.println(Thread.currentThread().getName()+"获胜");
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

这个案例就可以很清除的展示了多线程的概念。
然后最后一种方法是实现Callable接口
这种方法的步骤就比较多了
首先是创建一个类,实现Callable接口
然后重写call方法
然后创建目标对象
然后创建执行服务
然后提交执行
最后获取结果,关闭服务
说完了三种方法,接下来就可以说一下静态代理了。
所谓静态代理其实就是创建一个代理对象,但是这个代理对象可以做很多真实对象做不了的事,让真实对象专注去做自己的事。不过要求他们要实现同一个接口。
以租房子为例,我要租房子,那我需要做的就是给钱,签合同,那么之前的挑房子以及签完合同以后交付钥匙就交给中介,也就是第三方的中间商就可以了。在这个例子中,需要租房子的我,就是真实对象,而中间商就是代理对象。我们两都实现了一个租房的接口。

package Demo02;

public class StaticProxy {
    public static void main(String[] args) {
        //我要租房子
        Mine mine = new Mine();
        Business business=new Business(mine);
        business.HireHouse();
    }
}
//被实现的同一个接口
interface Hire{
    void HireHouse();
}
//真实角色,需要租房子的我
class Mine implements Hire{

    @Override
    public void HireHouse() {
        System.out.println("付钱,签合同");
    }
}
//代理角色,租房子的中间商
class Business implements Hire{
    private Hire hire;

    public Business(Hire hire) {
        this.hire = hire;
    }

    @Override
    public void HireHouse() {
        before();
        this.hire.HireHouse();
        after();
    }

    private void after() {
        System.out.println("交付钥匙");
    }

    private void before() {
        System.out.println("挑房子");
    }

}

运行起来就是这样的
在这里插入图片描述
然后说到线程的状态,众所周知,线程有五种状态。分别是创建状态,就绪状态,运行状态,阻塞状态和死亡状态。
首先说创建状态,顾名思义,就是线程刚被创建出来,也就是刚被new出来。这个时候我们调用start()方法就一个让线程进入到就绪状态。这个时候线程其实是准备就绪了,但是他要等待CPU调度,如果说获得了CPU的资源,那么线程就从就绪状态变成了运行状态,这个时候我们的线程就算正式跑起来了。进入到运行状态的线程会有几种结果,第一种就是线程自然执行完成或者因为外部的干扰导致线程终止了,这个时候线程就变成了死亡状态。还有一种情况是因为一些原因导致线程需要暂时停止,比如sleep、wait、join、yield等,这个时候线程就会变成阻塞状态,等到线程会唤醒就会变成就绪状态,等待CPU调度。
前面提到了从运行状态变成阻塞状态会有好几种情况,接下来就来说一下这几种情况。
首先是yield,这是线程礼让。其实很好理解,就是说有两个线程,A线程已经是运行状态了,这个时候B线程自然就是就绪状态了。然后线程礼让就是让A线程再变成就绪状态,两个线程公平竞争,让CPU重新调度,看最后哪个线程会变成运行状态。
然后是sleep,这个其实很常见了,就是线程休眠,他主要是可以放大问题的发生性,有的时候因为线程跑的太快了,所以很多问题我们发现不了,这个时候让他们去休眠一段时间,很多问题就可以被发现了。很多时候,sleep用来模拟网络延时和倒计时。
然后是join,join可以理解为插队,就是说有一个线程,他想要先执行,那么他就让别的线程先阻塞,等他执行完以后再执行别的线程。
还有一个wait(),他也是等待,但是他和sleep不一样的是,sleep不会释放锁,而wait会释放锁,而且wait可以用notify()方法来唤醒。
如果我们想要看自己的线程的状态,那么就可以用state=thread.getState();来查看。
而且因为我们都知道线程谁先执行谁后执行是按照优先级来的,我们如果想要改变或者获取优先级的话就可以用getPriority()和setPriority()方法。
然后设计到一个守护线程的概念。线程一般是分为用户线程和守护线程的,虚拟机必须要确保用户线程执行完毕,但是不会管守护线程的死活。所以说我们比较常见的垃圾回收,后台记录操作日志什么的都是守护线程。如果我们想要把一个自己写的线程设置为守护线程,那么就可以通过thread.setDaemon(true)的方式进行设置,因为一般默认是false,也就是用户线程。

之前提到了线程同步。所谓线程同步其实就是一种等待机制。举个例子,大家都想上厕所,但是就一个厕所,怎么办呢,那只能按先来后到排队了,这就是队列,厕所一次只能容纳一个人,所以第一个人进去以后就会把门给锁上,不让第二个人进来,这就是锁。所以说线程同步其实就是队列加锁。
线程同步可以用两个方法。
第一个是synchronized关键字,它可以用在方法和代码块里。但是加锁有时候会遇到一个问题,就是死锁。死锁就是A在等B先放手,B在等A先放手,恶性循环,僵住了。
还有一种呢是Lock,但是它和synchronized关键字相比,只有代码块锁,没有方法锁。而且Lock是显性锁,也就是说他是需要我们手动打开和关闭的,不像synchronized是隐性锁,出了作用域就自动释放了。但是Lock的性能要更好。因此一般推荐使用Lock锁。
关于多线程还有两中方法,一种是使用一个缓冲区的管程法,一种是信号灯法。
管程法的代码如下

package Demo02;

public class TestThreadDemo {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Productor(buffer).start();
        new Consumer(buffer).start();

    }
}
//生产者
class Productor extends Thread{
    Buffer buffer;
    public Productor(Buffer buffer){
        this.buffer=buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了第"+i+"个商品");
            buffer.push(new Product(i));
        }
    }
}
//消费者
class Consumer extends Thread{
    Buffer buffer;
    public Consumer(Buffer buffer){
        this.buffer=buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第"+buffer.pull().id+"个商品");
        }
    }
}
//产品
class Product{
    //产品编号
    int id;
    public Product(int id) {
        this.id = id;
    }
}
//缓冲区
class Buffer{
    ///设置一下容器大小
    Product[] products = new Product[10];
    //缓冲区内容器商品数量
    int count=0;
    //生产者放入商品
    public synchronized void push(Product product){
        //如果容器满了,就等消费者消费
        if(count==products.length){
            //消费者进行消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器没满,那么继续往缓冲区的容器中存入商品
        products[count]=product;
        count++;
        //通知消费者进行消费
        this.notifyAll();
    }
    //消费者取出商品
    public synchronized Product pull(){
        if(count==0){
            //消费者等待,生产者进行生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Product product=products[count];
        //通知生产者进行生产
        this.notifyAll();
        return product;
    }
}

信号灯的代码如下

package Demo02;

public class TestThreadDemo2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
//演员
class Player extends Thread{
    TV tv;
    public Player(TV tv){
        this.tv=tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%5==0){
                this.tv.play("广告");
            }else{
                this.tv.play("明星大侦探");
            }
        }
    }
}
//观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv=tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            this.tv.watch();
        }
    }
}
class TV{
    //节目
    String programe;
    //标志位
    boolean flag=true;
    //表演
    public synchronized void play(String programe){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演"+programe+"节目");
        this.programe=programe;
        flag=!flag;
        this.notifyAll();
    }
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看"+programe+"节目");
        flag=!flag;
        this.notifyAll();
    }
}

最后是线程池,线程经常用的话,老是被创建和销毁,太影响性能了。所以我们可以把创建一个线程池,提前创建线程放在里面,用的时候从里面拿就可以了。
示例代码如下:

package Demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        //创建线程池
        //参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        //创建服务
        service.execute(new TestPool());
        service.execute(new TestPool());
        service.execute(new TestPool());
        service.execute(new TestPool());
        //关闭连接
        service.shutdown();
    }
}
class TestPool implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"第"+i+"个");
        }
    }
}

运行结果如下
在这里插入图片描述

转载自原文链接, 如需删除请联系管理员。

原文链接:对多线程的理解,转载请注明来源!

0