Java多线程专题

多线程是什么?

要说线程, 我们最好先提出并发这个概念. 或许我们已经无数次听到用车道比喻cpu个数,车道上运行的车比作线程的例子, 在这里, 我还是不厌其烦的重述这个经典的例子, 来加深任何看到这篇博文,并且不了解并发技术应用背景的人对多线程的理解. 我们将看到, 并发技术和多核CPU(Chip Multiprocessors)的发展最大程度的实现了宽发射、乱序的超标量处理, 以此获得的性能提升是现在一切盛行的服务和技术的基础. 有计算机的地方, 就有并发运算, 而多线程技术(Simultaneous Multithreading)就就是实现了并发运算的技术.
如果将计算机CPU的个数比作车道的个数, 在程序运行中,我们可以分出以下几种情况:

  • 车很少,车道没有被占满.
  • 车数量刚好占满车道.
  • 车很多,有些车道有多量车,但是或许还有空间跑更多的车.
  • 车非常多,即使把路塞满,也放不下, 有的车不得不在天上飘着.

这里的车道就是CPU核心数,一个车道上的车就是一个线程. 有的车跑的快,有的车跑的慢,我们假设一个车道一次只能用一辆车作为全部车的速度,那么当然让跑的快的先跑,跑的慢的后跑. 有时为了公平起见,可能约定一辆车轮流跑固定的时间,再轮到其他车.

有的人可能会问: 进程到哪里去了? 程序不都是进程吗?

答: 进程是一个封地(CPU和内存)的领主,他是所有资源和名誉的所有者,但是他不自己干活.我们这里讲的是真正在干活的劳动人民, 请不要提它.

多线程有什么用?

从上面的例子我们已经看到, CPU多个核心并行执行,一个CPU核心内多个线程在一定时间内并发执行.
并行是真正意义上的同时发生的多件事,并发是一段时间内轮流发生的多件事.
由此,一个程序内,一心可以多用.
程序并发的进行后台计算,向用户发送数据,接受用户的指令,向网络发送数据、接受数据.
线程带来的并发真正的让一个应用程序有手有脚,能说会跳.

由此拓展,线程使代码模块化,异步化,简单化,这些特点我会在其他微服务的博文中体现.

多线程带来的问题

进程之间内存是隔离的,线程内部内存是共享的.我们都知道共享就意味着抢占与无序. 抢占与无序使得程序的结果不再可预测,我们需要加锁避免. 由此死锁和活锁问题也随之而生,可见线程不是没有代价的.
线程带来了很多问题,也衍生了很多技术. 例如,线程对资源的争抢,促使我们在任何需要发送、接受、使用资源的地方都放一个池或者缓冲区,而这是让程序受益匪浅的. 当一个机器无论如何都运行不过来时,我们需要分布式计算, 这也就促生的大数据的处理能力. 无论如何,线程是一个好的开端.

Java多线程的三种实现方式

线程被创建后,用start方法启用线程,则线程不一定立即执行,而是交给cpu进行调度.线程大致有三种实现方式,第一种只做学习用,第二种较为简单,第三种更成熟.

方式一: 继承Thread类(实际上不使用)

Java是单继承的, 继承了Thread啥也别干了.

一个数数程序

public class MyFirstThread extends Thread{
    int id;
    MyFirstThread(int id){
        this.id = id;
    }
    public void run(){
        for(int i =0;i<2000;i++){
            System.out.println("This is number "+ id+". Time: "+i);
        }
    }
    public static void main(String[] args) {
        MyFirstThread thread1 = new MyFirstThread(1);
        MyFirstThread thread2 = new MyFirstThread(2);
        thread1.start();
        thread2.start();
    }
}
运行结果

注意:这里若使用对象的run方法,将按照先后顺序执行,不会使用多线程.

一个下载图片程序

import java.io.IOException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
class ThreadDownLoader {
    public void downloader(String url,String name) throws IOException {
        FileUtils.copyURLToFile(new URL(url),new File(name));
    }
}
public class ThreadWithDownLoad extends Thread{
    private String url;
    private String name;
    public ThreadWithDownLoad(String url,String name){
        this.url=url;
        this.name=name;
    }
    public void run(){
        ThreadDownLoader webdownloader =new ThreadDownLoader();
        try {
            webdownloader.downloader(url,name);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载失败");
        }
    }
    public static void main(String[] args) {
        ThreadWithDownLoad IamDownLoading1 =
                new ThreadWithDownLoad(
                        "https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
                        "picture1.jpg");
        ThreadWithDownLoad IamDownLoading2 =
                new ThreadWithDownLoad(
                        "https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
                        "picture2.jpg");
        ThreadWithDownLoad IamDownLoading3 =
                new ThreadWithDownLoad(
                        "https://i2.hdslb.com/bfs/archive/e961e1c53d9946d4e67cab442e33a71506278757.png@672w_378h_1c.webp",
                        "picture3.jpg");
        IamDownLoading1.start();
        IamDownLoading2.start();
        IamDownLoading3.start();
    }
}

方法二: 用实现Runnable的对象作为Thread的构造参数

//实现Runnable接口
//重写run方法
//用thread创建带有runnable参数的对象
public class ThreadInitIpRunnable implements Runnable{
    int id;
    ThreadInitIpRunnable(int id){
        this.id = id;
    }
    public void run(){
        for(int i =0;i<2000;i++){
            System.out.println("This is number "+ id+". Time: "+i);
        }
    }
    public static void main(String[] args) {
        ThreadInitIpRunnable threadR1 = new ThreadInitIpRunnable(1);
        ThreadInitIpRunnable threadR2 = new ThreadInitIpRunnable(2);
        //用Runnable当参数构造Thread
        Thread thread1 = new Thread(threadR1);
        Thread thread2 = new Thread(threadR2);
        thread1.start();
        thread2.start();
    }
}

####一个抢票程序

public class BuySomeTickets implements Runnable {
    ticket t = new ticket(100);
    public void run(){
        while(true){
            if(t.getNum()<=0)break;
            System.out.println(Thread.currentThread().getName()+"拿到了第"+t.getNum()+"张票。");
            t.getTicket();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        BuySomeTickets tickets = new BuySomeTickets();
        new Thread(tickets,"王二虎").start();
        new Thread(tickets,"大黑牛").start();
        new Thread(tickets,"奔波霸").start();
    }
}
class ticket{
    private static int num=100;
    ticket(int num){this.num=num;}
    public void getTicket(){num--;}
    public int getNum(){return num;}
}
运行结果

如果让你实现这个程序,结果会是什么呢? 一张票会不会被重复拿?
线程是不安全的,我们需要一些措施防止这些问题.

# Java 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×