Thread:线程类

本文总结自,B站-遇见狂神说

1. 进程与线程

在这里插入图片描述

程序

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

进程(Process)

是程序的一次过程,它是一个动态的概念。是系统资源分配的单位

线程

  • 一个进程可以包含若干个线程
  • 一个进程中至少有一个线程(不然没有意义)
  • 线程是CPU调度和执行的单位

多线程

真正的多线程是指有多个CPU,即多核。如果是模拟出来的多线程(即一个PCU的情况下),在同一时间点,CPU只能执行一个代码,因为切换的很快,所以产生同时执行的错觉。

2. 核心内容

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。
  • 主线程main(),为系统的入口,用于执行整个程序。
  • 在一个进程中,如果开辟了多个线程,线程 的运行由调度器安排,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  • 对同一份资源操作时,会存在抢夺资源的问题,需要加入并发控制;
  • 线程会带来额外开销,如CPU调度时间,并发控制开销;
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;

3. 三种创建方式

image-20200618091917713

继承Thread类

public class ThreadExtends extends Thread {
    @Override
    public void run(){
        // run方法线程体
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法线程体--"+ i);
        }
    }

    public static void main(String[] args) {
        // main线程,主线程

        ThreadExtends t = new ThreadExtends();

        // 开启线程(和主线程同时跑)
        t.start();
        // 先跑子线程
        // t.run();

        for (int i = 0; i <= 20; i++) {
            System.out.println("主线程--"+ i);
        }
    }
}

结果:

...
run方法线程体--6
run方法线程体--7
run方法线程体--8
主线程--2
run方法线程体--9
主线程--3
...

案例:网图下载

实现多线程下载图片【导包:commons-io-2.7.jar】

public class ThreadDown_Extends extends Thread {

    /**
     * 网络图片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全参构造方法
     * @param url
     * @param name
     */
    public ThreadDown_Extends(String url, String name) {
        this.url = url;
        this.name = name;
    }

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

下载类

public class WebDownloader{
    /**
     * 下载方法
     * @param url
     * @param name
     */
    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异常,downloder方法出现问题...");
        }
    }
}

测试类

public class TestThread {
    public static void main(String[] args) {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times+".png";
        testExtends(url, name);
    }
    /**
     * 测试创建多线成方法:继承线程类
     */
    private static void testExtends(String url, String name) {
        ThreadDown_Extends td1 = new ThreadDown_Extends(url, name);
        ThreadDown_Extends td2 = new ThreadDown_Extends(url, name);
        ThreadDown_Extends td3 = new ThreadDown_Extends(url, name);

        td1.start();
        td2.start();
        td3.start();
    }

}

实现Runable接口

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

    public static void main(String[] args) {
        // main线程,主线程

        ThreadRunnable r = new ThreadRunnable();

        // 调用开启多线程(两个线程同时跑)
        new Thread(r).start();

        for (int i = 0; i <= 20; i++) {
            System.out.println("主线程--"+ i);
        }
    }
}

案例:网图下载

public class ThreadDown_Runnable implements Runnable {
    /**
     * 网络图片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全参构造方法
     * @param url
     * @param name
     */
    public ThreadDown_Runnable(String url, String name) {
        this.url = url;
        this.name = name;
    }

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

下载类【与继承Thread类案例相同】

测试类

public class TestThread {
    public static void main(String[] args) {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times+".png";
      
        testRunnable(url, name);
    }

    /**
     * 测试创建多线成方法:实现Runnable接口
     */
    private static void testRunnable(String url, String name) {
        // 创建Runnable接口的实现类对象
        ThreadDown_Runnable r = new ThreadDown_Runnable(url, name);
        // 创建线程对象,通过线程对象来开启我们的线程,【代理】
        /*Thread t = new Thread(r);
        t.start();*/

        // 简洁版
        new Thread(r).start();
    }
}

案例:龟兔赛跑

  1. 首先来个赛道距离,然后要离终点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 模拟兔子睡觉
  6. 最终乌龟赢得比赛

代码:

package cn.luis.race;

/**
 * @ClassName Race
 * @Description TODO :龟兔赛跑,(兔子休息,乌龟赢得比赛)
 * @Author L
 * @Date 2020.06.15 0:13
 * @Version 1.0
 * @Remark
 **/
public class Race implements Runnable {

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {

            // 模拟兔子睡觉(名为兔子的进程和每走十步,兔子睡一下)
            if ("流氓兔".equals(Thread.currentThread().getName()) && i % 10 == 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 判断比赛是否结束
            boolean flag = gameOver(i);
            // 如果比赛结束了
            if (flag) {
                break;
            }

            System.out.println(Thread.currentThread().getName() + " -- 跑了 --" + i + " --步");
        }
    }

