Java多线程学习

本文详细讲解了Java中线程的基本概念、创建方式(继承Thread和实现Runnable接口)、线程状态与控制(stop、sleep、yield、join、状态监测和优先级)、同步机制(synchronized和死锁)、线程池应用以及生产者消费者问题的解决方案。
摘要由CSDN通过智能技术生成

java 多线程

1. 什么是线程

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

2. 线程创建

2.1继承Thread类

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用star()方法启动线程
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class testThread01 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("这是线程——"+i);
        }
    }

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

        //创建一个线程对象
        testThread01 testThread01 = new testThread01();
        //调用start()方法开启线程
        testThread01.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("这是主线程——"+i);
        }
    }
}

结果如下:

这是主线程——0
这是线程——0
这是线程——1
这是主线程——1
这是主线程——2
这是线程——2
这是线程——3
这是主线程——3

这就证明了线程是一起并发执行的,而如果用了run()方法,就会先执行线程,再执行主线程

线程开启后不一定执行,是由CPU调度执行

案例:下载图片

首先提前下载一个IO jar包 commons-io-2.8.0.jar 百度搜索即可,这里面有需要用的方法

下载完复制到项目里,右键添加为库,这样即可用它了

在这里插入图片描述

然后编写代码:

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

//联系thread,实现多线程同步下载图片
public class testThread02 extends Thread{
    private String url;//图片网络地址
    private String name;//图片名字

    public testThread02(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) {
        testThread02 t1 = new testThread02("https://th.bing.com/th/id/R75c994349b4a23f7f1bd54325f6695f2?rik=j5mH2rdDFFnxRg&riu=http%3a%2f%2fwww.kexueyou.com%2fupload%2fimage%2f201802%2f23%2f0223541746.jpg&ehk=pAmeTEOMBD1o6WJu0k5CX2McoNe6lNibQoJ8Y1O9ppo%3d&risl=&pid=ImgRaw","1.jpg");
        testThread02 t2 = new testThread02("https://th.bing.com/th/id/R296892fd56a2996dc86cc7496c3e6769?rik=WRTqBzMqVl6oWw&riu=http%3a%2f%2fwww.long-photo.com%2fuploads%2f2019%2f1126%2f9e3d023191bc319d719cded5b73f6c86.jpg&ehk=Gw6wbLcNc%2fZZ8h%2fwFTqShn91egofNmKPQzQ8bOP85jI%3d&risl=&pid=ImgRaw","2.jpg");
        testThread02 t3 = new testThread02("https://th.bing.com/th/id/R93b57656f51231e0675afbcf321bea8e?rik=R12H0aCZUQzTrw&riu=http%3a%2f%2fpic223.nipic.com%2ffile%2f20190619%2f9773031_200628982080_2.jpg&ehk=HLWsezOUvCtRlze%2bY%2fnUecT0ERkgHJOOycdC7EcbRjQ%3d&risl=&pid=ImgRaw","3.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方法出现问题");
        }
    }
}

这里整理思路:

  1. 多线程下载图片,第一步创建一个下载器用于下载,这里就用到了之前下载的jar包,把图片copy下来,这里需要两个值,一个是url,一个是name,所以方法需要两个参数,分别是String url和String name,然后用try-catch把URL包围即可,(当下载地址出错或者别的问题就会被捕捉,并输出异常)。
  2. 接着让类继承thread,然后重写run()方法,run方法既是线程的执行体,这里暂时不管run,因为下载器的数据还没有传进来,设置两个私有属性url和name,重写类,用this.把数据传进来。
  3. 现在编写run()方法,new一个下载器,调用downloader方法,输出下载的文件名。
  4. 写main函数,new 多个testThread02类,传入要下载的图片地址和名字,最后用.start启动线程,输出结果。

输出结果为:

下载了文件名为:2.jpg
下载了文件名为:3.jpg
下载了文件名为:1.jpg

这里再次体现了多线程的CPU调度性质。

