多线程学习笔记(自己看资料或者视频的学习笔记,如涉及侵权内容请告知,这边及时删除)

多线程的相关知识

进程与线程的关系

一个进程里面可以由多个线程,如视频中同时听声音,看图片,看弹幕等。
程序是一个静态的概念,进程时执行程序的一次执行过程,是一个动态的概念。
真正的多线程是指由多个CPU。即多核,模拟的多线程,是指一个CPU,但是切换的很快,所以有同时执行的错觉。

线程的创建

继承Thread
  • 继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,电泳start()方法开启线程
public class MyThread1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在看代码"+i);
        }
    }

    public static void main(String[] args) {
        // 线程开启不一定立即执行,由cpu调度执行
        // 创建线程对象
        MyThread1 myThread1 = new MyThread1();
        //开启线程
//        myThread1.run();   先走run方法
        myThread1.start(); // 同时进行

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

运行结果:交替输出
注意:线程开启不一定立即执行,由CPU调度
在这里插入图片描述

网图的下载

依赖jar包commons-io-1.4.jar

import org.apache.commons.io.FileUtils;

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

/**
 * 练习Thread,实现多线程下载图片
 */
public class MyThread2 extends Thread {

    private String url; // 下载图片的url
    private String name; // 保存的图片名称

    public MyThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }
   // 下载图片线程的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloadPicture(url,  name);
        System.out.println("下载了图片" + name);
    }


    public static void main(String[] args) {

        MyThread2 t1 = new MyThread2("https://p2.ssl.qhimgs1.com/sdr/400__/t016f54160c5c81c652.jpg", "1.jpg");
        MyThread2 t2 = new MyThread2("https://p0.ssl.qhimgs1.com/sdr/400__/t013ff0809aec190204.jpg", "2.jpg");
        MyThread2 t3 = new MyThread2("https://p0.ssl.qhimgs1.com/sdr/400__/t015bcc85b3e2dfeeb4.jpg", "3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}


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

运行结果
在这里插入图片描述

实现Runnable接口
// 创建线程方式2:实现Runnable接口
public class MyThread3 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在看代码"+i);
        }
    }

    public static void main(String[] args) {
        // 线程开启不一定立即执行,由cpu调度执行
        // 创建线程对象
        MyThread3 myThread3 = new MyThread3();
        //开启线程
        new Thread(myThread3).start();

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

运行结果
在这里插入图片描述

实现Callable接口
好处
  1. 可以定义返回值
  2. 可以抛出异常
import org.apache.commons.io.FileUtils;

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

public class TestCallable implements Callable<Boolean> {

    private String url; // 下载图片的url
    private String name; // 保存的图片名称

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    // 下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownloader1 webDownloader = new WebDownloader1();
        webDownloader.downloadPicture(url,  name);
        System.out.println("下载了图片" + name);
        return true;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {

        TestCallable t1 = new TestCallable("https://p2.ssl.qhimgs1.com/sdr/400__/t016f54160c5c81c652.jpg", "1.jpg");
        TestCallable t2 = new TestCallable("https://p0.ssl.qhimgs1.com/sdr/400__/t013ff0809aec190204.jpg", "2.jpg");
        TestCallable t3 = new TestCallable("https://p0.ssl.qhimgs1.com/sdr/400__/t015bcc85b3e2dfeeb4.jpg", "3.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 b1 = f1.get();
        boolean b2 = f2.get();
        boolean b3 = f3.get();

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


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

线程并发问题的发现

购买汽车票案例如下
// 多个线程操作同一个对象
// 拿买汽车票举例
// 多个线程操作同一个资源时,,线程不安全,数据紊乱
public class MyThread4 implements Runnable {

    private int ticketNums = 10;
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }

            // 模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
        }
    }

    public static void main(String[] args) {
        MyThread4 ticket = new MyThread4();
        new Thread(ticket, "张三").start();
        new Thread(ticket, "李四").start();
        new Thread(ticket, "王二麻子").start();

    }
}

执行结果
在这里插入图片描述

模拟龟兔赛跑
// 模拟龟兔赛跑
public class Race implements Runnable {
    // 胜利者
    private static String winner;

    @Override
    public void run() {
        // 谁先跑到100步谁就是赢家
       for (int i =1; i <= 100; i++) {
           // 模拟兔子睡觉
           if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
               try {
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           boolean flag = gameOver(i);
           // 判断比赛是否结束
           if (flag) {
               break;
           }
           System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
       }


    }

    public boolean gameOver (int steps) {
        if (winner != null) {
            return true;
        } else {
            if (steps>=100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner is" + winner);
                return true;
            }
        }
        return false;
    }

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

运行结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

静态代理模式

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真是对象

好处

  • 代理对象可以做很多真实对象做不了的事情
  • 真实对象专注做自己的事情
public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        // 利用代理对象去调结婚方法,实际执行的是新郎本身
        weddingCompany.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 after() {
        System.out.println("结婚之后,收尾款");
    }

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


}

实现结果
在原有方法的基础上做了一些功能增强
在这里插入图片描述
静态代理模式与多线程之间的关系
在这里插入图片描述

线程的状态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 观测线程状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread( () -> {
            // 睡眠1秒
            for (int i = 0; i < 1; 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);

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

    }
}

执行结果
在这里插入图片描述

线程停止

建议自己写方法来停止线程

public class TestStop implements Runnable {

    // 设置一个标志位,用来判断何时结束线程
    private static boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("run.....Thread" + i++);
        }
    }

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

    public static void main(String[] args) {
        TestStop testStop = new TestStop();

        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if (i == 900) {
                // 通过调用stop()方法,转换标志位,停止线程
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}
线程休眠

sleep(时间)指定当前线程阻塞的毫秒数
sleep存在异常InterruptException
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一个锁,sleep不会释放锁

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

// 模拟十秒倒计时
public class TestSleep {

    public static void main(String[] args) throws InterruptedException {
//        tenDown();
        printCurrentDate();
    }


    // 打印系统当前时间
    public static void  printCurrentDate() {
        Date date = new Date(System.currentTimeMillis());  //获取系统当前时间

        try {
            while (true) {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis()); // 重新获取系统当前时间
            }
        } 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;
            }
        }
    }
}