    /**
     * 判断是否完成比赛
     * @param step
     * @return
     */
    private boolean gameOver(int step) {

        // 判断是否有获胜者
        if (winner != null) {
            // 存在
            return true;
        }
        if (step >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println("Winner is : " + winner);
            return true;
        }
        return false;
    }

    /**
     * 主方法
     * @param args
     */
    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "小乌龟").start();
        new Thread(race, "流氓兔").start();
    }
}

结果:

...
小乌龟 -- 跑了 --97 --步
小乌龟 -- 跑了 --98 --步
小乌龟 -- 跑了 --99 --步
Winner is : 小乌龟

实现Callable接口

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

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

  3. 创建目标对象

  4. 创建执行服务

    ExecutorService service = Executors.newFixedThreadPool(3);
    
  5. 提交执行

    Future<Boolean> r1 = service.submit(c1);
    
  6. 获取结果

    boolean rs1 = r1.get();
    
  7. 关闭服务

    service.shutdownNow();
    

案例:网图下载

package cn.luis.callable;

import cn.luis.down.WebDownloader;

import java.util.concurrent.Callable;

/**
 * @ClassName ThreadDown
 * @Description TODO 实现Callable接口:实现多线程下载图片
 * @Author L
 * @Date 2020.06.14 10:39
 * @Version 1.0
 * @Remark
 **/
public class ThreadDown_Callable implements Callable<Boolean> {
    /**
     * 网络图片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全参构造方法
     * @param url
     * @param name
     */
    public ThreadDown_Callable(String url, String name) {
        this.url = url;
        this.name = name;
    }

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

测试类:

package cn.luis.callable;

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

/**
 * @ClassName TestThread
 * @Description TODO 测试类
 * @Author L
 * @Date 2020.06.14 11:45
 * @Version 1.0
 * @Remark TODO callable接口好处:1.可以定义返回值 2.可以抛出异常
 **/
public class TestThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times + ".png";
        testCallable(url, name);
    }

    /**
     * 测试创建多线程方法:实现Callable接口
     */
    private static void testCallable(String url, String name) throws ExecutionException, InterruptedException {
        // 创建Callable接口的实现类对象
        ThreadDown_Callable c1 = new ThreadDown_Callable(url, name);
        ThreadDown_Callable c2 = new ThreadDown_Callable(url, name);
        ThreadDown_Callable c3 = new ThreadDown_Callable(url, name);

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

        // 提交执行
        Future<Boolean> r1 = service.submit(c1);
        Future<Boolean> r2 = service.submit(c2);
        Future<Boolean> r3 = service.submit(c3);

        // 获取结果(可以抛出异常)
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        // 打印结果
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        // 关闭服务
        service.shutdownNow();
    }

}

4. Lambda表达式

在这里插入图片描述

  • λ希腊字母中排序第十一位的字母。

  • 避免匿名内部类定义过多,只展示核心逻辑。

  • 函数式(接口)编程思想:接口只包含唯一一个抽象方法

代码:

接口

public interface ILike {
    void like();
}

实现类

public class Like implements ILike {
    @Override
    public void like() {
        System.out.println("i like lambda 1!");
    }
}

测试类

public class testLambda {

    // 第二种:静态内部类
    static class Like2 implements ILike {
        @Override
        public void like() {
            System.out.println("i like lambda 2!");
        }
    }

    public static void main(String[] args) {
        // 第一种:接口引用指向实现类
        ILike like = new Like();
        like.like();

        // // 第二种:静态内部类的使用
        like = new Like2();
        like.like();

        // 第三种:局部内部类
        class Like3 implements ILike {
            @Override
            public void like() {
                System.out.println("i like lambda 3!");
            }
        }
        like = new Like3();
        like.like();

        // 第四种:匿名内部类,没有类的名字,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void like() {
                System.out.println("i like lambda 4!");
            }
        };
        like.like();

        // 第五种:lambda表达式
        like = () -> System.out.println("i like lambda 5!");
        like.like();
    }
}

表达式简化

  1. 参数类型,要么都写要么都不写

     (a,b)-> {...}
    
  2. 省略大括号,只有一条语句时才可以

    (a,b)-> System.out.println("aaa");
    
  3. 省略参数小括号,只有一个参数时才可以

    c -> System.out.println("aaa");
    

5. 静态代理

在这里插入图片描述

要求:

  1. 真实对象和代理对象都要实现同一接口
  2. 代理对象必须要代理真实角色(将真实角色传入)

优点:

  1. 代理对象可以做很多真实对象做不了的事情
  2. 真实对象专注做自己的事情

