Java 多线程入门

145 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第39天,Java 多线程入门 - 掘金 (juejin.cn)

前言

Java 多线程入门,了解多个线程是如何同时进行的。

一、线程简介

操作系统的线程、线程详解

多任务 -> 多进程 -> 多线程

多任务)在多道系统中,允许计算机宏观上执行多道程序(区别于单道系统,内存中可有多道程序)。

多进程)当一个程序在等I/O时,就可以把CPU让给另一个程序,程序执行走走停停,因此引入进程的概念来更好的描述和控制程序的并发执行,从而提高系统的吞吐率和资源利用率。

多线程)引入线程是为了多道程序在并发执行时所付出的时空开销,提高系统的并发性能。同一进程中多个线程可以并发执行,而同一进程下的线程共享进程所拥有的所有资源,所以同一进程下的线程切换就会减少很多时空开销。(轻量级+共享性)

二、线程实现

1)继承Thread,重写run方法

2)实现Runnable接口

3)实现Callable接口

1、Thread

实例1:第一步,自定义线程类继承Thread类;第二步,重写run方法,编写线程执行体;第三步,创建线程对象,调用start方法启动线程。

package com.xhu.java.thread;

public class TestThread extends Thread {
    //类似于GC线程的其它线程
    @Override
    public void run() {
        //run方法线程
        for (int i = 0; i < 5; i++) {
            System.out.println("run线程");
        }
    }

    //程序的主线程
    public static void main(String[] args) {
        TestThread t1 = new TestThread();
        //调用start,开启线程
        t1.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main线程");
        }
    }
}

在这里插入图片描述 注:多个线程的先后执行是否CPU的调度算法实现的,不能人为干预。

package com.xhu.java.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//用多线程同步下载图片
public class TestDownPicture extends Thread {
    String url;
    String fileName;

    public TestDownPicture(String url, String fileName) {
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        WebDownLoader wdl = new WebDownLoader();
        wdl.downLoader(this.url, this.fileName);
        System.out.println("下载完毕!"+ fileName);
    }

    public static void main(String[] args) {
        TestDownPicture tdp1 = new TestDownPicture("1.jpg", "d:down1.jpg");
        TestDownPicture tdp2 = new TestDownPicture("1.jpg", "d:down2.jpg");
        TestDownPicture tdp3 = new TestDownPicture("1.jpg", "d:down3.jpg");
        tdp1.start();
        tdp2.start();
        tdp3.start();
    }
}

class WebDownLoader {
    //下载方法
    public void downLoader(String url, String fileName) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、Runnable

package com.xhu.java.thread;

//执行线程需要需要将this丢入runnable的实现类,再调start方法。
//推荐使用runnable接口
public class TestRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程1");
    }

    //主线程
    public static void main(String[] args) {
        TestRunnable tr = new TestRunnable();
        //代理
        Thread t = new Thread(tr);
        t.start();
    }
    //1.实现Runnable,重写run,2.代理,3.start
}

在这里插入图片描述

3、买票案例

package com.xhu.java.thread;

//同一对象,多个线程来操作。
//模拟买票
public class TestBuyTicket implements Runnable {
    //该对象只有5张票
    private int ticketNums = 15;

    //抢票线程体
    @Override
    public void run() {
        while (true) {

            if (ticketNums < 1) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNums-- + "票");
            //每抢一张票,不允许马上又抢
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        TestBuyTicket t1 = new TestBuyTicket();
        //处理同一对象,同步问题就需注意
        new Thread(t1, "小李").start();
        new Thread(t1, "小王").start();
        new Thread(t1, "黄牛").start();
    }
}

4、龟兔赛跑

package com.xhu.java.thread;

//多线程模拟龟兔赛跑
public class Race implements Runnable {
    boolean flag = false;

