多线程相关知识点

本文为本人很久以前在学习多线程知识的时候做的相关的笔记。以下整理成文章分享给需要的人。

线程简介

任务、进程、线程、多线程

多任务

例如:边吃饭边玩手机、开车打电话等

多线程相关知识点
image-20201121182711680

多线程

多线程相关知识点
image-20201121182838025

普通方法调用和多线程

多线程相关知识点
image-20201121183117755

程序、进程、线程

多线程相关知识点
image-20201121183303069

Process(进程)与Thread(线程)

多线程相关知识点
image-20201121183410329

核心概念

多线程相关知识点
image-20201121184556444

线程创建

线程创建的三种方式

Thread class   —> 继承Thread类(重点)

Runnable接口 —> 实现Runnable接口(重点)

Callable接口 —> 实现Callable接口(了解)

学习的时候一定要查看JDK帮助文档。

Thread

多线程相关知识点
image-20201122104559391

代码案例:

package com.itjing.multithread;

/**
 * 创建线程的方式一:继承Thread类,重写run方法,调用start开启线程
 * 总结:注意,线程开启不一定立即执行,由CPU安排调度
 */

public class TestThread01 {


    public static void main(String[] args) {
        /*自定义线程*/
        MyThread myThread = new MyThread();
        myThread.start();


        /*main线程,主线程执行的代码*/
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在学习多线程" + i);
        }

        /*经过验证,输出的结果是交叉的,说明多线程运行了*/
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        /*run方法线程体*/
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在看代码" + i);
        }
    }
}

线程开启不一定立即执行,由CPU安排调度

练习使用Thread,实现多线程同步下载图片

1.导入commons-io-2.6.jar包,将其添加到项目运行库

2.编写相关代码:

package com.itjing.multithread;

import org.apache.commons.io.FileUtils;

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

/**
 * 练习使用Thread,实现多线程同步下载图片
 */

public class TestThread02 {

    public static void main(String[] args) {
        MyThread02 myThread1 = new MyThread02("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269639&di=be049ee4c5f2a6a3a22a6746630c2567&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201811%2F16%2F20181116195909_klmfb.thumb.700_0.jpg""1.jpg");
        MyThread02 myThread2 = new MyThread02("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269638&di=b57def68f3cf8da1ed310b36e9fb1549&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Ffront%2F511%2Fw1024h1087%2F20190416%2FsUnA-hvsckth5610974.jpg""2.jpg");
        MyThread02 myThread3 = new MyThread02("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269638&di=4b3599e3ad4d3c11fb56d87feb09b2ad&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201811%2F16%2F20181116084944_yuwqb.jpg""3.jpg");

        /*每次下载的顺序都不一定相同*/
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

class MyThread02 extends Thread {
    private String url; //网络图片地址
    private String name; //保存的文件名


    public MyThread02(String url, String name) {
        this.url = url;
        this.name = name;
    }

    /*下载图片的线程的执行体*/
    @Override
    public void run() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.download(this.url, this.name);
        System.out.println("下载了的文件名为" + name);
    }
}

//下载器
class WebDownLoader {
    //下载方法
    public void download(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download方法出现问题!");
        }
    }
}

Runnable

多线程相关知识点
image-20201122114904621

代码案例:

package com.itjing.multithread;

/**
 * 创建线程方式2:实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
 */

public class TestThread03 {
    public static void main(String[] args) {
        MyThread03 myThread03 = new MyThread03();
        new Thread(myThread03).start();

        /*main线程,主线程执行的代码*/
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在学习多线程" + i);
        }
    }
}

class MyThread03 implements Runnable {

    @Override
    public void run() {
        /*run方法线程体*/
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在看代码" + i);
        }
    }
}

小结:

多线程相关知识点
image-20201122115847830

多线程同时操作同一个对象案例:

这时会发现问题,多个线程操作同一个资源的情况下,线程不安全

package com.itjing.multithread;

import java.util.concurrent.TimeUnit;

/**
 * 多线程同时操作同一个对象
 * 买火车票的例子
 *
 * 发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱
 */

public class TestThread04 {
    public static void main(String[] args) {
        MyThread04 myThread04 = new MyThread04();
        new Thread(myThread04, "小明").start();
        new Thread(myThread04, "老师").start();
        new Thread(myThread04, "黄牛").start();
    }
}

class MyThread04 implements Runnable {
    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (ticketNums > 0) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
        }
    }
}

龟兔赛跑案例