2.2 实现Runnable借口

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
public class testThread03 implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("这是线程——"+i);
        }
    }
    public static void main(String[] args) {
        testThread03 Thread03 = new testThread03();
        //创建线程对象,通过线程对象开启线程,这就叫代理
        new Thread(Thread03).start();
        for (int i = 0; i < 20; i++) {
            System.out.println("这是主线程——"+i);
        }
    }
}

实现结果与实验一相同,这里和继承Thread做个比较:

  • 继承Thread类

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

    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
案例:抢票

生活中抢票就是一个典型的多个线程但只有一个资源的情况,这里做出模拟,并查看情况

public class testThread04 implements Runnable{
    private int ticket = 10;
    @Override
    public void run() {
        while (true){
            if(ticket<=1){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticket--+"张票");
        }
    }
    public static void main(String[] args) {
        testThread04 ticket = new testThread04();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"小红").start();
        new Thread(ticket,"黄牛贩子").start();
    }
}

输出结果为:

黄牛贩子–>拿到了第9张票
小明–>拿到了第10张票
小红–>拿到了第10张票
黄牛贩子–>拿到了第8张票
小明–>拿到了第7张票
小红–>拿到了第6张票
黄牛贩子–>拿到了第5张票
小明–>拿到了第4张票
小红–>拿到了第3张票
黄牛贩子–>拿到了第2张票
小明–>拿到了第1张票
小红–>拿到了第0张票

这里明显发现问题,跟预料中的不同,同一张票有被多人拿走的情况,这就是多线程同资源的问题,也是并发的问题。

案例:龟兔赛跑

假设龟兔分别是两个线程,一起跑步,一个中涂停了一会,另一个一直跑

public class testThread05 implements Runnable{
    //设置一个唯一的胜利者
    private static String winner;
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //判断比赛是否结束
            boolean flag = gameOver(i);
            //有胜利者时即不再运行
            if (flag){
                break;
            }
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i==70){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private 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) {
        testThread05 race = new testThread05();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

实现结果为:

乌龟跑了1步
兔子跑了1步
乌龟跑了2步
兔子跑了2步
乌龟跑了3步
乌龟跑了4步
乌龟跑了5步
兔子跑了3步
乌龟跑了6步
兔子跑了4步
乌龟跑了7步
兔子跑了5步
兔子跑了6步

乌龟跑了97步
乌龟跑了98步
乌龟跑了99步
winner is 乌龟
兔子跑了70步

2.3实现callable接口(了解)

这里拿之前下载图片的案例做修改:

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://th.bing.com/th/id/R75c994349b4a23f7f1bd54325f6695f2?rik=j5mH2rdDFFnxRg&riu=http%3a%2f%2fwww.kexueyou.com%2fupload%2fimage%2f201802%2f23%2f0223541746.jpg&ehk=pAmeTEOMBD1o6WJu0k5CX2McoNe6lNibQoJ8Y1O9ppo%3d&risl=&pid=ImgRaw","1.jpg");
        testCallable t2 = new testCallable("https://th.bing.com/th/id/R296892fd56a2996dc86cc7496c3e6769?rik=WRTqBzMqVl6oWw&riu=http%3a%2f%2fwww.long-photo.com%2fuploads%2f2019%2f1126%2f9e3d023191bc319d719cded5b73f6c86.jpg&ehk=Gw6wbLcNc%2fZZ8h%2fwFTqShn91egofNmKPQzQ8bOP85jI%3d&risl=&pid=ImgRaw","2.jpg");
        testCallable t3 = new testCallable("https://th.bing.com/th/id/R93b57656f51231e0675afbcf321bea8e?rik=R12H0aCZUQzTrw&riu=http%3a%2f%2fpic223.nipic.com%2ffile%2f20190619%2f9773031_200628982080_2.jpg&ehk=HLWsezOUvCtRlze%2bY%2fnUecT0ERkgHJOOycdC7EcbRjQ%3d&risl=&pid=ImgRaw","3.jpg");
        //创建执行服务:
        ExecutorService service = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = service.submit(t1);
        Future<Boolean> r2 = service.submit(t2);
        Future<Boolean> r3 = service.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);
        //关闭服务
        service.shutdown();
    }
}
//下载器
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.jpg
下载了文件名为:1.jpg
下载了文件名为:3.jpg
true
true
true

