多线程入门级教学!!!

多线程

线程的三种创建方式

Thread(继承)

  1. 将一个类继承Thread

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

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

    //创建线程方法一,继承Thread类,重写run()方法,调用start()开启线程
    //总结:线程开启不一定立即执行,由cpu调度执行!
    public class Test extends Thread{
        @Override
        public void run() {
            //线程执行代码
            for(int i=0;i<=20;i++){
                System.out.println("多线程学习"+i);
            }
        }
    
        public static void main(String[] args) {
            //创建一个线程对象
            Test testThread=new Test();
            //调用start()方法开启线程
            testThread.start();
            //我们可以知道两条线程是交替执行的!
            //主线程,main线程
            for(int i=0;i<=20;i++){
                System.out.println("我在学习多线程"+i);
            }
        }
    }
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pX8DfPa7-1617895406766)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1617852035095.png)]

通过Thread继承方式实现同时下载多张图
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.io.FileUtils;

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

/**
 * @author 霖磬
 * @remark 多线程练习,通过多线程同时去下载多张图片
 * @createTime 2021年4月8日11:27:00
 * @version 1.0
 * */
@AllArgsConstructor
@NoArgsConstructor
public class ThreadTest2 extends Thread {
    private String url;//地址
    private String name;//保存文件名
    @Override
    public void run() {
        //创建下载器对象
        WebDownloader webDownloader=new WebDownloader();
        //调用下载方法
        webDownloader.downloader(url,name);
        //打印下载文件名和地址
        System.out.println("地址名:"+url);
        System.out.println("文件名:"+name);
    }

    public static void main(String[] args){
        //创建三个线程
        ThreadTest2 t1=new ThreadTest2("https://linqin-edu.oss-cn-beijing.aliyuncs.com/aila001.jpg","艾拉");
        ThreadTest2 t2=new ThreadTest2("https://linqin-edu.oss-cn-beijing.aliyuncs.com/2021/03/16/e8e45ed40cae4d9e840974803ba91eealobby-miku002.jpg","初音");
        ThreadTest2 t3=new ThreadTest2("https://linqin-edu.oss-cn-beijing.aliyuncs.com/2021/03/15/5bcfabd2b01644bb977501c3979383adfile.png","初音2");
        //执行这三个线程,我们可以知道执行顺序是由cpu调度来决定的
        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("downloader方法出现问题,io异常");
        }

    }
}

我们可以知道执行顺序是由cpu调度来决定的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8wt9mUd2-1617895406767)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1617853478480.png)]

Runnable(实现)

//通过实现Runnable接口来实现线程的创建
public class ThreadRunnableTest implements Runnable{
    //实现接口run
    @Override
    public void run() {
        //线程执行代码
        for(int i=0;i<=20;i++){
            System.out.println("多线程学习"+i);
        }
    }

    public static void main(String[] args) {
        /**
         继承Thread类的实现方法
        //创建一个线程对象
        ThreadTest testThread=new ThreadTest();
        //调用start()方法开启线程
        testThread.start();
        **/
        //我们可以知道两条线程是交替执行的!
        //Runnable来执行,创建Thread对象,将线程对象丢进去,在调用start()来执行
        new Thread(new ThreadRunnableTest()).start();
        //主线程,main线程
        for(int i=200;i>=0;i--){
            System.out.println("我在学习多线程"+i);
        }
    }
}

Thread其源码也是实现Runnable接口!!