多线程相关知识点
image-20201122122636150

代码实现:

package com.itjing.multithread;

/**
 * 模拟龟兔赛跑案例
 */

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

class Race implements Runnable {
    //胜利者
    //定义为静态,保证胜利者只有一个
    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步");
        }
    }

    //判断是否完成比赛
    public boolean gameOver(int steps) {
        //判断是否有胜利者
        if (null != winner) {//已经存在胜利者了
            return true;
        }
        {
            if (steps >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }
}

Callable

多线程相关知识点
image-20201122124140488

改造下载图片:

package com.itjing.multithread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * 线程创建方式3:实现callable接口
 * 好处:
 * 1.可以定义返回值
 * 2.可以抛出异常
 */

public class TestCallable {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable1 = new MyCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269639&di=be049ee4c5f2a6a3a22a6746630c2567&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201811%2F16%2F20181116195909_klmfb.thumb.700_0.jpg""4.jpg");
        MyCallable myCallable2 = new MyCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269638&di=b57def68f3cf8da1ed310b36e9fb1549&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Ffront%2F511%2Fw1024h1087%2F20190416%2FsUnA-hvsckth5610974.jpg""5.jpg");
        MyCallable myCallable3 = new MyCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606026269638&di=4b3599e3ad4d3c11fb56d87feb09b2ad&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201811%2F16%2F20181116084944_yuwqb.jpg""6.jpg");

        // 创建执行服务
        // 阿里巴巴开发手册建议用ThreadPoolExecutor 创建
        ExecutorService service = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = service.submit(myCallable1);
        Future<Boolean> r2 = service.submit(myCallable2);
        Future<Boolean> r3 = service.submit(myCallable3);

        //获取结果
        Boolean rs1 = r1.get();
        Boolean rs2 = r2.get();
        Boolean rs3 = r3.get();

        //关闭服务
        service.shutdownNow();
    }
}

class MyCallable implements Callable<Boolean{
    private String url; //网络图片地址
    private String name; //保存的文件名


    public MyCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    /*下载图片的线程的执行体*/
    @Override
    public Boolean call() {
        WebDownLoader2 webDownLoader = new WebDownLoader2();
        webDownLoader.download(this.url, this.name);
        System.out.println("下载了的文件名为" + name);
        return true;
    }
}

//下载器
class WebDownLoader2 {
    //下载方法
    public void download(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download方法出现问题!");
        }
    }
}

静态代理

代码案例

package com.itjing.proxy;

/**
 * 静态代理模式总结
 * 真实对象和代理对象都要实现同一个接口
 * 代理对象要代理真实角色
 * 好处:
 * 代理对象可以做很多真实对象做不了的事情(功能增强)
 * 真实对象专注做自己的事情
 */

public class StaticProxy {
    public static void main(String[] args) {
        //用到了同样的思想
        new Thread(() -> System.out.println("我爱你")).start();

        new WeddingComany(new You()).HappyMarry();
    }
}

interface Marry {
    //人间四大喜事
    //久旱逢甘霖
    //他乡遇故知
    //洞房花烛夜
    //金榜题名时
    void HappyMarry();
}

//真实角色 ,你去结婚
class You implements Marry {

    @Override
    public void HappyMarry() {
        System.out.println("我要结婚了,超开心");
    }
}

//代理角色,帮助你结婚
class WeddingComany implements Marry {

    private Marry target;

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

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

    private void before() {
        System.out.println("结婚之前,布置现场");
    }

    private void after() {
        System.out.println("结婚之后,收尾款");
    }
}

Lambda表达式

多线程相关知识点
image-20201122173241507

为什么要使用Lamdba表达式

#避免匿名内部类定义过多
#可以让你的代码看起来很简洁
#去掉了一堆没有意义的代码,只留下核心的逻辑

也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了。

理解 Function Interface(函数式接口)是学习Java8 Lambda表达式的关键所在。

函数式接口的定义

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

例如:

public interface Runnable{
    public abstract void run();
}

对于函数式接口,他们可以通过Lambda表达式来创建该接口的对象。

代码案例:

package com.itjing.lambda;

/**
 * 推导Lambda表达式
 */


public class TestLambda {

    //3. 静态内部类
    static class Like2 implements ILike {

        @Override
        public void lambda() {
            System.out.println("静态内部类");
        }
    }


    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();

        /*调用的是静态内部类*/
        like = new Like2();
        like.lambda();