和runnable比起来,好处是有返回值和抛出异常。

2.4静态代理

这里举一个结婚找婚庆公司的例子,

public class staticProxy {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("准备结婚")).start();
        new weddingMarry(new You()).happyMarry();
    }
}
interface Marry{
    void happyMarry();
}
class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("结婚啊真开心");
    }
}
class weddingMarry implements Marry{
    private Marry target;

    public weddingMarry(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("结婚之前,各种忙碌");
    }
}

输出结果为:

准备结婚
结婚之前,各种忙碌
结婚啊真开心
结婚之后,各种收钱

小结:

  1. 真是对象和代理对象都要实现同一个接口
  2. 代理对象可以做很多真是对象做不了的事情
  3. 让真实对象专注于自己的事情

2.5 Lambda表达式

Lambda表达式就是为了简化代码,突出逻辑,这里先理解一个概念:Functional Interface 函数式接口

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

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

这里展示简化过程:

1. 一个love外部类
public class testLambda {
    public static void main(String[] args) {
        ILove love = new love();
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}
class love implements ILove{
    @Override
    public void love(int a) {
        System.out.println("I love-->"+a);
    }
}
2.改为静态内部类
public class testLambda {
    static class love implements ILove{
        @Override
        public void love(int a) {
            System.out.println("I love-->"+a);
        }
    }
    public static void main(String[] args) {
        ILove love = new love();
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}
3.改为局部内部类
public class testLambda {
    public static void main(String[] args) {
        class love implements ILove{
            @Override
            public void love(int a) {
                System.out.println("I love-->"+a);
            }
        }
        ILove love = new love();
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}
4.改为匿名内部类
public class testLambda {
    public static void main(String[] args) {
        ILove love = new ILove() {
            @Override
            public void love(int a) {
                System.out.println("I love-->"+a);
            }
        };
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}
5.改为lambda表达式
public class testLambda {
    public static void main(String[] args) {
        ILove love = (int a) -> {
                System.out.println("I love-->"+a);
        };
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}
6.继续简化lambda表达式
public class testLambda {
    public static void main(String[] args) {
        ILove love = a -> System.out.println("I love-->"+a);
        love.love(2);
    }
}
interface ILove{
    void love(int a);
}

小结:

  • lambda表达式只能有一行代码的情况下才能简化成一行,如果有多行,就要代码块包裹
  • 前提是接口为函数式接口(接口只有一个方法)
  • 多个参数也可以去掉参数类型,都去掉,用括号包裹

3. 线程状态

3.1 线程的五大状态

线程有五个状态,分别是新生状态,就绪状态,运行状态,阻塞状态和死亡状态

在这里插入图片描述

3.1.1 线程方法

在这里插入图片描述

3.2 线程停止

/*
测试stop
1.建议线程自己停止,利用次数,不建议死循环
2.建议使用标志位
3.不建议使用stop或者的的destroy等JDK不建议的方法
 (所以自己写一个stop方法)
 */
public class testStop implements Runnable{
    //设置标志位
    private Boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("run...Thread"+i++);
        }
    }
    //设置一个公开的方法停止线程,切换标志位
    public void stop(){
        this.flag = false;
    }
    public static void main(String[] args) {
        testStop testStop = new testStop();
        new Thread(testStop).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main+"+i);
            if (i==90){
                testStop.stop();
                System.out.println("Thread is stop");
            }
        }
    }
}

结果:

main+89
main+90
run…Thread156
Thread is stop
main+91
main+92
main+93
main+94
main+95
main+96
main+97
main+98
main+99

3.3 线程休眠

就是用sleep来达到休眠的效果,1000毫秒等于1秒。

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

一般用休眠来干两件事,一是模拟网络延时(为了放大问题),二是模拟倒计时

十秒倒计时:

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

打印时间:

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

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

3.5 线程礼让

礼让就是让两个线程退到起跑线,再来一次,礼让不一定成功,看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()+"线程停止执行");
    }
}

输出1:礼让成功

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

输出2:礼让失败

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

3.6 join