并发初识
//多线程操作一个线程 引发的并发问题
//模拟环境:火车票购票
public class ThreadRunnableTest2 implements  Runnable{
    //票数
    private int ticketNums=10;//票数
    @Override
    public void run() {
        //不断去买票直到票没了
        while(true){
            //如果票没了则停止购票
            if(ticketNums<=0){
                break;
            }
            //模拟延时 没200毫秒去拿票
            try{
                Thread.sleep(200);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        ThreadRunnableTest2 threadRunnableTest2=new ThreadRunnableTest2();
        //模拟三个对象来抢票 小明同学、老师、黄牛
        new Thread(threadRunnableTest2,"小明").start();
        new Thread(threadRunnableTest2,"老师").start();
        new Thread(threadRunnableTest2,"黄牛").start();
    }

}

运行结果

老师--->拿到了第10张票
小明--->拿到了第8张票
黄牛--->拿到了第9张票
    老师--->拿到了第7张票
    黄牛--->拿到了第7张票
    小明--->拿到了第7张票
黄牛--->拿到了第5张票
老师--->拿到了第4张票
小明--->拿到了第6张票
小明--->拿到了第3张票
老师--->拿到了第1张票
黄牛--->拿到了第2张票

我们可以发现,三者都可能抢到同一张票,这就是并发问题

多个线程操作同一个资源的时,线程不安全,数据紊乱

我们用一个简单的例子在模拟一下

龟兔赛跑
  1. 首先来个赛道距离,然后要离终点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 故事中是乌龟赢,兔子需要睡觉, 我们来模拟兔子睡觉
  6. 最终,乌龟获胜
package com.lin.demo;

/**
 * @author 霖磬
 * @remark 模拟龟兔赛跑
 * @createTime 2021年4月8日13:22:26
 * @version 1.0
 * */
public class Race implements  Runnable{
    //胜利者
    private static String winner;

    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            //判断比赛是否结束
            boolean flag=gameOver(i);
            //模拟兔子睡觉
            if(Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    Thread.sleep((long) 0.1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果比赛结束则停止程序
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-----》跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判读是否存在胜利者
        if(winner!=null){
            return true;
        }
        //谁先跑到一百步谁就赢
        if(steps==100){
            winner=Thread.currentThread().getName();
            System.out.println(winner+"赢了");
            return true;
        }

        return false;
    }

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

前两种方式 小结

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象.start();
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:创建Thread对象传入目标对象在调用start()方法
  • 推荐使用:避免单继承局限性,灵活多变,方便同一个对象被多个线程使用

Callable(实现)

  1. 实现Callable接口,需要返回值
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1)
  5. 提交执行:Future result1=ser.submit(1)
  6. 获取结果:boolean r1=result1.get()
  7. 关闭服务:ser.shutdownNow();
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;

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

/**
 * @author 霖磬
 * @remark 多线程练习,通过多线程同时去下载多张图片
 * @createTime 2021年4月8日11:27:00
 * @version 1.0
 * */
/*
    callable可以定义返回值,可以抛出异常
*/
@AllArgsConstructor
@NoArgsConstructor
public class ThreadTest3 implements Callable<Boolean> {
    private String url;//地址
    private String name;//保存文件名
    @Override
    public Boolean call() {
        //创建下载器对象
        WebDownloaders webDownloader=new WebDownloaders();
        //调用下载方法
        webDownloader.downloader(url,name);
        //打印下载文件名和地址
        System.out.println("地址名:"+url);
        System.out.println("文件名:"+name);
        return true;
    }

    @SneakyThrows
    public static void main(String[] args){
        //创建三个线程
        ThreadTest3 t1=new ThreadTest3("https://linqin-edu.oss-cn-beijing.aliyuncs.com/aila001.jpg","艾拉.jpg");
        ThreadTest3 t2=new ThreadTest3("https://linqin-edu.oss-cn-beijing.aliyuncs.com/2021/03/16/e8e45ed40cae4d9e840974803ba91eealobby-miku002.jpg","初音.jpg");
        ThreadTest3 t3=new ThreadTest3("https://linqin-edu.oss-cn-beijing.aliyuncs.com/2021/03/15/5bcfabd2b01644bb977501c3979383adfile.png","初音2.jpg");
        //创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> f1=ser.submit(t1);
        Future<Boolean> f2=ser.submit(t2);
        Future<Boolean> f3=ser.submit(t3);
        //获取结果
        boolean rs1=f1.get();
        boolean rs2=f2.get();
        boolean rs3=f3.get();
        //关闭服务
        ser.shutdownNow();
    }
}

class WebDownloaders{
    //下载方法
    public void downloader(String url,String name){
        try {
            //文件下载
            FileUtils.copyURLToFile(new URL(url), new File(name));
        }catch (IOException e){
            e.printStackTrace();
            System.out.println("downloader方法出现问题,io异常");
        }

    }
}

Callable相较于前两者更加复杂!

Lambda表达式

他的出现是为了精简代码,简化程序!

为什么使用他?

  1. 避免匿名内部类定义过多
  2. 让代码看起来更加简洁
  3. 去掉了一堆没有意义的代码,只留下核心逻辑

理解函数式接口:任何接口只包含一个抽象方法,那么他就是函数式接口

第一个例子、

package lambda;

//推到Lambda表达式
public class TestLambda {
    //静态内部类
    static class Like2 implements ILike{
        @Override
        public void lin() {
            System.out.println("实现ILke2");
        }
    }

    public static void main(String[] args) {
        //1、实例化创建类调用
        ILike iLike=new Like();
        iLike.lin();
        //2、静态内部类调用
        ILike like=new Like2();
        like.lin();
        //3、局部内部类
        class Like3 implements ILike{
            @Override
            public void lin() {
                System.out.println("实现ILke3");
            }
        }
        ILike like3=new Like3();
        like3.lin();
        //4、匿名内部类
        like=new ILike() {
            @Override
            public void lin() {
                System.out.println("匿名实现ILke");
            }
        };
        like.lin();

        //5、使用lambda简化
        like=()->{
            System.out.println("lambda简化使用");
        };
        like.lin();
    }
}

//定义一个函数是接口
interface ILike{
    void lin();
}

//实现类
class Like implements ILike{
    @Override
    public void lin() {
        System.out.println("实现ILke");
    }
}

第二个例子、

package lambda;

//推到Lambda表达式
public class TestLambda2 {
    public static void main(String[] args) {
        //1、实例化创建类调用
        ILike2 like=(int a)->{
            System.out.println("1lambda"+a);
        };
        like.lin(520);
        //2、简化 去除参数类型
        ILike2 like2=(a)->{
            System.out.println("2lambda"+a);
        };
        like2.lin(520);
        //3、简化括号 因为其只有单个参数 所有才可以去除
        ILike2 like3=a->{
            System.out.println("3lambda"+a);
        };
        like3.lin(520);
        //4、简化花括号 因为其执行逻辑只有一行,所以才可以去除花括号
        ILike2 like4=a-> System.out.println("4lambad"+a);
        like4.lin(520);
    }
}

//定义一个函数是接口
interface ILike2{
    void lin(int a);
}

Lambda总结

  1. lambda的使用前提是接口为函数式接口
  2. 可以近一步简化
  3. 去除参数类型
  4. 简化括号 因为其只有单个参数 所有才可以去除
  5. 简化花括号 因为其执行逻辑只有一行,所以才可以去除花括号

线程状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrzRmdW6-1617895406768)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1617865551561.png)]