        //4. 局部内部类
        class Like3 implements ILike {

            @Override
            public void lambda() {
                System.out.println("局部内部类");
            }
        }

        /*调用的是局部内部类*/
        like = new Like3();
        like.lambda();

        //5. 匿名内部类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("匿名内部类");
            }
        };
        like.lambda();


        //6. 使用lambda表达式简化
        like = ()-> System.out.println("使用lambda表达式!");
        like.lambda();
    }
}

//1. 定义一个函数式接口
interface ILike {
    void lambda();
}

//2. 实现类
class Like implements ILike {

    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}
package com.itjing.lambda;

public class TestLambda2 {
    public static void main(String[] args) {
        /*使用lambda表达式简化*/
        ILove love = (int a) -> {
            System.out.println("I love you --> " + a);
        };

        /*简化1. 参数类型*/
        love = (a) -> {
            System.out.println("I love you --> " + a);
        };

        /*简化2. 简化括号*/
        love = a -> {
            System.out.println("I love you --> " + a);
        };

        /*简化3. 去掉花括号*/
        love = a -> System.out.println("I love you --> " + a);

        //总结:
            //lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
            //前提是接口是函数式接口
            //多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

        love.love(520);
    }
}

interface ILove {
    void love(int a);
}

线程相关

多线程相关知识点
image-20201122182623353
多线程相关知识点
image-20201122182653949

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millls) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

停止线程

不推荐使用JDK提供的stop()、destroy()方法。【已废弃】

推荐线程自己停下来

建议使用一个标志位进行终止变量,当flag=false,则终止线程执行。

多线程相关知识点
image-20201122192249531

代码案例:

package com.itjing.state;

/**
 * 测试stop
 * 1. 建议线程正常停止--->利用次数,不建议死循环。
 * 2. 建议使用标志位--->设置一个标志位
 * 3. 不要使用stop或destroy等过时或者JDK不建议使用的方法
 */

public class TestStop {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if (i == 900) {
                myThread.stop();
                System.out.println("线程该终止了");
            }
        }
    }
}

class MyThread implements Runnable {
    //设置一个标志位
    private boolean flag = true;

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

    //设置一个公开的方法停止线程,转换标志位
    public void stop() {
        this.flag = false;
    }
}

线程休眠

sleep(时间)指定当前线程阻塞的毫秒数;

sleep存在异常InterruptedException;

sleep时间达到后,线程进入就绪状态;

sleep可以模拟网络延时,倒计时等;

每一个对象都有一个锁,sleep不会释放锁。

代码案例:(计时)

模拟网络延时:可以放大问题的发生性。

package com.itjing.state;

public class TestSleep {
    public static void main(String[] args) throws InterruptedException {
        tenDown();
    }
    /*模拟倒计时*/
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
    }
}

线程礼让

礼让线程,让当前正在执行的线程暂停,但不阻塞

将线程从运行状态转为就绪状态

让CPU重新调度,礼让不一定成功!看CPU心情!

代码案例:

package com.itjing.state;

/**
 * 测试礼让线程
 * 礼让不一定成功,看CPU心情
 */

public class TestYield {
    public static void main(String[] args) {
        MyYield yield = new MyYield();
        new Thread(yield,"A").start();
        new Thread(yield,"B").start();
    }
}


class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程正在执行!");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName() + "线程停止执行!");
    }
}

Join

join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

可以想象成插队

代码实现:

package com.itjing.state;

/**
 * 可以想象成插队,强制执行,必须等join的线程执行了以后,其它线程才能继续执行,测试join方法
 */

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
        MyJoin join = new MyJoin();
        Thread thread = new Thread(join);
        thread.start();
        for (int i = 1; i <= 500; i++) {
            //200之前main线程和thread交替执行,200以后,main让出时间片让thread执行
            //待thread执行完成后,main线程才继续执行
            if (i == 200) {
                thread.join();
            }
            System.out.println("主线程正在执行" + i);
        }
    }
}

class MyJoin implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 1000; i++) {
            System.out.println("Vip线程来了" + i);
        }
    }
}

线程状态观测

多线程相关知识点
image-20201122202206109

代码实现:

package com.itjing.state;

/**
 * 观察测试线程的状态
 */

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("-----end-----");
        });


        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);// NEW

        //观察启动后
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUNNABLE

        while (state != Thread.State.TERMINATED) {//只要线程不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出状态
        }

