Java之多线程详解

news2024/9/23 21:41:38

目录

一、线程简介

进程(Process )与 线程(Thread)

二、线程创建

1、线程Thread

1.1. 步骤

1.2 应用

1.3 案例:下载图片

2、实现Runnable接口

2.1 步骤

2.2 应用

 3.小结

3. 实现Callable接口(了解)

三、静态代理

 四、Lamda表达式

1.无参: 

2.有参

五、线程状态(五大状态)

1. 线程方法:

2. 停止线程

 3. 线程休眠

(1)模拟倒计时:

(2)打印当前时间

 4. 线程礼让

 5. join合并线程(插队)

 6. 观测线程状态

 六、线程优先级

七、守护(daemon)线程

八、线程同步

1.  三大不安全案例

(1)购票

(2)取钱

(3)集合

2.  同步方法

(1)不安全的买票 ---> 使用同步方法

 (2)不安全的取钱 --->  同步块

(3)不安全的集合 ---> 同步块(手动)

 (4) 测试JUC安全类型的集合(java原有类)

九、死锁

十、Lock锁

附加:synchronized 与Lock 的对比

十一、线程协作(生产者消费者模式)、线程通信

1. 并发协作 (生产者和消费者问题)---> 管程法

2. 并发协作(生产者和消费者问题)---> 信号灯法

十二、 线程池

总结:


一、线程简介

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

普通方法调用:只有主线程一条执行路径。

多线程:多条执行路径,主线程和子线程并行交替执行。

一个进程中可以有多个线程,如视频中同时听声音,看图像,看弹幕等。

进程(Process )与 线程(Thread)

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程则是执行程序的一次执行过程(run),它是一个动态的概念,是系统资源分配的单位。

通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,否则没有存在的意义。

线程是CPU调度和执行的单位。

核心:

1. 线程是独立的执行路径;

2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如main(),gc()线程;

3. main()称之为主线程,为系统的入口,用于执行整个程序;

4. 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;

5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;

6. 线程会带来额外的开销,如CPU调度时间,并发控制报销。

7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

二、线程创建

三种创建方式 :

1. 继承Thread类

2. 实现Runnable接口

3. 实现Callable接口

1、线程Thread

每个线程都有优先权。

1.1. 步骤

(1)自定义线程类继承Theard类;

(2)重写run()方法,编写线程执行体;

(3)创建线程对象,调用start()方法启动线程。

1.2 应用

 线程不一定立即执行,CPU安排进度。

1.3 案例:下载图片

要先导入一个包 (直接网上下载即可):

导入的包不能直接用,要先Add as Lidrary...

 

会看到加进去的资源:

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习,实现多线程同步下载图片
public class TestThread2 extends Thread{
    private String url;   //网络图片地址
    private String name;   //保存的文件名

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