    @Override
    public void run() {
        int i = 0;
        while (++i <= 100) {
            //第一名是否产生
            if (flag) break;
            //终结比赛
            if (i >= 100) {
                flag = true;
                System.out.println("winner is " + Thread.currentThread().getName());
                break;
            }
            //模拟兔子睡觉
            if (i >> 1 == 0 && "兔子".equals(Thread.currentThread().getName())) {
                try {
                    Thread.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
    }

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

5、Callable

在这里插入图片描述

package com.xhu.java.thread;

import java.util.concurrent.*;

//1.实现Callable接口来实现多线程
public class TestCallable implements Callable {
    //2.重写call方法
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "正在执行");
        return null;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.实例多线程对象
        TestCallable tc = new TestCallable();
        //4. 开启服务
        ExecutorService ser = Executors.newFixedThreadPool(1);
        //5. 提交执行
        Future submit = ser.submit(tc);
        //6. 获取结果
        Integer obj = (Integer) submit.get();
        //7. 断开服务
        ser.shutdownNow();
    }
}

三、静态代理

package com.xhu.java.thread;

//模拟静态代理
//1.需要实现同一个接口
//2.代理对象实现目标对象不能实现或很复杂的(可能是为了更好的封装)
public class TestStaticProxy {
    public static void main(String[] args) {
        //Runnable 的静态代理
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程代码段");
            }
        }).start();
        //对比结婚的静态代理
        new WeddingCompany(new Marry() {
            @Override
            public void happyMarry() {
                System.out.println("男主女主结婚啦");
            }
        }).happyMarry();
    }
}

//同一接口:和结婚相关
interface Marry {
    void happyMarry();
}

//情侣结婚
class Lovers implements Marry {
    String manName;
    String womanName;

    public Lovers(String manName, String womanName) {
        this.manName = manName;
        this.womanName = womanName;
    }

    @Override
    public void happyMarry() {
        System.out.println("男主:" + this.manName + ",女主:" + this.womanName + ",结婚啦");
    }
}

//婚庆公司,提供其它功能,让结婚变得更有仪式感
class WeddingCompany implements Marry {
    Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        before();
        this.target.happyMarry();
        after();
    }

    private void after() {
        System.out.println("打扫现场,收取尾款");
    }

    private void before() {
        System.out.println("布置现场");
    }
}

四、Lambda

函数式接口:一个接口里面只有一个抽象方法。

函数式编程:Lambda表达式,保留函数式接口的匿名内部类的核心部分。(形参)-> {重写的语句},如(int a)->System.out.println(a)or(a)->System.out.println(a) ora->System.out.println(a)

优点:留下核心逻辑,简洁,避免匿名内部类过多。

package com.xhu.java.thread;

public class TestLambda {
    public static void main(String[] args) {
        Lambda l = ()-> System.out.println("lambda");
        l.show();
    }
}

interface Lambda {
    void show();
}

五、线程状态

在这里插入图片描述 OS 进程线程详解 在这里插入图片描述

1、线程停止

package com.xhu.java.thread;

//1.JDK提示的过时的方法stop、destroy不建议使用
//2.建议使用标志位来停止线程。
public class TestStop implements Runnable {
    //设置标志位来停止线程
    private boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while (flag) {
            System.out.println("run Thread " + i++);
        }
    }

    public void stop() {
        this.flag = false;
    }

    //主线程
    public static void main(String[] args) {
        TestStop ts = new TestStop();
        new Thread(ts).start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("main" + i);
            if (i == 9000) {
                ts.stop();
                System.out.println("该线程该停止了");
            }
        }
    }
}

2、线程休眠

在这里插入图片描述

package com.xhu.java.thread;

import java.text.SimpleDateFormat;
import java.util.Date;