案例:婚庆公司和结婚

接口

public interface Marry {
    void happyMarry();
}

新郎

public class Xinlang implements Marry {
    @Override
    public void happyMarry() {
        System.out.println("小明要结婚啦!");
    }
}

婚庆公司:帮助你结婚(是个代理角色)

public class HunQing implements Marry {

    /**
     * 代理谁 --> 真实目标角色
     */
    private Marry target;

    public HunQing(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 StaticProxy {
    public static void main(String[] args) {

        new HunQing(new Xinlang()).happyMarry();
        // 线程底部原理与结婚案例相同
        new Thread(() -> System.out.println("I love You!")).start();
    }
}

6. 线程状态

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

7. 线程操作

在这里插入图片描述

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 休眠
void join() 等待该线程终止
static void yield() 暂停单签正在执行的线程对象,并执行其他线程
void interrupt() 中断线程【废弃】
boolean isAlive() 测试线程是否处于活动状态

停止线程

在这里插入图片描述

  1. 利用次数,不建议死循环
  2. 建议使用标志位
  3. 不要使用JDK不建议的方法,如:stop,destory

代码:

public class TestStop implements Runnable {

    /**
     * 1.设置标志位
     */
    private boolean flag = true;

    /**
     * 重写线程执行方法
     */
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("子线程 -- " + i++);
        }
    }

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

    /**
     * 主方法
     *
     * @param args
     */
    public static void main(String[] args) {
        // 常见Runnable接口实现类
        TestStop stop = new TestStop();

        // 模拟多线程
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程 -- " + i);
            // 满足条件时停止子线程
            if (i == 9) {
                stop.stop();
                System.out.println("线程该停止了!");
            }

            // 开启多线程
            new Thread(stop).start();
        }
    }
}

结果:

主线程 -- 0
主线程 -- 1
主线程 -- 2
主线程 -- 3
线程该停止了!
主线程 -- 4

线程休眠

在这里插入图片描述
格式

 Thread.sleep(1000);

代码:

package cn.luis.state;

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

/**
 * 线程休眠
 * 1. 模拟网络延时:买票
 * 2. 倒计时
 **/
public class TestSleep {

    public static void main(String[] args) {
       /* down1();
        down2();*/
        time();
    }

    /**
     * 打印当前系统时间
     */
    private static void time() {
        
        while (true) {
            try {
                Thread.sleep(1000);
                Date date = new Date();
                System.out.println(new SimpleDateFormat("YYYY:HH:mm:ss").format(date));

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

    /**
     * 倒计时2
     */
    private static void down2() {
        int num = 5;
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
        System.out.println("时间到!");
    }

    /**
     * 倒计时1
     */
    public static void down1() {
        for (int i = 5; i > 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
        System.out.println("时间到!");
    }
}

线程礼让

在这里插入图片描述
格式

 Thread.yield();

代码:

礼让不一定成功!

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()+"线程停止执行!");
    }
}

结果:(礼让成功)

a线程开始执行!
b线程开始执行!
a线程停止执行!
b线程停止执行!

线程强制执行

在这里插入图片描述

相当于插队

格式:(中断异常)

Thread.join();

代码:

public class TestJoin implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println("线程VIP来啦!");
        }
    }

    public static void main(String[] args) {
        TestJoin join = new TestJoin();
        // 启动子线程
        Thread t = new Thread(join);
        t.start();

        // 主线程
        for (int i = 0; i < 5; i++) {
            if (i == 2) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main" + i);
        }
    }
}

结果:

main0
main1
线程VIP来啦!
线程VIP来啦!
线程VIP来啦!
main2
main3
main4

观测线程状态

在这里插入图片描述

格式:

Thread.State state = thread.getState();

代码:

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

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

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

        // 只要线程不终止,就一直输出状态
        while(state != Thread.State.TERMINATED){
            Thread.sleep(100);
            // 更新线程状态
            state = thread.getState();
            // 输出状态
            System.out.println(state);
        }
        // 线程中断或者结束后不能再次启动
        thread.start();
    }
}

结果:

NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
...
TIMED_WAITING
线程终止啦~
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.base/java.lang.Thread.start(Thread.java:804)
	at cn.luis.state.TestState.main(TestState.java:44)

线程优先级

在这里插入图片描述

并不是一定按照优先级执行!

格式:

thread.setPriority(1);

thread.setPriority(Thread.MAX_PRIORITY);

代码:

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

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

        // 先设置优先级,再启动
        t1.start();
        t2.setPriority(1);
        t2.start();

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

        t4.setPriority(6);
        t4.start();

        t5.setPriority(8);
        t5.start();

        // 10
        t6.setPriority(Thread.MAX_PRIORITY);
        t6.start();

    }
}