    @Override
    public void run(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=nfC2tVNM&id=49E031AAC7715C25D8E03215367A7B4A50E14354&thid=OIP.nfC2tVNM9TgwQ5QuqECd6wHaFj&mediaurl=https%3a%2f%2fimg.mianfeiwendang.com%2fpic%2f65133e4129b6446aa22c9f9f%2f1-810-jpg_6-1080-0-0-1080.jpg&exph=810&expw=1080&q=%e5%9b%be%e7%89%87&simid=608035325172283078&FORM=IRPRST&ck=80CEE6C1EA35720EF9BEBABB8A76BE85&selectedIndex=2&ajaxhist=0&ajaxserp=0","tu1.jpg");
        TestThread2 t2 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=80Dxn%2fcN&id=CC6B486E6CF249AEBC90D7836749A199D90AA629&thid=OIP.80Dxn_cNDbS19cNdvbfFtAHaE8&mediaurl=https%3a%2f%2fts1.cn.mm.bing.net%2fth%2fid%2fR-C.f340f19ff70d0db4b5f5c35dbdb7c5b4%3frik%3dKaYK2ZmhSWeD1w%26riu%3dhttp%253a%252f%252fseopic.699pic.com%252fphoto%252f50042%252f5231.jpg_wh1200.jpg%26ehk%3dxZ6W88VJH8r4Z5ndYYXdcufSGF3FL3fHMuqEMKb34dY%253d%26risl%3d%26pid%3dImgRaw%26r%3d0&exph=800&expw=1200&q=%e5%9b%be%e7%89%87&simid=608018720686148240&FORM=IRPRST&ck=EBDC49650088CF01F97518285859FB91&selectedIndex=10&ajaxhist=0&ajaxserp=0","tu2.jpg");
        TestThread2 t3 = new TestThread2("https://cn.bing.com/images/search?view=detailV2&ccid=ZcjidB6y&id=11729528C58D02867C32B9081DFB13ED01058ADD&thid=OIP.ZcjidB6ytMyNAjg9clT4PAHaNK&mediaurl=https%3a%2f%2fuploadfile.bizhizu.cn%2f2017%2f0701%2f20170701031430776.jpg&exph=1820&expw=1024&q=%e5%9b%be%e7%89%87&simid=608019901792222615&FORM=IRPRST&ck=1FEE61A3AE518883CFE4F534DB8733B6&selectedIndex=0&idpp=overlayview&ajaxhist=0&ajaxserp=0","tu3.jpg");

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


运行结果:

2、实现Runnable接口

2.1 步骤

(1)定义MyRunnable类实现Runnable接口

(2)实现run() 方法,编写线程执行体;

(3)创建线程对象,调用start()方法启动线程。

2.2 应用

public class TestThread3 implements Runnable{
    @Override
    public void run(){
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在"+i);
        }
    }

    public static void main(String[] args) {
       //创建runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();
        //创建线程对象,通过线程对象来开启线程代理
        new Thread(testThread3).start();//交替进行运行

        for (int i = 0; i < 100; i++) {
            System.out.println("我在学习"+i);
        }
    }
}

 3.小结

1. 继承Thread类

        子类继承Thread类具有多线程能力

        启动线程:子类对象. start()

        不建议使用:避免OOP单继承局限性

2. 实现Runnable接口

        实现接口Runnable具有多线程能力

        启动线程:传入目标对象 + Thread对象.strat()

        推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

案例:抢火车票

//多个线程同时操作同一个对象
//购买火车票的例子

public class TestThread4 implements Runnable{
    //票数
    private int ticketNumns = 10;

    @Override
    public void run() {
        while(true){
            if (ticketNumns<=0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"---拿到了第"+ticketNumns--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"我").start();
        new Thread(ticket,"黄牛").start();
    }
}

 发现问题?多个线程操作同一个资源的情况下,线程不安全,数据紊乱。

详细的请从目录移至后续线程同步。

相同案例:《龟兔赛跑》详见:多线程之《龟兔赛跑》案例

3. 实现Callable接口(了解)

1. 实现Callable接口,需要返回值类型

2. 重写call方法,需要抛出异常

3. 创建目标对象

4. 创建执行服务:ExecutorService ser = Executors.nweFixedThreadPool(1);

5. 提交执行:Future<Boolean> result1 = ser.submit(t1);

6. 获取结果:boolean r1 = result1.get()

7. 关闭服务:ser.shutdownNow();

下载资源图片:

import java.util.concurrent.*;
/*
可以定义返回值,可以抛出异常
 */

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

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