线程礼让

礼让线程,让当前正在执行的线程停止,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功(有可能还会调用原来礼让的线程),看cpu心情

// 测试礼让线程
// 礼让不一定成功,看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() + "线程停止执行");
    }
}

线程没有礼让的执行结果
在这里插入图片描述
礼让成功的执行结果
在这里插入图片描述

join

join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞,可以想象成插队

// 测试线程强制执行
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        MyJoin myJoin = new MyJoin();
        Thread thread = new Thread(myJoin);
        thread.start();

        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                thread.join();
            }
            System.out.println("main线程" + i);
        }
    }
}

class MyJoin implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("vip  线程来啦" + i);
        }
    }
}

执行结果(在主线程5的时候执行了子线程,子线程跑完了,主线程才开始继续跑)
在这里插入图片描述

线程优先级

优先级高不一定就会执行,只是被调用的几率比较高

// 测试优先级
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(4);
        t2.start();

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

        t4.setPriority(Thread.MIN_PRIORITY); // MIN_PRIORITY = 1
        t4.start();

//        t5.setPriority(-1); // 报错
//        t5.start();

//        t6.setPriority(11); // 报错   优先级范围  1-10
//        t6.start();


    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        // 打印当前线程的优先级
        System.out.println(Thread.currentThread().getName() + "-----" + Thread.currentThread().getPriority());
    }
}

执行结果
在这里插入图片描述

守护线程

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

// 测试守护线程
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        YouNumberOne youNumberOne = new YouNumberOne();
        Thread thread = new Thread(god);
        // 设置上帝为守护线程
        thread.setDaemon(true); // 默认为false 用户线程
        thread.start();
        new Thread(youNumberOne).start();
    }
}

// 上帝
class God implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("上帝守护着你");
        }
    }
}

// 你
class YouNumberOne implements Runnable {

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

}

执行结果
程序会结束,按理来说上帝线程一直再跑,程序是不会停止的,由于上帝线程被设置成了守护线程,虚拟机不会关心守护线程是否执行完毕,只要用户线程结束了,那么程序就可以停止了,所以即使上帝线程没有执行完毕,程序也会停止。
在这里插入图片描述

线程同步

多个线程操作同一个资源
并发:同一个对象被同多个线程操作

3大线程不安全实例
不安全买票
// 不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket, "张三").start();
        new Thread(buyTicket, "李四").start();
        new Thread(buyTicket, "王二麻子").start();
    }
}

// 买票
class BuyTicket implements Runnable{

    // 票数
    private int ticketNums = 10;
    boolean flag = true; // 循环标志位,用来结束线程
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }

    private void buy() {
        if (ticketNums <= 0) {
            flag = false;
            return;
        }

        // 模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNums-- + "张票");
    }
}

执行结果
在这里插入图片描述

不安全取钱
// 不安全的取钱
// 两个人同时去银行去同一张账户里的钱
public class DrawingMoney {
    public static void main(String[] args) {
        Account account = new Account(100, "结婚基金");
        new Drawing(account, 50, "你").start();
        new Drawing(account, 100, "你的妻子").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 reduceMoney; // 取走的钱
    int handMoney; // 手上的钱

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

    @Override
    public void run() {
        if (account.money - reduceMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "余额不足,取不了");
            return;
        }

        // 放大问题,模拟延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        account.money = account.money - reduceMoney; // 余额
        handMoney = handMoney + reduceMoney; // 手上的钱
        System.out.println(Thread.currentThread().getName() + "取走了" + reduceMoney);
        System.out.println(account.name + "余额为:" + account.money);
        System.out.println(Thread.currentThread().getName() + "手上的钱" + handMoney);
    }
}

执行结果
在这里插入图片描述

线程不安全的集合
import java.util.ArrayList;
import java.util.List;

// 线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread( () -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        // 模拟延时
        Thread.sleep(5000);
        System.out.println(list.size());
    }
}

执行结果(正常情况下,结果应该是10000,由于线程不安全,结果并不是我们想要的结果)
在这里插入图片描述