五个状态: 创建状态、就绪状态、阻塞状态、运行状态、死亡状态

可以这样理解:当我们通过new创建一个线程时为创建状态,在通过start()方法启动线程,线程进入就绪状态(注意进入此状态并不会立刻执行,而是要经过Cpu的调度才能进入运行状态),进入运行状态分为两种情况,一种情况是你通过某种手段(wait()、sleep()等方法休眠了线程),或者直接执行完毕进入死亡状态

线程方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imljWopC-1617895406772)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1617866073119.png)]

停止线程

  1. 不推荐使用JDK提供的stop()、destroy()方法【已废弃】
  2. 推荐线程自己停下来
  3. 建议使用一个boolear值来终止,当布尔值=false时终止线程
package stop;

public class ThreadStopTest implements Runnable{
    //1、设置标志位
    private boolean flag=true;
    @Override
    public void run() {
        int i=1;
        while(flag){
            i++;
            System.out.println("执行了--->"+i);
        }
    }
    //2、创建一个公开方法来停止线程
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        ThreadStopTest threadStopTest=new ThreadStopTest();
        new Thread(threadStopTest).start();

        for (int i=0;i<1000;i++){
            if(i==900){
                System.out.println("停止线程");
                threadStopTest.stop();

            }
        }
    }

}

sleep方法 模拟网络延时

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

案例一、模拟倒计时

package stop;

import com.sun.org.apache.bcel.internal.generic.NEW;

//模拟倒计时
public class TestSleep {
    //倒计时方法
    public void tenDown() throws InterruptedException {
        int i=10;
        while(true) {
            //每一秒执行一次
            Thread.sleep(1000);
            System.out.println(i--);
            if (i <= 0) {
                break;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestSleep sleep= new TestSleep();
        sleep.tenDown();
    }
}
线程休眠 Thread.sleep()方法
  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常
  • sleep时间到了会重新进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

线程礼让 Thread.yield()方法

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让可能失败,得看cpu心情

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQ7qSpiX-1617895406773)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1617868334494.png)]