    @Override
    public Boolean call(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://cn.bing.com/images/search?view=detailV2&ccid=nfC2tVNM&id=49E031AAC7715C25D8E03215367A7B4A50E14354&thid=OIP.nfC2tVNM9TgwQ5QuqECd6wHaFj&mediaurl=https%3a%2f%2fimg.mianfeiwendang.com%2fpic%2f65133e4129b6446aa22c9f9f%2f1-810-jpg_6-1080-0-0-1080.jpg&exph=810&expw=1080&q=%e5%9b%be%e7%89%87&simid=608035325172283078&FORM=IRPRST&ck=80CEE6C1EA35720EF9BEBABB8A76BE85&selectedIndex=2&ajaxhist=0&ajaxserp=0", "tu1.jpg");
        TestCallable t2 = new TestCallable("https://cn.bing.com/images/search?view=detailV2&ccid=80Dxn%2fcN&id=CC6B486E6CF249AEBC90D7836749A199D90AA629&thid=OIP.80Dxn_cNDbS19cNdvbfFtAHaE8&mediaurl=https%3a%2f%2fts1.cn.mm.bing.net%2fth%2fid%2fR-C.f340f19ff70d0db4b5f5c35dbdb7c5b4%3frik%3dKaYK2ZmhSWeD1w%26riu%3dhttp%253a%252f%252fseopic.699pic.com%252fphoto%252f50042%252f5231.jpg_wh1200.jpg%26ehk%3dxZ6W88VJH8r4Z5ndYYXdcufSGF3FL3fHMuqEMKb34dY%253d%26risl%3d%26pid%3dImgRaw%26r%3d0&exph=800&expw=1200&q=%e5%9b%be%e7%89%87&simid=608018720686148240&FORM=IRPRST&ck=EBDC49650088CF01F97518285859FB91&selectedIndex=10&ajaxhist=0&ajaxserp=0", "tu2.jpg");
        TestCallable t3 = new TestCallable("https://cn.bing.com/images/search?view=detailV2&ccid=ZcjidB6y&id=11729528C58D02867C32B9081DFB13ED01058ADD&thid=OIP.ZcjidB6ytMyNAjg9clT4PAHaNK&mediaurl=https%3a%2f%2fuploadfile.bizhizu.cn%2f2017%2f0701%2f20170701031430776.jpg&exph=1820&expw=1024&q=%e5%9b%be%e7%89%87&simid=608019901792222615&FORM=IRPRST&ck=1FEE61A3AE518883CFE4F534DB8733B6&selectedIndex=0&idpp=overlayview&ajaxhist=0&ajaxserp=0", "tu3.jpg");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

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

        //打印
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);

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

三、静态代理

以你结婚请婚庆公司为案例。    婚庆公司就为代理对象,你就是真实对象。

静态代理模式:

        真实对象和代理对象要实现同一接口;

        代理对象要代理真实角色。

//你结婚去请代理公司
public class StacticProxy {
    public static void main(String[] args) {
        You you = new You();

        new Thread(()-> System.out.println("好")).start();

        new WeddingCompany(you).HappyMarry();

    }
}


interface Marry{
    void HappyMarry();
}

//你自己——真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("开心");
    }
}

//婚庆公司——代理角色
class WeddingCompany implements Marry{
    private Marry target;

    public WeddingCompany(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("结婚之后,清理现场");
    }
}

 四、Lamda表达式

为什么使用lambda表达式?

        避免匿名内部类定义过多。

        可以让代码更简洁。

        去掉没有意义的代码,只留下核心逻辑。

函数式接口的定义:

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

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

1.无参: 

/*
推导lambda表达式
 */
public class TestLambda1 {
    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda(){
            System.out.println("I like lambda2");
        }
    }
    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("I like lambda3");
            }
        }
        like = new Like3();
        like.lambda();

        //5.匿名内部类:没有类的名称,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();

        //6.用lambda简化
        like = ()->{
            System.out.println("I like lambda5");
        };
        like.lambda();

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

//2.实现类
class Like implements ILike{
    @Override
    public void lambda(){
        System.out.println("I like lambda");
    }
}

2.有参


//有参数
public class TestLambda2 {
    public static void main(String[] args) {
        //1.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);

        love.love(2);
    }
}
interface ILove{
    void love(int a);
}

总结:

1. lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,需要使用代码块;

2. 前提是接口为函数式接口,且只有一个方法;

3. 多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号。

五、线程状态(五大状态)

 

1. 线程方法:

(1) 更改线程的优先级

setPriority(int newPriority)

(2) 在指定毫秒数内让当前线程休眠

static void sleep(long mills)

(3) 等待该线程终止

void join()

(4) 暂停当前正在执行的线程对象,并执行其他线程

static void yield()

(5) 中断线程(不推荐使用)

void interrupt()

(6) 测试线程是否处于活动状态

boolean isAlive()

2. 停止线程

不推荐使用stop()、destroy()方法。

推荐线程自己停止下来。

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