同步方法及同步块

注意:锁的对象应该是变化的量

synchronized 同步方法
// 不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket, "张三").start();
        new Thread(buyTicket, "李四").start();
        new Thread(buyTicket, "王二麻子").start();
    }
}

// 买票
class BuyTicket implements Runnable{

    // 票数
    private int ticketNums = 10;
    boolean flag = true; // 循环标志位,用来结束线程
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }

    // synchronized 同步方法,锁的是当前对象
    private synchronized void buy() {
        if (ticketNums <= 0) {
            flag = false;
            return;
        }

        // 模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNums-- + "张票");
    }
}



synchronized 同步代码块
// 不安全的取钱
// 两个人同时去银行去同一张账户里的钱
public class DrawingMoney {
    public static void main(String[] args) {
        Account account = new Account(100, "结婚基金");
        new Drawing(account, 50, "你").start();
        new Drawing(account, 100, "你的妻子").start();
    }
}

// 账户
class Account{
    int money; // 卡里的存款
    String name; // 卡名

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

// 银行:模拟取钱操作
// synchronized 锁的是银行,但实际应该锁账户,所以用同步代码块,而不能用同步方法
class Drawing extends  Thread{
    Account account; // 账户
    int reduceMoney; // 取走的钱
    int handMoney; // 手上的钱

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

    @Override
    public void run() {
        synchronized (account) {
            if (account.money - reduceMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "余额不足,取不了");
                return;
            }

            // 放大问题,模拟延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            account.money = account.money - reduceMoney; // 余额
            handMoney = handMoney + reduceMoney; // 手上的钱
            System.out.println(Thread.currentThread().getName() + "取走了" + reduceMoney);
            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(Thread.currentThread().getName() + "手上的钱" + handMoney);
        }

    }
}

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

// 线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread( () -> {
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }

            }).start();
        }
        // 模拟延时
        Thread.sleep(5000);
        System.out.println(list.size());
    }
}

CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList;

// 测试JUC安全类型的集合
// 在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,
//用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中
//的 Collection 实现等;
public class TestJUC {

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

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(copyOnWriteArrayList.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; // 化妆人

    // 有参构造方法
    public MakeUp(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void makeUp() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {  // 拿到口红的锁
                System.out.println( this.girlName + "拿到口红的锁");
                Thread.sleep(1000);
                // 睡眠一秒之后拿到镜子的锁
                synchronized (mirror) { // 拿到镜子的锁
                    System.out.println(this.girlName + "拿到镜子的锁");
                }
            }


        } else {
            synchronized (mirror) {  // 拿到镜子的锁
                System.out.println( this.girlName + "拿到镜子的锁");
                Thread.sleep(1000);
                // 睡眠一秒之后拿到镜子的锁
                synchronized (lipstick) { // 拿到口红的锁
                    System.out.println(this.girlName + "拿到口红的锁");
                }
            }


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

互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lock锁

不安全的代码
//  测试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;

    @Override
    public void run() {
        while (true) {
            if (ticketNums > 0 ) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(ticketNums--);
            } else {
                break;
            }
        }
    }
}

执行结果
在这里插入图片描述

加锁之后
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;

    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 {
                lock.unlock(); // 解锁
            }

        }
    }
}

执行结果
在这里插入图片描述

synchronized与lock的对比

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

生产者消费者问题

管程法
// 测试生产者消费者模型,--> 利用缓冲区解决:管程法
// 生产者,消费者,产品,缓冲区
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 synContainer;
    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    // 生产
    @Override
    public void run() {
        for (int i = 1; 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 = 1; 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) {
        // 判断容器是否满了,如果容器满了,就需要等待消费者消费
        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;
    }
}
信号灯法
// 测试生产者消费者问题,信号灯法:标志位
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 = new TV();
    public Player (TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i%2 == 0) {
               tv.play("快乐大本营");
            } else {
                tv.play("抖音");
            }
        }
    }
}

// 消费者
class Watcher extends Thread {
    TV tv = new TV();
    public Watcher (TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            tv.watch();
        }
    }
}

// 节目
class TV {
    // 表演的节目
    String voice;
    // 观众等待,演员表演 T
    // 演员等待,观众观看 F
    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:真正的线程池接口
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable接口
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

// 测试线程池
public class TestPool {

    public static void main(String[] args) {
        // 1.创建服务,创建线程池
        // newFixedThreadPool参数为线程池的大小
        ExecutorService es = Executors.newFixedThreadPool(10);
        // 执行
        es.execute(new MyThread());
        es.execute(new MyThread());
        es.execute(new MyThread());
        es.execute(new MyThread());
        // 关闭连接
        es.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 TotalThread {
    public static void main(String[] args) {
        new TotalThread1().start();
        new Thread(new TotalThread2()).start();
        FutureTask<Integer> future = new FutureTask<Integer>(new TotalThread3());
        new Thread(future).start();

        try {
            Integer i = future.get();
            System.out.println(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}


class TotalThread1 extends Thread {

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

class TotalThread2 implements Runnable {

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

class TotalThread3 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("TotalThread3");
        return 100;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值