//       thread.start();  //死亡后的线程不能再次启动,所以会报错
    }
}

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

线程的优先级用数字表示,范围从1-10.

Thread.MIN_PRIORITY = 1;

Thread.MAX_PRIORITY = 10;

Thread.NORM_PRIORITY = 5;

使用以下方式改变或获取优先级

getPriority()      setPriority(int xxx)

优先级的设定建议在start()调度前。

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。这都看CPU的调调度。

代码案例:

package com.itjing.state;

/**
 * 测试线程的优先级,优先级越大被CPU调用的概率越大
 */

public class TestPriority {
    public static void main(String[] args) {
        /*主线程默认优先级*/
        System.out.println(Thread.currentThread().getName() + "---->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

       /* t5.setPriority(-1); //异常Exception in thread "main" java.lang.IllegalArgumentException
        t5.start();

        t6.setPriority(11); //异常Exception in thread "main" java.lang.IllegalArgumentException
        t6.start();*/

    }
}


class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---->" + Thread.currentThread().getPriority());
    }
}

守护线程(daemon)

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如,后台记录操作日志,监控内存,垃圾回收等待。。。

代码案例:

package com.itjing.state;

/**
 * 测试守护线程
 */

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程
        thread.start();//上帝守护线程启动

        new Thread(you).start();//你  用户线程启动
    }
}

//上帝
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你!");
        }
    }
}
//你
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着!");
        }

        System.out.println("--------goodBye! world!----------");
    }
}

线程同步

多个线程操作同一个资源

多线程相关知识点
image-20201124140721604

线程同步形成条件

多线程相关知识点
image-20201124141233909

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:

1.一个线程持有锁会导致其它所有需要此锁的线程挂起;

2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

3如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

三大不安全案例:

不安全的买票

package com.itjing.syn;

import java.util.concurrent.TimeUnit;

/**
 * 不安全的买票
 */

public class UnsageBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "苦逼的我").start();
        new Thread(station, "牛逼的你们").start();
        new Thread(station, "可恶的黄牛党").start();
    }
}

class BuyTicket implements Runnable {

    //票
    private int ticketNums = 100;
    boolean flag = true//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag) {
            buy();
        }
    }

    //买票方法
    private void buy() {
        //判断是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //模拟延时
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //买票
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票");
    }
}

不安全的取钱

package com.itjing.syn;

import java.util.concurrent.TimeUnit;

/**
 * 不安全的买票
 */

public class UnsageBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station, "苦逼的我").start();
        new Thread(station, "牛逼的你们").start();
        new Thread(station, "可恶的黄牛党").start();
    }
}

class BuyTicket implements Runnable {

    //票
    private int ticketNums = 100;
    boolean flag = true//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag) {
            buy();
        }
    }

    //买票方法
    private void buy() {
        //判断是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //模拟延时
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //买票
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票");
    }
}

不安全的list

package com.itjing.syn;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 线程不安全的集合
 */

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();

        }
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:

synchronized方法 和 synchronized块

同步方法: public synchronized void method (int args){}

synchronized 方法控制对”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

缺陷:若将一个大的方法声明为synchronized将会影响效率

多线程相关知识点
image-20201125101806215

同步块

同步块:synchronized(Obj){}

Obj称之为 同步监视器

Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

同步方法中无需指定同步监视器,因为同步方法的的同步监视器就是this,就是这个对象本身,或者是class【反射中讲解】

同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

synchronized默认锁的是this

改造买票:

private synchronized void buy() {}

改造银行取钱:

 //将run方法中的代码,放到同步块中
 @Override
    public void run() {
  //synchronized 默认锁的是this,取钱操作的是account
        synchronized (account) {
            ......
        }
    }

改造List:

......
synchronized (list) {                    list.add(Thread.currentThread().getName());
}
......

死锁

多线程相关知识点
image-20201125110446377

代码案例:

package com.itjing.deadlock;

import java.util.concurrent.TimeUnit;

/**
 * 死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
 */

public class TestDeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0"灰姑凉");
        Makeup g2 = new Makeup(1"白雪公主");

        g1.start();
        g2.start();
    }
}

//口红
class Lipastick {

}

//镜子
class Mirror {

}

//
class Makeup extends Thread {