//模拟时间
//模拟计时器
public class TestSleep {
    //模拟时间
    public static void stimulateTime() throws InterruptedException {
        Date date = null;
        int num = 10;
        while (num-- > 0) {
            date = new Date();
            System.out.println(new SimpleDateFormat("hh:mm:ss").format(date));
            Thread.sleep(1000);
        }
    }
    //模拟计时器
    public static void down() throws InterruptedException {
        int num = 10;
        while (num-- > 0) {
            System.out.println(num);
            Thread.sleep(1000);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        stimulateTime();
        down();
    }
}

3、线程礼让

1)让正在进行的线程暂停,但不阻塞。

2)将线程从运行态转换为就绪态,跟其它线程在同一就绪队列。

3)CPU根据调度算法来重新调线程,所以礼让不一定会成功。

package com.xhu.java.thread;

public class TestYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "->start");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "->end");
    }

    public static void main(String[] args) {
        TestYield ty = new TestYield();
        new Thread(ty, "one").start();
        new Thread(ty, "two").start();
    }
}

4、线程合并

线程插队

package com.xhu.java.thread;

//线程插队
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("vip线程" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin tj = new TestJoin();
        Thread t = new Thread(tj);
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main排队" + i);
            if (i == 50) {
                t.join();
            }
        }
    }
}

5、五个状态

package com.xhu.java.thread;

public class TestThreadState {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("..........");
            }
        });
        //新生状态
        Thread.State state = t.getState();
        System.out.println(state);
        //运行态
        t.start();
        state = t.getState();
        System.out.println(state);
        //阻塞态
        Thread.sleep(2000);
        state = t.getState();
        System.out.println(state);
        //终止状态
        Thread.sleep(6000);
        state = t.getState();
        System.out.println(state);
    }
}

6、获取优先级

在这里插入图片描述

package com.xhu.java.thread;

public class TestPriority {
    public static void main(String[] args) {
        //默认优先级 5
        new Thread(() -> System.out.println(Thread.currentThread().getName()
                + "-->" + Thread.currentThread().getPriority())).start();
        //设置优先级
        Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName()
                + "-->" + Thread.currentThread().getPriority()));
        t1.setPriority(10);
        //注意:先设置优先级再启动
        t1.start();
        
        System.out.println(t1.getPriority());
        //主线程main的优先级
        System.out.println("main's Priority is " + Thread.currentThread().getPriority());
    }
}

7、守护线程

在这里插入图片描述

package com.xhu.java.thread;

//测试守护线程
//用户线程
//守护线程
public class TestDaemon {
    public static void main(String[] args) {
        You y = new You();
        God g = new God();
        //设置守护线程,JVM不用等它,完成用户线程就可以结束了。
        Thread tg = new Thread(g);
        tg.setDaemon(true);//默认是false,为用户线程。
        tg.start();
        //用户线程
        new Thread(y).start();
    }
}

//你
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 36; i++) {
            System.out.println("好好活着");
        }
        System.out.println("died");
    }
}

//上帝
class God implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("god 永生,保护我们");
        }
    }
}

六、线程同步

多个线程操作同一资源,需要同步线程,保证正确性。

1、线程不安全

ArrayList

package com.xhu.java.thread;

import java.util.ArrayList;
import java.util.List;

public class TestArrayList {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            int j = i;
            new Thread(() -> {
                list.add(j);
            }).start();
        }

        //主线程阻塞4秒,让上面的线程跑完
        Thread.sleep(4000);
        //期待size ==  1000,但实际<=1000
        System.out.println(list.size());
        //得到结果870
    }
}

2、线程同步

1)synchronized方法,默认锁this 2)synchronized块,自定义锁任意对象

1)调用synchronized方法,必须获得该对象的锁,如果该对象已被锁住,则线程阻塞。

缺点)如果一个方法很长,那么它独占对象的时间就会很长,导致效率降低。所以可以通过synchronized块来锁特定的地方。

A)抢票锁

package com.xhu.java.thread;