join方法 合并线程

  • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
  • 可以想象成插队
package stop;

//想象成插队 join方法
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 Exception {
        //启动主main
        for(int i=0;i<300;i++){
           if(i==200){
               //启动线程
               TestJoin join=new TestJoin();
               Thread thread=new Thread(join);
               thread.start();
               //线程插队
               thread.join();//插队
           }
            System.out.println("main=》"+i);
        }
    }
}

线程优先级

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

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

    • Thread.MIN PRIORITY= 1;
    • Thread. MAX PRIORITY= 10;
    • Thread.NORM PRIORITY = 5;
  • 使用一下方式来改变或获取优先级

    • getPriority().setPriority(int xxx)
package stop;

//测试线程优先级
public class TestPriority{

    public static void main(String[] args) {
        //创建线程对象
        MyPriority myPriority=new MyPriority();
        //创建四个 设置优先级 并执行
        Thread t1=new Thread(myPriority,"第一");
        t1.setPriority(1);//第一个
        t1.start();

        Thread t2=new Thread(myPriority,"第五");
        t2.setPriority(Thread.NORM_PRIORITY);//第五
        t2.start();

        Thread t3=new Thread(myPriority,"第二");
        t3.setPriority(2);//第二
        t3.start();

        Thread t4=new Thread(myPriority,"第三");
        t4.setPriority(3);//第三
        t4.start();
    }
}

class MyPriority  implements Runnable{
    @Override
    public void run() {
        System.out.println("线程名--->"+Thread.currentThread().getName()+"/t线程优先级---->"+Thread.currentThread().getPriority());
    }
}

注意:优先级低只是意味着获取调度的概率低,并不是优先级低而就不执行了,这都得看cpu的调度

守护线程(daemon)

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如后台记录操作日志,监控内存,垃圾回收等…
package stop;

//守护线程测试
public class DaemonTest {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();
        //开启上帝线程
        Thread thread=new Thread(god);
        //设置其为上帝线程 默认为false
        thread.setDaemon(true);
        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("《《《《《《《《《《离开了这个世界》》》》》》》》》》》》");
    }
}

线程同步

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时也带来了随机冲突问题,为了保证数据在方法中被随机访问时的正确性,在访问时加入了锁机制(synchronized),当一个线程获取到对象的排它锁,独占资源而其他对象则需要排队,使用是释放锁即可,但可能会存在以下问题:

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

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

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

    同步方法
    • 由于我们可以通过private来保证数据只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就synchronized关键字,他包括两种用法:synchronized方法和synchronized块
    • synchronized方法控制对"对象的访问",每个对象都对应一把锁,每个synchronized方法都必须获取调用该方法的对象的锁才能执行,否则会造成线程阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的锁才能进入这个锁继续执行。

    注意:将一个方法声明为synchronized为影响效率

同步块
  • 同步块 synchronized**(OBJ){}**
  • Obj称之为同步监听器
    • Obj可以是任何对象,但是推荐使用共享资源作为同步监听器
    • 同步方法无需指定同步监听器,因为同步方法的同步监听器就是其本身,或者是class
  • 同步监听器的执行过程
    1. 第一个线程访问,锁定同步监听器,执行其代码
    2. 第二个线程访问,发现同步监听器被锁定,无法执行
    3. 第一个线程访问完毕,解锁同步监听器
    4. 第二个线程,发现无锁后则去锁定访问

死锁

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

import lombok.SneakyThrows;

//多个线程互相持有对方需要的资源从而形成僵持
public class DeadLock {
    public static void main(String[] args) {
        MackUp m1=new MackUp(0,"霖磬");
        MackUp m2=new MackUp(1,"赵瑾茜");
        m1.start();
        m2.start();
    }
}

//钱
class Money{

}

//房子
class Room{

}

class MackUp extends Thread{
    //需要的资源只有一份
    static Money money=new Money();
    static Room room=new Room();
    int choice;//选择
    String name;//使用的人