class MyPriority implements Runnable {

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

结果:

main -- 5
Thread-5 -- 10
Thread-4 -- 8
Thread-0 -- 5
Thread-3 -- 6
Thread-2 -- 4
Thread-1 -- 1

8. 守护线程

在这里插入图片描述

格式:

thread.setDaemon(true);

代码;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Life life = new Life();
        Thread thread = new Thread(god);
        // 默认是false表示是用户线程,正常的线程都是用户线程
        thread.setDaemon(true);

        // 上帝守护线程启动(上帝线程会一直运行,直到life线程挂了,虚拟机不用等待守护线程执行完毕)
        thread.start();
        // 生命线程启动
        new Thread(life).start();

    }
}

/**
 * 上帝
 */
class God implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("守护线程...");
        }
    }
}

/**
 * 生命
 */
class Life implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("用户线程正在活跃...!");
        }
        System.out.println("用户线程挂啦!");
    }
}

结果:(虚拟机无需等待守护线程停止)

守护线程...
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程挂啦!

9. 线程同步

在这里插入图片描述

同一进程的多个线程共享同一块存储空间,在方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制

当一个线程获得对象的排它锁,独占资源,其他线程必须等待它用完后释放锁,有可能存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁和释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 若一个优先级高的线程等待优先级低的线程释放锁,会导致优先级倒置没因其性能问题。

同步方法

同步方法:synchonized默认锁的对象是this,也就是类本身

案例:买票

class BuyTicket implements Runnable {

    private int ticketNum = 5;
    // 标志位
    boolean flag = true; 

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

    /**
     * 同步锁方法,锁的是this
     */
    private synchronized void buy() {
        // 判断是否有票
        if (ticketNum <= 0) {
            flag = false;
            return;
        }
        // 模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 买票
        System.out.println(Thread.currentThread().getName() + "拿到-- " + ticketNum-- + " --票");
    }
}

测试类:

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

结果:

小明拿到-- 5 --票
小明拿到-- 4 --票
黄牛拿到-- 3 --票
小李拿到-- 2 --票
黄牛拿到-- 1 --

同步代码块

Obj:同步监视器,也就是代码块里要操作的对象,【可以锁任何对象,锁增删改的对象就好】

案例:银行取钱

账户类

class Account {
    /**
     * 余额
     */
    int nowMoney;

    /**
     * 构造方法
     *
     * @param nowMoney
     */
    public Account(int nowMoney) {
        this.nowMoney = nowMoney;

    }
}

银行:模拟取款

class Bank extends Thread {
    /**
     * 账户
     */
    Account account;

    /**
     * 取了多少钱
     */
    int quMoney;

    public Bank(Account account, int quMoney, String name) {
        super(name);
        this.account = account;
        this.quMoney = quMoney;

    }

    /**
     * 同步方法:synchonized默认锁的对象是this,也就是类本身
     * 同步块:  Obj:同步监视器,也就是代码块里要操作的对象,【可以锁任何对象,锁增删改的对象就好】
     */
    @Override
    public void run() {
        synchronized (account) {
            // 判断有没有钱
            if (account.nowMoney - quMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "钱不够!");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 手里的钱
            //System.out.println(this.getName()+Thread.currentThread().getName());
            System.out.println(this.getName() + "手里的钱:" + this.quMoney);
            // 卡内余额 = 余额 - 取出的钱
            account.nowMoney = account.nowMoney - quMoney;
            System.out.println("卡内余额为:" + account.nowMoney);
        }

    }
}

测试类:

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100);

        Bank you = new Bank(account, 70, "二萌");
        Bank her = new Bank(account, 40, "阿雷");

        you.start();
        her.start();

    }
}

结果:

二萌手里的钱:70
卡内余额为:30
阿雷钱不够

线程不安全的集合:ArrayList

public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        // 模拟延时
        Thread.sleep(100);
        // 主线程
        System.out.println(Thread.currentThread().getName()+"线程,集合中存储了多少个线程:"+list.size());
    }
}

结果:

main线程,集合中存储了多少个线程:10

Lock锁【JDK1.5】

在这里插入图片描述

通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当

ReentrantLock类实现了Lock接口,可重入锁,与synchonized有相同的并发性和内存语义。

格式:

private final ReentrantLock lock = new ReentrantLock();

可以显式加锁、释放锁:【必须在try—catch–finally代码块中】

try {
    // 加锁
    lock.lock();
	...
} catch (Exception e) {
    e.printStackTrace();
} finally {
    // 解锁
    lock.unlock();
}