public class TestSynchronized {
    public static void main(String[] args) throws InterruptedException {
        //车站有10张票
        Station s = new Station(100000);

        //三个人去抢
        Thread t1 = new Thread(s, "我");
        Thread t2 = new Thread(s, "你");
        Thread t3 = new Thread(s, "黄牛党");
        //开始抢票
        //第二种方法,synchronized块,synchronized默认锁this,但是如果要锁其它对象就可以用synchronized块
        synchronized (s){
            t1.start();
            t2.start();
            t3.start();
        }


        Thread.sleep(10000);
        System.out.println(Station.modCount);
    }
}

class Station implements Runnable {
    private int ticketNum;
    private boolean flag = true;
    public static int modCount;

    public Station(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {//锁上this
        while (flag) {
            //没票了
            if (ticketNum < 1) {
                flag = false;
                break;
            }
            //买票
            try {
                buy(ticketNum--);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void buy(int i) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "抢到了票" + i);
        modCount++;
        //Thread.sleep(1000);
    }

    //通过设置标志位来停止线程
    public void stop() {
        this.flag = false;
    }

}

B)让ArrayList安全

package com.xhu.java.thread;

import java.util.ArrayList;
import java.util.List;

public class TestArrayList {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            int j = i;
            new Thread(() -> {
                synchronized (list) {
                    list.add(j);
                }
            }).start();
        }

        //主线程阻塞4秒,让上面的线程跑完
        Thread.sleep(4000);
        //期待size ==  1000,但实际<=1000
        System.out.println(list.size());
        //得到结果870
    }
}

C)JUC中的安全集合

package com.xhu.java.thread;


import java.util.concurrent.CopyOnWriteArrayList;

public class TestJUC {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<Integer> carr = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
            int j = i;
            new Thread(() -> {
                carr.add(j);
            });
        }
        Thread.sleep(2000);
        System.out.println(carr.size());
    }
}

3、死锁

死锁详解 四个必要条件:

1)互斥访问,资源独占。

2)请求并保持,保持了申请的独占资源的同时去申请其它的。

3)不可剥夺,保持的独占资源不可剥夺。

4)循环等待,相互请求互相保持的资源。

package com.xhu.java.thread;

public class DeadLock implements Runnable {
    //同时拿到两个筷子才能吃饭
    private LeftChopSticks lc;
    private RightChopSticks rc;
    private int choice;

    public DeadLock(LeftChopSticks lc, RightChopSticks rc, int choice) {
        this.lc = lc;
        this.rc = rc;
        this.choice = choice;
    }

    @Override
    public void run() {
        if (choice == 0) {
            synchronized (lc) {
                System.out.println(Thread.currentThread().getName() + "拿到了左筷子");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //拿到左筷子,准备拿右筷子
                synchronized (rc) {
                    System.out.println(Thread.currentThread().getName() + "拿到了左筷子");
                }
            }
        } else {
            synchronized (rc) {

                System.out.println(Thread.currentThread().getName() + "拿到了右筷子");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //拿到右筷子,准备拿左筷子
                synchronized (lc) {
                    System.out.println(Thread.currentThread().getName() + "拿到了右筷子");
                }
            }
        }

    }

    public static void main(String[] args) {
        //只new 一根左筷子
        LeftChopSticks lc = new LeftChopSticks();
        //只new 一根右筷子
        RightChopSticks rc = new RightChopSticks();
        DeadLock dl1 = new DeadLock(lc,rc,1);
        DeadLock dl2 = new DeadLock(lc,rc,0);
        new Thread(dl1,"小李").start();
        new Thread(dl2,"小王").start();
    }

}

//一根左筷子
class LeftChopSticks {

}

//一根右筷子
class RightChopSticks {

}

4、Lock

JUC的同步接口Lock,规范了同步锁的定义,其实现类ReentrantLock,有着跟synchronized一样的内存语义和并发性。