    MackUp(int choice,String name){
        this.choice=choice;
        this.name=name;
    }
    @Override
    public void run() {
        try {
            layOut();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //花钱,互相持有对方的锁,就是需要拿到对方的资源
    private void layOut() throws InterruptedException {
        if(choice==0){
            synchronized(money){//获得钱的锁
                System.out.println(name+"获取钱的锁");
                Thread.sleep(1000);
                synchronized(room){//一秒钟后买房的锁
                    System.out.println(name+"获取房子的锁");
                }
            }
        }else{
            synchronized(room){//获取房子的锁
                System.out.println(name+"获取房子的锁");
                Thread.sleep(2000);
                synchronized(money){//二秒钟后钱的锁
                    System.out.println(name+"获取钱的锁");
                }
            }
        }
    }
}

以上会产生死锁问题,这个时候只要把里面的锁拿出来单独使用即可解决

 //花钱,互相持有对方的锁,就是需要拿到对方的资源
    private void layOut() throws InterruptedException {
        if(choice==0){
            synchronized(money){//获得钱的锁
                System.out.println(name+"获取钱的锁");
                Thread.sleep(1000);
            }
            synchronized(room){//一秒钟后买房的锁
                System.out.println(name+"获取房子的锁");
            }
        }else{
            synchronized(room){//获取房子的锁
                System.out.println(name+"获取房子的锁");
                Thread.sleep(2000);
            }
            synchronized(money){//二秒钟后钱的锁
                System.out.println(name+"获取钱的锁");
            }
        }
    }
产生死锁的四个必要条件
  1. 互斥条件:一个资源每次只能被一个用户使用
  2. 请求与保持条件:一个进程因请求资源而阻塞,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源在未使用完的情况下不能被强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

上列问题是死锁的必要出现条件,只需要攻破任意一种即可避免死锁问题

Lock(锁)

  • 从jdk1.5开始,java提供了更强大的线程同步机制—通过显示定义同步锁对象来实现同步.。同步锁用Lock对象来充当
  • Lock接口是控制多个线程对共享资源进行访问的工具.锁提供了对共享资源的独占访问,每一个对象只能有一个线程对Lock对象加锁,在访问之前应当先获取Lock对象
  • ReentrantLock类实现了Lock,他拥有synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
package stop;

import java.util.concurrent.locks.ReentrantLock;

//模拟倒计时
public class TestLock {
    private final ReentrantLock lock=new ReentrantLock();
    //倒计时方法
    public void tenDown() throws InterruptedException {

        int i = 10;
        while (true) {
            try {
                lock.lock();//加锁

                //每一秒执行一次
                Thread.sleep(1000);
                System.out.println(i--);
                if (i <= 0) {
                    break;
                }
            } finally {
                lock.unlock();//解锁
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestLock sleep= new TestLock();
        sleep.tenDown();//执行完一个再去执行下一个
        sleep.tenDown();
        sleep.tenDown();

    }
}

通常使用try finally来加锁以及释放锁

synchronized与Lock对比
  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

线程协作 - 生产者消费者模式

Thread.wait()方法 线程等待方法

Thread.notifyAll()方法 通知线程执行

1.线程通信

(1)应用场景:生产者和消费者问题

  • 假设仓库中只能存放- -件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.

(2)分析:

  • 这是一个线程同步问题,生产者和消费者共享同- -个资源,并且生产者和消费者之间相互依赖,互为条件.
  • 对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.
  • 在生产者消费者问题中,仅有synchronized是不够的
  • synchronized可阻止并发更新同一个共享资源,实现了同步
  • synchronized不能用来实现不同线程之间的消息传递(通信)

(3)解决方式:
并发协作模型“生产者/消费者模式”–>

案例一、管程法

package lambda;

//测试:生产者消费者模型---》利用缓冲区解决:管程法
//生产者、消费者、缓冲区、产品
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;
    }
}

案例二、信号灯灯法

package lambda;

public class Test {
    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("观看了:"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }

}

线程池

使用线程池
◆背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
◆思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
◆好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理…
corePoolSize: 核心池的大小
maximumPoolSize:最大线程数
keepAliveTime: 线程没有任务时最多保持多长时间后会终止

◆JDK 5.0起提供了线程池相关API: ExecutorService 和Executors
◆ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
void shutdown() :关闭连接池
◆Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值