可以理解为插队,无论执行多少,有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 InterruptedException {
        Thread thread = new Thread(new TestJoin());
        thread.start();
        for (int i = 0; i < 10; i++) {
            if (i==4){
                thread.join();
            }
            System.out.println("main+"+i);
        }
    }
}

线程VIP进入–>98
线程VIP进入–>99
main+4
main+5
main+6
main+7
main+8
main+9

3.7 监测线程状态

打开JDK帮助文档,就可以找到线程的这几个状态

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

  • NEW
    尚未启动的线程处于此状态。
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

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

我们来设计一个方法来监测一下:

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("此时是"+i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("、、、、、、、、、、、、、");
            }
        });
        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //启动线程,观察状态
        thread.start();
        state = thread.getState();//更新状态
        System.out.println(state);
        //只要线程不终止就一直输出状态
        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();//更新状态
            System.out.println(state);
        }
    }
}

输出为:

NEW
RUNNABLE
此时是0
TIMED_WAITING
、、、、、、、、、、、、、
此时是1
TIMED_WAITING
、、、、、、、、、、、、、
此时是2
TIMED_WAITING
、、、、、、、、、、、、、
此时是3
TIMED_WAITING
、、、、、、、、、、、、、
此时是4
TIMED_WAITING
、、、、、、、、、、、、、
TERMINATED

3.8 线程优先级

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,"t1");
        Thread t2 = new Thread(myPriority,"t2");
        Thread t3 = new Thread(myPriority,"t3");
        Thread t4 = new Thread(myPriority,"t4");
        //先设置优先级再启动
        t1.setPriority(Thread.MIN_PRIORITY);//MIN_PRIORITY是最小值为1
        t1.start();
        t2.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY是最大值为10
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(Thread.NORM_PRIORITY);//NORM_PRIORITY是中间值为5
        t4.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
    }
}

输出1:正常结果,按优先级排列

main—>5
t2—>10
t4—>5
t3—>4
t1—>1

输出2:性能倒置

main—>5
t1—>1
t2—>10
t3—>4
t4—>5

3.9 守护线程

public class TestDaemon {
    public static void main(String[] args) {
        MyDaemon daemon = new MyDaemon();
        Usr usr = new Usr();
        //守护线程设置和启动
        Thread thread = new Thread(daemon);
        thread.setDaemon(true);
        thread.start();
        new Thread(usr).start();
    }
}
//守护线程
class MyDaemon implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("正在被守护");
        }
    }
}
//用户线程
class Usr implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("用户执行--"+i);
        }
    }
}

用户执行–8
用户执行–9
正在被守护
正在被守护

当用户线程结束,虚拟机会自动关闭守护线程。

4. 线程同步

线程同步就类似于排队,当很多线程要用同一资源时候就会出现问题,这时候就需要线程同步来解决排队的问题,除了排队,还需要解决锁的问题,因为如果一个线程抢占了资源但没有上锁,那么其他线程就可能去抢占资源,所以还需要设置锁。

在这里插入图片描述

4.1 不安全的例子

举两个不安全的例子,一个是抢票,一个是取钱

//测试不安全买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        //三个人抢票
        new Thread(station,"Vip").start();
        new Thread(station,"倒霉蛋").start();
        new Thread(station,"黄牛").start();
    }
}
//买票途径
class BuyTicket implements Runnable{
    //设定票数和标志位
    private int tickets = 10;
    boolean flag = true;
    //线程运行
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }
    //买票的方法
    private void buy(){
        //监测是否还有票
        if (tickets<=0){
            flag = false;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //否则抢票
        System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets--+"张票");
    }
}

倒霉蛋抢到了第10张票
Vip抢到了第9张票
黄牛抢到了第8张票
Vip抢到了第7张票
倒霉蛋抢到了第6张票
黄牛抢到了第5张票
Vip抢到了第4张票
倒霉蛋抢到了第3张票
黄牛抢到了第2张票
Vip抢到了第1张票
倒霉蛋抢到了第0张票
黄牛抢到了第-1张票

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account("小金库",100);
        Drawing You = new Drawing(account,50,0,"你");
        Drawing YouWife = new Drawing(account,100,0,"你的妻子");
        You.start();
        YouWife.start();
    }
}
//账户
class Account {
    //卡名和余额
    String name;
    int money;
    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}