//测试stop
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable{
    //1.线程中定义线程体使用的标识位
    private boolean flag = true;

    @Override
    public void run(){
        int i = 0;
        //线程体使用该标识
        while (flag){
            System.out.println("run.....Thread" + i++);
        }
    }
    //2.设置一个公开的方法停止线程,转换标志位
    //对外提供方法,改变标识
    public void stop(){
        this.flag = false;
    }
    
    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread().start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if (i==900){
                //调用自己写的stop方法,切换标志位,停止线程
                testStop.stop();
                System.out.println("线程停止了");
            }
        }
    }
}

线程停止后,主线程仍然运行,直到程序结束 ↓↓↓

 3. 线程休眠

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

sleep存在异常InterruptedException;

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

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

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

(1)模拟倒计时:

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

十秒的倒计时:

(2)打印当前时间

public class TestSleep {
    public static void main(String[] args) {
      //打印当前时间
        Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 打印当前时间,每隔一秒打印一次

 4. 线程礼让

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

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

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

//测试礼让线程
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

礼让失败↓↓↓

礼让成功↓↓↓

 5. join合并线程(插队)

join强制执行线程,待此线程执行完成后,再执行其他线程,其他线程可能阻塞。

//测试join方法(插队)
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("我来插队了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 10; i++) {
            if (i==8){
                thread.join();//插队
            }
            System.out.println("mian"+i);
        }
    }
}

 6. 观测线程状态

Thread.Stata

线程可以处于以下状态之一:

NEW:尚未启动的线程处于此状态。

RUNNABLE:在java虚拟机中执行的线程处于此状态。

BLOCKED:被阻塞等待监视器锁定的线程处于此状态。

WAITING:正在等待另一线程执行特定动作的线程处于此状态。

TIMED WAITING:正在等待另一线程执行动作达到指定等待时间的线程处于此状态。

TERMINATED:已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态,这些状态是不反映任何操作系统线程状态的虚拟机状态。

死亡之后的线程不能再次启动

//观察测试线程状态
public class TestStata {
    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("、、、、、");
        });

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

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

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

 六、线程优先级

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

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

▷Thread.MIN_PRIORITY = 1;

▷Thread.MAX_PRIORITY = 10;

▷ThreadNORM_PRIORITY = 5;

默认优先级是5

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

getPriority()       setPriority(int xxx)

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

        MyPriority myPriority = new MyPriority();

        Thread thread1 = new Thread(myPriority);
        Thread thread2 = new Thread(myPriority);
        Thread thread3 = new Thread(myPriority);
        Thread thread4 = new Thread(myPriority);

        //先设置优先级,再启动
        thread1.start();//默认优先级 5

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(3);
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY=10
        thread4.start();

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

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

七、守护(daemon)线程

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

虚拟机必须确保用户线程执行完毕,虚拟机不用等待守护线程执行完毕。

如:后台记录操作日志、监控内存、垃圾回收等待......

//测试守护线程
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("你开心的活着,第"+i+"天");
        }
        System.out.println("====Bye! World~");
    }
}

八、线程同步

多个线程操作同一个资源。

并发同一个对象多个线程同时操作

处理多线程问题时,多个线程访问同一对象,并且某些线程还想修改这个对象。就需要线程同步。线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前边的线程使用完毕,下一个线程再使用。

形成条件:队列 + 锁

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

①一个线程持有锁会导致其他所有需要此锁的线程挂起;

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

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

1.  三大不安全案例

(1)购票

//不安全的买票
//线程不安全,出现负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"A").start();
        new Thread(buyTicket,"B").start();
        new Thread(buyTicket,"C").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums = 10;
    private boolean flag = true;
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
        //判断是否有票
        if (ticketNums == 0){
            return;
        }
        //模拟延时
        Thread.sleep(1000);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}

 

 

(2)取钱

//不安全的取钱
//两个人去银行取同一个账户的钱
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"共同账户");

        Drawing you = new Drawing(account, 50, "你");
        Drawing he = new Drawing(account, 100, "他");

        you.start();
        he.start();

    }
}

//账户
class Account{
    int money; //余额
    String name; //账户名

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

//银行:模拟
class Drawing extends Thread{
    Account account;   //账户
    //取了多少钱
    int drawingMoney;
    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //判断有没有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"余额不足");
            return;
        }