代码:

package cn.luis.lock;

import java.util.concurrent.locks.ReentrantLock;

class BuyTicket implements Runnable {
    int ticketNum = 5;

    /**
     * 定义Lock锁
     * ReentrantLock:可重入锁
     */
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (ticketNum > 0) {
                    // 模拟延时(睡眠)
                    Thread.sleep(200);
                    // 打印线程名称
                    System.out.println(Thread.currentThread().getName() + " -- 拿到了第-- " + ticketNum-- + " --张票");
                } else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 解锁
                lock.unlock();
            }

        }
    }
}

测试类:

public class TestLock {
    public static void main(String[] args) {
        BuyTicket b = new BuyTicket();
        new Thread(b).start();
        new Thread(b).start();
        new Thread(b).start();
    }

}

结果:

Thread-0 -- 拿到了第-- 5 --张票
Thread-2 -- 拿到了第-- 4 --张票
Thread-1 -- 拿到了第-- 3 --张票
Thread-0 -- 拿到了第-- 2 --张票
Thread-0 -- 拿到了第-- 1 --张票

死锁

产生原因

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

死锁避免方法

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

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

只要破坏其中的任意一个或多个即可!

synchnoized与Lock的对比

  • Lock是显式锁(手动开启和关闭锁)
  • Lock只有代码块锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好、扩展型好(提供更多的子类)
  • synchonized是隐式锁,出了作用域自动释放
  • synchonized有代码块锁和方法锁

优先使用顺序

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

10. 线程通信 – 生产者和消费者问题

线程间通信方法

  • 均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常:lllegalMonitorStateException
方法名 作用
wait() 一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一对象上所有调用wait方法的线程,优先级高的线程优点调度

并发协作模式”生产者消费者模式“

管程法 – 利用缓冲区解决

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据

生产者将生产好的数据放入缓冲区,消费者冲缓冲区中拿出数据

代码:


信号灯法 – 标志位

生产者:演员

class Actor extends Thread {
    Movie movie;

    public Actor(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.movie.play("夏洛特烦恼");
            } else {
                this.movie.play("唐人街探案");
            }
        }
    }
}

消费者:观众

class watcher extends Thread {
    Movie movie;

    public watcher(Movie movie) {
        this.movie = movie;
    }

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

产品:电影

class Movie {
    /**
     *  拍摄电影时,观众等待
     */

    /**
     * 放映电影时,演员等待
     */

    /**
     * 表演的节目
     */
    String movie;
    /**
     * 标志位
     */
    boolean flag = true;

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

    }

    /**
     * 观看
     */
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众看了:" + movie);
        System.out.println("~~~~~~~~~~");
        // 通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

测试类:

public class TestPC2 {
    public static void main(String[] args) {
        Movie movie= new Movie();

        new Actor(movie).start();
        new watcher(movie).start();

    }
}

结果:

演员表演了:夏洛特烦恼
==========
观众看了:夏洛特烦恼
~~~~~~~~~~
演员表演了:唐人街探案
==========
观众看了:唐人街探案
~~~~~~~~~~
演员表演了:夏洛特烦恼
==========
观众看了:夏洛特烦恼
~~~~~~~~~~
演员表演了:唐人街探案
==========
观众看了:唐人街探案
~~~~~~~~~~

11. 线程池 【JDK5】

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

解决

  • 提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中。

JDK5.0起提供了线程池相关API:ExecutorServiceExcecutors

ExecutoeService方法

ExecutoeService:真正的线程池接口,常见子类ThreadExecutor方法

  • 执行Runnable,任务/命令,无返回值
void execute(Runnable command);
  • 执行Callable接口,任务/命令,有返回值
<T> Future<T> sunbmit(Callable<T> task);

Excecutors工具类

线程池的工厂类,用于创建并返回不同类型的线程池

优点

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maxmumPooSize:最大线程数
    • keeoAliveTime:线程无任务是最多保持多长时间后会终止

使用步骤:

  1. 创建服务,创建线程池(参数为线程池大小)

    ExecutorService service = Executors.newFixedThreadPool(10);
    
  2. 执行线程类,方法:

     // runable接口是execute方法
     service.execute(new MyThread());
     
     // callable接口是submit方法
     Future<Boolean> r1 = service.submit(c1);
    
  3. 关闭连接池

     service.shutdownNow();
    

代码:

线程类

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

创建线程池

package cn.luis.pool;

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

public class TestPool {

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

        // 2.执行Runable接口实现类的线程【execute方法】,callable接口是submit方法
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 3.释放连接
        service.shutdownNow();
    }
}

结果:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4

END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章