    //需要的资源只有一份,用static来保证只有一份
    static Lipastick lipastick = new Lipastick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的人

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipastick) {//获得口红的锁
                System.out.println(this.girlName + "获得口红的锁");
                TimeUnit.SECONDS.sleep(1);
                synchronized (mirror) {//一秒钟后想要获得镜子
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        } else {
            synchronized (mirror) {//获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
                TimeUnit.SECONDS.sleep(2);
                synchronized (lipastick) {//一秒钟后想要获得口红
                    System.out.println(this.girlName + "获得口红的锁");
                }
            }
        }
    }
}

死锁避免方法

产生死锁的四个必要条件:

1.互斥条件:一个资源每次只能被一个进程使用。

2.请求和保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。

3.不剥夺条件:进程已获得的资源,在未使用之前,不能强行剥夺。

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破坏其中的任意一个或多个条件就可以避免死锁发生。

多线程相关知识点
image-20201125113043228

Lock(锁)

多线程相关知识点
image-20201125113325629

ReentrantLock:可重入锁

代码案例:

package com.itjing.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试lock锁
 */

public class TestLock {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(lock, "A").start();
        new Thread(lock, "B").start();
        new Thread(lock, "C").start();
    }
}

class MyLock implements Runnable {

    int ticketNums = 10;

    //可重入锁
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                if (ticketNums > 0) {
                    System.out.println(Thread.currentThread().getName() + "---->第" + ticketNums-- + "票");
                } else {
                    break;
                }
            } finally {
                lock.unlock();//解锁
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

synchronized 与 Lock的对比

多线程相关知识点
image-20201125114557652
多线程相关知识点
image-20201125114626081

线程通信

多线程相关知识点
image-20201125114933886
多线程相关知识点
image-20201125115110538

java提供了几个方法解决线程之间的通信问题

方法名 作用
wait() 表示线程一直等待,直到其他线程通,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

多线程相关知识点
image-20201125174100037
多线程相关知识点
image-20201125174146272

代码案例:(管程法)

package com.itjing.pc;

/**
 * 测试:生产者消费者模型-->利用缓存区解决:管程法
 */

public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        //生产者
        new Productor(container).start();
        //消费者
        new Consumer(container).start();
    }
}

//生产者
class Productor extends Thread {
    SynContainer synContainer;

    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synContainer.push(new Chicken(i));
            System.out.println("生产了" + i + "只鸡");
        }
    }
}

//消费者
class Consumer extends Thread {
    SynContainer synContainer;

    public Consumer(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第" + synContainer.pop().id + "只鸡");
        }
    }
}

//产品,鸡
class Chicken {
    int id;

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

//缓冲区
class SynContainer {

    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];

    //容器技术器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Chicken chicken) {
        //如果容器满了,就需要等待消费者消费
        while (count == chickens.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;

        //通知消费者消费,生产者等待
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop() {
        //判断能否消费
        while (count == 0) {
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;

    }
}

代码案例:(信号灯法)

package com.itjing.pc;

/**
 * 测试生产者消费者--信号法,标志位解决
 */

public class TestPC2 {
    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 < 20; i++) {
            if (i % 2 == 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 < 20; i++) {
            tv.watch();
        }
    }
}

//产品,节目
class TV {
    //演员表演,观众等待   T
    //观众观看,演员等待   F

    String voice;//表演的节目
    boolean flag = true;

    //表演
    public synchronized void play(String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" + voice);
        //通知观众
        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }

    //观看
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了:" + this.voice);

        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

线程池

这里只是简单地了解一下,没有深入底层,等后面学有余力再去深入。

多线程相关知识点
image-20201209100626090
多线程相关知识点
image-20201209100655136

代码案例:

package com.itjing.pool;

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

/**
 * 线程池
 */

public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool  参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new myThread());
        service.execute(new myThread());
        service.execute(new myThread());
        service.execute(new myThread());

        //2.关闭
        service.shutdown();
    }
}

class myThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

总结

这里只是总结了普通的三种创建方式,高级部分暂时没有总结

package com.itjing;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 线程创建方式总结
 */

public class TestThreadSummary {
    public static void main(String[] args) {
        new MyThread1().start();
        new Thread(new MyThread2()).start();

        FutureTask<Integer> task = new FutureTask<>(new MyThread3());
        new Thread(task).start();
        try {
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.集成Thread类
class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread1");
    }
}

//2.实现Runnable接口
class MyThread2 implements Runnable {

    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}

//3.实现Callable接口
class MyThread3 implements Callable<Integer{

    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread3");
        return 100;
    }
}


原文始发于微信公众号(程序员阿晶):多线程相关知识点

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/19718.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!