//银行取钱
class Drawing extends Thread{
    //账户
    Account account;
    //取走多少钱
    int drawingMoney;
    //拿到手多少钱
    int nowMoney;
    public Drawing (Account account,int drawingMoney,int nowMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
        this.nowMoney = nowMoney;
    }
    //开始取钱
    @Override
    public void run() {
        //判断是否有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"的取不钱了");
            return;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money=account.money-drawingMoney;
        System.out.println(account.name+"的账户余额为:"+account.money);
        nowMoney=nowMoney+drawingMoney;
        System.out.println(this.getName()+"手里拥有:"+nowMoney);
    }
}

小金库的账户余额为:50
小金库的账户余额为:-50
你手里拥有:50
你的妻子手里拥有:100

4.2 同步方法

其实很简单,只需要加上synchronized关键字即可完成锁的任务,比如第一个取票的例子,

    private synchronized void buy(){
        //监测是否还有票
        if (tickets<=0){
            flag = false;
            return;
        }

Vip抢到了第10张票
Vip抢到了第9张票
Vip抢到了第8张票
Vip抢到了第7张票
Vip抢到了第6张票
黄牛抢到了第5张票
黄牛抢到了第4张票
倒霉蛋抢到了第3张票
倒霉蛋抢到了第2张票
倒霉蛋抢到了第1张票

这样就实现了线程的安全,但要注意,这里的synchronized指的是this,也就是BuyTicket给这个对象上锁,因为我们增删改查的对象是这个,当对象不唯一时,synchronized就得用另一种代码块的格式来完成上锁,比如银行取钱的例子:

    //开始取钱
    @Override
    public void run() {
        synchronized (account) {
            //判断是否有钱
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "的取不钱了");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - drawingMoney;
            System.out.println(account.name + "的账户余额为:" + account.money);
            nowMoney = nowMoney + drawingMoney;
            System.out.println(this.getName() + "手里拥有:" + nowMoney);
        }
    }

小金库的账户余额为:0
你的妻子手里拥有:100
你的取不钱了

这时就不会出现问题,我们增加account里的钱到1000,再次运行,结果为:

小金库的账户余额为:900
你的妻子手里拥有:100
小金库的账户余额为:850
你手里拥有:50

这里synchronized括号里的东西就是会变化的东西,如果不用代码块,锁的就是银行,但银行本来就没有变化,变化的是取钱的人

5. 死锁

多个线程互相抱着对方需要的资源,形成僵持,就成了死锁。

public class TestLock {
    public static void main(String[] args) {
        Eat tom = new Eat(0, "tom");
        Eat ela = new Eat(1, "ela");
        tom.start();
        ela.start();
    }
}
class Bread{}
class Milk{}
class Eat extends Thread{
    //用static表示唯一性
    static Bread bread = new Bread();
    static Milk milk = new Milk();
    int choose;
    String name;
    public Eat (int choose,String name){
        this.choose = choose;
        this.name = name;
    }
    @Override
    public void run() {
        eat();
    }
    public void eat(){
        if (choose==0){
            synchronized (bread){
                System.out.println(this.name+" get bread!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (milk){
                    System.out.println(this.name+" get milk!!!");
                }
            }
        }else {
            synchronized (milk){
                System.out.println(this.name+" get milk!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (bread){
                    System.out.println(this.name+" get bread!!!");
                }
            }
        }
    }
}

tom get bread!!!
ela get milk!!!

进程已结束,退出代码为 130 (interrupted by signal 2: SIGINT)

代码运行只能手动退出,因为双方都想要占有对方的锁,于是陷入了僵持。

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

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

5.1 LOCK锁

lock锁是显示的锁,要手动的开锁关锁,会更省运行内存,但不能用于方法

import java.util.concurrent.locks.ReentrantLock;

public class TestLock2 {
    public static void main(String[] args) {
        TestLock02 lock02 = new TestLock02();
        new Thread(lock02).start();
        new Thread(lock02).start();
        new Thread(lock02).start();
    }
}
class TestLock02 extends Thread{
    int ticketNum = 10;
    //设置锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                if (ticketNum>0){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                }else {
                    break;
                }
            } finally {
                lock.unlock();
            }

        }
    }
}