package com.xhu.java.thread;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock implements Runnable {
    //10张票
    private int ticketNum;
    //显式可重入锁
    private final ReentrantLock rl = new ReentrantLock();

    public TestLock(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {
        while (true) {
            //锁定,保证ticketNum的正确性
            rl.lock();
            try {
                if (ticketNum < 1) break;
                System.out.println(Thread.currentThread().getName() + "抢到了票" + ticketNum--);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //解锁
                rl.unlock();
            }

        }
    }

    public static void main(String[] args) {
        TestLock tl = new TestLock(10);
        //多个人抢票
        new Thread(tl, "小李").start();
        new Thread(tl, "小王").start();
        new Thread(tl, "黄牛党").start();
    }
}

七、线程通信问题

线程同步就需要线程通信,那么线程如何通信? 典型问题:生产者-消费者问题 软件方法、硬件方法、信号量、管程做到线程通信 在这里插入图片描述

1、管程法

package com.xhu.java.thread;

//管程法解决生产者-消费者问题
public class TestTube {
    public static void main(String[] args) {
        Container c = new Container(10);
        Producer p = new Producer(c);
        Customer cus = new Customer(c);
        new Thread(p).start();
        new Thread(cus).start();
    }
}

//Producer
class Producer implements Runnable {
    private Container c;

    public Producer(Container c) {
        this.c = c;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //生成鸡
            Chicken chicken = new Chicken(i + "");
            try {
                c.push(chicken, i);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//Customer
class Customer implements Runnable {
    private Container c;

    public Customer(Container c) {
        this.c = c;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //消费鸡
            try {
                c.pop(i);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//产品为鸡
class Chicken {
    private String id;

    public Chicken(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//装鸡的容器
class Container {
    private int capacity;
    private Chicken[] chickens;
    private int count;

    public Container(int capacity) {
        this.capacity = capacity;
        this.chickens = new Chicken[this.capacity];
    }

    //把鸡装入容器
    public synchronized void push(Chicken chicken, int i) throws InterruptedException {
        //容器已满,则等待
        if (chickens.length == count) {
            this.wait();
        }
        //容器没满,把鸡装入
        chickens[count++] = chicken;
        System.out.println("生产了第" + i + "鸡");
        //通知其它操作容器且等待的线程,有鸡了
        this.notifyAll();
    }

    //把容器里的鸡取出来
    public synchronized Chicken pop(int i) throws InterruptedException {
        //容器为空,则等待
        if (count == 0) {
            this.wait();
        }
        //容器里有鸡,则取鸡
        Chicken chicken = chickens[--count];
        System.out.println("消费了第" + i + "只鸡");
        //通知其它操作容器且等待的线程,可以加鸡
        this.notifyAll();
        return chicken;
    }
}

八、线程池

背景)每次都创建线程,用完就销毁线程,这样耗费资源太大,对性能影响很大。

解决)提前创建多个线程放入线程池,使用时就获取,不使用的时候就放回线程池中,避免频繁的创建和销毁线程。

好处)减少资源消耗的同时还可以提高响应速度。同时也便于线程管理

corePoolSize:核心池的大小。

maxmumPoolSize:最大线程数。

keepAliveTime:线程没有任务时最多保持多长时间后会终止。

在这里插入图片描述

package com.xhu.java.thread;

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

public class TestThreadPool {
    public static void main(String[] args) {
        //1.创建线程池服务
        ExecutorService ser = Executors.newFixedThreadPool(10);//参数为池子大小
        //2.执行线程
        ser.execute(new MyThread());
        ser.execute(new MyThread());
        ser.execute(new MyThread());
        ser.execute(new MyThread());
        //3.关闭连接
        ser.shutdownNow();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

总结

1)多线程是什么?

2)Java 如何实现多线程?

3)静态代理思想

4)Lambda函数式编程,简化匿名内部类。

5)线程状态

6)线程同步

7)线程通信问题

8)线程池

参考文献

[1] Java 多线程 狂神 [2] [JDK 1.12] [3] [The Art of Java Concurrency Programming 方腾飞,魏鹏,程晓明]