        //sleep可以放大问题的发生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡里的余额=余额-取出的钱
        account.money = account.money - drawingMoney;
        //手上的钱
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        System.out.println(this.getName()+"手里的钱:"+nowMoney);
    }
}

 

 

(3)集合

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

//线程的不安全集合
//两个线程同时进行,打印出来的数字不够10000
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list= new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

 

 

2.  同步方法

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

synchroniaed方法synchronized块。

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

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

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

(1)不安全的买票 ---> 使用同步方法

在this方法上加锁(synchronized)。

 (2)不安全的取钱 --->  同步块

同步块:synchronized(Obj){}

Obj 称之为同步监听器。

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

        ▶ 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class。

同步监视器的执行过程:

① 第一个线程访问,锁定同步监视器,执行其中代码。

② 第二个线程访问,发现同步监视器被锁定,无法访问。

③ 第一个线程访问完毕,解锁同步监视器。

④ 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

 

 锁的对象是变化的量(需要增删改的对象)

(3)不安全的集合 ---> 同步块(手动)

 (4) 测试JUC安全类型的集合(java原有类)

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

 

九、死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

//死锁:多个线程互相持有对方需要的资源,然后形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0,"灰姑娘");
        MakeUp g2 = new MakeUp(1,"白雪公主");

        g1.start();
        g2.start();
    }
}
//口红
class Lipstick{

}

//镜子
class Mirror{

}

class MakeUp extends Thread{
    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

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

    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 (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
            }
            //1秒后想获得镜子
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
            }
        }else{
            //获得镜子的锁
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
            }
            //2秒后想获得口红
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
            }
        }
    }
}

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

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

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

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

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

十、Lock锁

◉ 通过显示定义同步锁对象来实现同步。

同步锁使用 Lock 对象充当。

◉ java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。

锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。

◉ ReentrantLock(可重入锁) 类实现了 Lock,它拥有与 synchroniaed 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock , 可以显示加锁、释放锁。

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
class TestLock2 implements Runnable{
    int ticketNums = 10;
    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true) {
            try {//保证线程代码安全
                lock.lock();//加锁
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }finally {//如果同步代码有异常,要将unlock()写入finally语句块
                //解锁
                lock.unlock();
            }
        }
    }
}

附加:synchronized 与Lock 的对比

▶ Lock 是显式锁(手动开启和关闭锁,记得关锁);synchronized 是隐式锁,出了作用域自动释放。

▶ Lock只有代码块锁,synchronized 有代码块锁和方法锁。

▶ 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。

▶ 优先使用顺序:

        Lock  > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

十一、线程协作(生产者消费者模式)、线程通信

 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

synchronized 只能实现线程的同步,不能实现线程的通信(消息传递)。

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

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

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

1. 并发协作 (生产者和消费者问题)---> 管程法

//测试:生产者消费者模型 ---> 利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Productor(synContainer).start();
        new Consumer(synContainer).start();
    }
}