10
9
8
7
6
5
4
3
2
1

6. 线程协作,生产者消费者问题

6.1 管程法

public class TestPc {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Producer(container).start();
        new Customer(container).start();
    }
}
//需要生产者,消费者,产品,缓冲区
//生产者
class Producer extends Thread{
    SynContainer container;
    public Producer (SynContainer container) {
        this.container = container;
    }
    //生产产品
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了第 "+i+"只小鸡");
        }
    }
}
//消费者
class Customer extends Thread{
    SynContainer container;
    public Customer (SynContainer container) {
        this.container = container;
    }
    //消费产品
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            System.out.println("消费者吃了第 "+container.eat().ID+"只小鸡");
        }
    }
}
//产品鸡
class Chicken {
    int ID ;//产品编号
    public Chicken(int ID) {
        this.ID = ID;
    }
}
//缓冲区
class SynContainer {
    //需要一个容器大小,十只鸡
    Chicken[] chickens = new Chicken[9];
    //容器计数器
    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 eat() {
        //查看是否可以消费,没有则等待
        if (count == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //可以消费则消费
        count--;
        Chicken chicken = chickens[count];
        //消费完了则通知生产者
        this.notifyAll();
        return chicken;
    }
}

生产了第 1只小鸡
生产了第 2只小鸡
生产了第 3只小鸡
生产了第 4只小鸡
生产了第 5只小鸡
生产了第 6只小鸡
生产了第 7只小鸡
生产了第 8只小鸡
生产了第 9只小鸡
生产了第 10只小鸡
消费者吃了第 9只小鸡
生产了第 11只小鸡
消费者吃了第 10只小鸡
消费者吃了第 11只小鸡
消费者吃了第 12只小鸡
消费者吃了第 8只小鸡
消费者吃了第 7只小鸡
消费者吃了第 6只小鸡
消费者吃了第 5只小鸡
消费者吃了第 4只小鸡
消费者吃了第 3只小鸡
消费者吃了第 2只小鸡
生产了第 12只小鸡
生产了第 13只小鸡
生产了第 14只小鸡
生产了第 15只小鸡
生产了第 16只小鸡
消费者吃了第 1只小鸡
生产了第 17只小鸡
生产了第 18只小鸡
生产了第 19只小鸡
生产了第 20只小鸡
消费者吃了第 20只小鸡
消费者吃了第 19只小鸡
消费者吃了第 18只小鸡
消费者吃了第 17只小鸡
消费者吃了第 16只小鸡
消费者吃了第 15只小鸡
消费者吃了第 14只小鸡
消费者吃了第 13只小鸡

可以看出是交替进行,并有互相等待的过程

6.2 信号灯法

public class TestPc2 {
    public static void main(String[] args) {
        Light light = new Light();
        new Police(light).start();
        new Car(light).start();
    }
}
class Police extends Thread{
    Light light;
    public Police(Light light){
        this.light = light;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i%2==0){
                this.light.control("红灯");
            }else {
                this.light.control("绿灯");
            }
        }
    }
}
class Car extends Thread{
    Light light;
    public Car(Light light){
        this.light = light;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            this.light.follow();
        }
    }
}
class Light {
    String sign;
    boolean flag;
    public synchronized void control(String sign){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("现在的灯是:"+sign);
        this.notifyAll();
        this.sign = sign;
        this.flag = !this.flag;
    }
    public synchronized void follow(){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("驾驶员看到了:"+sign);
        this.notifyAll();
        this.flag = !this.flag;
    }
}

现在的灯是:红灯
驾驶员看到了:红灯
现在的灯是:绿灯
驾驶员看到了:绿灯
现在的灯是:红灯
驾驶员看到了:红灯
现在的灯是:绿灯
驾驶员看到了:绿灯
现在的灯是:红灯
驾驶员看到了:红灯

6.3 线程池

        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new Thread());
        service.shutdown();

创建线程池,设定大小

执行线程

结束服务

可以参考Callable接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值