//生产者
class Productor extends Thread{
    SynContainer container;
    public Productor( SynContainer container){
        this.container = container;
    }
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;
    public Consumer( SynContainer container){
        this.container = container;
    }
    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第"+container.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){
        //如果容器满了,就需要等待消费者消费
        if (count == chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

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

        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop(){
        //判断能否消费
        if (count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

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

2. 并发协作(生产者和消费者问题)---> 信号灯法

//测试:生产者消费者模型 ---> 利用标志位解决:信号灯法
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{
    //演员表演,观众等待
    //观众观看,演员等待
    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("观众观看了:"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

十二、 线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中, 使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

好处

        提高响应速度(减少了创建新线程的时间)

        降低资源消耗(重复利用线程池中线程,不需要每次都创建)

        便于线程管理

                corePoolSize:核心池的大小

                maximumPoolSize:最大线程数

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

ExecutorService : 真正的线程池接口。常见子类ThreadPoolExecutor

        执行任务/命令,没有返回值,一般用来执行Runnable.

void exectute(Runnable command)

        执行任务,有返回值,一般有用来执行Callable

<T> Future<T> submit(Callable<T> task)

        关闭线程池

void shutdown()

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

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());
    }
}

 

总结:

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

//回顾总结线程的创建
public class ThreadNew {
    public static void main(String[] args) {
        new MyThread1().start();

        new Thread(new MyThread2()).start();

        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();

        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } 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;
    }
}

 

内容较多!

重在理解!!

感谢ლ(°◕‵ƹ′◕ლ)!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/137445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java开发的考研系统大学生考研推荐网站考研网站源码

简介&#xff1a; 考研信息推荐查询。主要是管理发布管理考研的知识文章&#xff0c;或者上传资料&#xff0c;发布考研的视频。学生可以注册后下载资料&#xff0c;查看考研文章视频等。文章分为vip文章和普通文章&#xff0c;学生查看vip文章需要消耗积分。 演示视频 https…

FX5U 原点回归指令 DSZR

上一篇文章中转述了网友的文章&#xff0c;因回原点实在重要&#xff0c;再详细描述DSZR指令。 DSZR是具有自动搜索功能的原点回归指令。它对当前位置没有要求&#xff0c;在任意位置哪怕是停止在限位开关位置上都能完成原点回归操作。 1.指令格式 S1 原点回归速度或存储了数…

linux虚拟机搭建kafka(单节点、使用kafka自带zookeeper)

本文使用kafka单节点安装及配置&#xff0c;并使用kafka自带的zookeeper。一般kafka需要起三个kafka构成集群&#xff0c;可以连单独的zookeeper&#xff0c;本文不涉及。一、kafka下载解压安装包下载地址&#xff1a;https://archive.apache.org/dist/kafka/2.5.0/kafka_2.12-…

MyPerf4J结合Grafana和InfluxDB采集JVM以及QPS指标

MyPerf4J结合Grafana和InfluxDB采集JVM以及QPS指标 背景 ​ 需要采集现场java程序运行的状态数据(包括JVM指标以及QPS,RPS指标等)。需要采集的方式尽可能轻量化 ​ 结合实际情况采用MyPerf4J作为Java探针&#xff0c;InfluxDB作为数据存储端&#xff0c;Grafana作为数据展示…

15---整合Echarts和完善头像上传

1、完善头像上传功能 上次写的头像上传功能&#xff0c;不能实现上传保存后立刻刷新右上角头像&#xff0c;这里做一个完善。首先是在Manage.vue中&#xff08;父&#xff09;&#xff0c;写刷新User的方法 //传一个user过去到header <Header :collapseBtnClass"col…

【信息论与编码 沈连丰】第三章:离散信源

【信息论与编码 沈连丰】第三章&#xff1a;离散信源第三章 离散信源3.1 离散信源的分类及其描述3.2 离散信源的熵3.3 信源的冗余度3.4 信源符号序列分组定理3.5 平稳离散信源及其性质第三章 离散信源 3.1 离散信源的分类及其描述 信源分类&#xff1a;本质上主要基于两方面来…

fastai教程学习笔记

这几天对着fastai教程读了下&#xff0c;大部分写得已经很不错。这里做点知识精炼的笔记。 安装fastai 推荐在conda环境内执行以下命令。它 pip install fastaifastai架构 fastai的编程架构如下图所示&#xff1a; fastai提供了高、中、低三层的API&#xff0c;用户可以根…

73、【哈希表】leetcode——15. 三数之和(C++版本)

题目描述 原题链接&#xff1a;15. 三数之和 解题思路 本题的难点在于去重&#xff0c;针对两种不同的方式&#xff1a;双指针和Hash采用不同的去重判定条件。 1、去重的目标 要明确&#xff0c;去重的是重复三元组&#xff0c;而不是三元组里重复的数。 2、去重初步思路 …

商业与数据生态议题解读,Doris Summit 2022 分论坛议程介绍|即刻报名

Doris Summit 2022 将于1 月 6 -7 日在线上正式举办&#xff0c;本次峰会共分2 天进行&#xff0c;首日上午为主论坛&#xff1a;核心技术解析&#xff0c;下午为商业与数据生态分论坛&#xff0c;7 日全天为行业用户最佳实践案例。大会汇聚了来自全球顶尖云厂商、一线互联网企…

CoMER论文翻译

文章目录Abstract1、Introduction2、Related Work2.1 HMER Methods2.2 Coverage Mechanism3、Methodology3.1、Background3.2、CNN Encoder3.3、Positional Encoding3.4、Attention Refinement Module3.5、Coverage4 Experiments论文链接&#xff1a;https://arxiv.org/abs/220…

数字图像处理 图像对比度增强算法概览

一、图像对比度增强 图像对比度增强又叫作图像对比度拉伸或者直接称为点运算。图像亮度和对比度调整的目的之一是在合适的亮度上提供最大的细节信息&#xff0c;细节纹理的沟纹越深&#xff0c;图像越清晰。在图像处理中&#xff0c;图像对比度增强是最基本的、原理比较简单却很…

【前端】Vuex模块化和持久化应用示例

概述 Vuex作为VUE状态管理组件&#xff0c;能够将项目公共数据进行统一管理。而且可以按照不同的业务功能将数据状态分模块管理。另外&#xff0c;对于网页刷新导致Vuex状态丢失的问题可以使用vuex-persistedstate插件配置将数据保存在localStorage或者sessionStorage中。 本…

「Python|场景案例」如何给图片添加水印

本文主要介绍如何使用python的PIL库给图片增加水印 文章目录背景说明工具准备处理步骤源代码处理效果展示背景说明 当我们想给一些图片添加水印的时候&#xff0c;尤其是图片数量较多的时候&#xff0c;就可以使用python进行自动化处理。包括但不限于在自媒体上发布自己的各种…

如何使用自助式商业智能 (BI) 避免组织中的数据孤岛

许多组织都存在数据问题。当许多员工远程工作&#xff08;或在混合环境中&#xff09;并在多个位置使用多个设备访问公司数据时&#xff0c;他们正在处理信息过载问题。这只会加剧数据孤岛的问题。 数据孤岛正是它听起来的样子&#xff1a;孤立在一个孤立的用户/环境中的数据&…

jdk版本和Class编译版本对应关系

JDK version和class file version(Class编译版本号)对应关系 JDK 17 61, JDK 16 60, JDK 15 59, JDK 14 58, JDK 13 57, JDK 12 56, JDK 11 55, JDK 10 54, JDK 9 53, JDK 8 52, JDK 7 51, JDK 6.0 50, JDK 5.0 …

数据结构-归并排序

一、概念及其介绍 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分治法(Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每…

三年“云改”,移动云这份答卷有多“硬”?

作者 | 曾响铃 文 | 响铃说 云计算是推动数字经济与实体经济深度融合的催化剂&#xff0c;是重点领域数字产业发展的助推器。近年来我国云计算产业发展提速&#xff0c;加快推动实体企业转型升级和创新发展。 2022年是移动云实施“云改”战略的第3年&#xff0c;也是移动云全…

【人工智能】基于五笔字型规范和人工神经网络的简中汉字识别【二】

环境搭建 一、NVIDIA驱动安装与更新二、Anaconda安装三、Pytorch安装四、验证CUDA和cudnn版本一、NVIDIA驱动安装与更新 显卡驱动就是用来驱动显卡的程序,它是硬件所对应的软件。 正常情况下,配有显卡的电脑都是安装有驱动程序的,但是有的时候驱动可能版本较低,所支持的 …

爬虫内容学习-工具类---Selenium

一、爬虫学习建议&#xff1a; 在编写python爬虫程序时&#xff0c;只需要做以下两件事&#xff1a; 发送GET请求&#xff0c;获取HTML [第一类] 解析HTML&#xff0c;获取数据 [第二类] 这两件事&#xff0c;python都有相应的库帮你去做&#xff0c;你只需要知道…

关乎你我,2022年都经历了哪些安全事件?|上云那些事

2022年&#xff0c;网络安全跟人们的工作生活关联愈发紧密。腾讯安全联合南方日报、南方&#xff0c;携手共建《上云那些事》栏目&#xff0c;为企业提供网络安全建设新思路&#xff0c;帮助大家及时发现身边的网络安全陷阱&#xff0c;提升安全意识和防范能力&#xff0c;减少…