韩顺平 线程与锁 自学笔记(Java30天基础)

程序进程和线程

程序的定义

是为了完成特定任务,用某种语言编写的一组指令的集合。简单的来说就是我们写的代码。

进程的定义

进程是指运行中的程序,比如我们使用的QQ,就启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用迅雷,又启动了一个进程,操作系统将迅雷分配新的内存空间。

进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身产生、存在和消亡的过程。

线程的定义

1、线程由进程创建的,是进程的一个实体。
2、一个进程可以拥有多个线程。

单线程

1、单线程:同一个时刻,只允许执行一个线程。
2、多线程:同一个时刻,可以执行多个线程,比如;一个QQ进程可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。
3、并发:同一个时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单的说,单核CPU实现的多任务就是并发。
4、并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行。
5、并发和并行同时存在。

写一段Java程序查看CPU个数

/**
 * @author wty
 * @date 2022/10/15 23:14
 * 写一段Java代码查看CPU个数
 */
public class Fundation {
    @Test
    public void selectCPU(){
        Runtime runtime = Runtime.getRuntime();
        int cpunums = runtime.availableProcessors(); // 获得当前电脑的处理器CPU数量
        System.out.println("当前电脑CPU个数:"+cpunums);


    }
}

创建线程的两种方法

在Java中线程来使用有两种方法。
1、继承Thread类,重写run方法。
2、实现Runnable接口,重写run方法。

继承Thread类

示例:

package com.UseThread;

import org.junit.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author wty
 * @date 2022/10/15 23:20
 * <p>
 * 继承Thread类,重写run方法。 创建线程
 */
    public static void main(String[] args) {
        // 创建一个Cat对象当作线程使用
        Cat cat = new Cat();
        cat.start(); // 启动线程,这里会调用run方法
        /**
         * 追源码
         *
         * public synchronized void start() {
         *         if (this.threadStatus != 0) {
         *             throw new IllegalThreadStateException();
         *         } else {
         *             this.group.add(this);
         *             boolean var1 = false;
         *
         *             try {
         *                 this.start0(); // 真正实现多线程的方法,是一个private native void start0(); 由JVM虚拟机调用
         *                 var1 = true;
         *             } finally {
         *                 try {
         *                     if (!var1) {
         *                         this.group.threadStartFailed(this);
         *                     }
         *                 } catch (Throwable var8) {
         *                 }
         *
         *             }
         *
         *         }
         *     }
         */
        //cat.run(); // 这里不直接调用run()是因为,Thread.currentThread().getName() = main,并不是Thread0开启的线程
        // 当main线程启动一个子线程的时候,主线程不会阻塞,会继续执行
        // 这时主线程main和子线程Thread0会交替进行
        System.out.println("主线程名字:" + Thread.currentThread().getName());
        for (int i = 1; i <= 50; i++) {
            System.out.println("第" + i + "个主线程正在运行!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 继承Thread类,重写run方法。 创建线程
 * 线程每隔一秒在控制台输出我是小猫咪
 */
class Cat extends Thread {
    int times = 0;

    @Override
    public void run() {//重写run方法,写上业务逻辑
        while (true) {
            times++;
            System.out.println("喵喵我是一只小猫咪(运行了第" + times + "次)," + "线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 休眠1000ms = 1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 80) {
                break; // 运行8次退出
            }

        } // while结束
    }

    // run方法的源码,Thread类实现了Runnable接口
    /**
     *    public void run() {
     *         if (this.target != null) {
     *             this.target.run();
     *         }
     *
     *     }
     */
}

cmd 之后输入 jconsole可以查看进程执行情况

jconsole查看
线程源码分析图示
线程源码分析图示

实现Runnable接口

说明

  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然是不可能了。
  2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。

练习要求:
要求
代码示例

package com.UseThread;

import org.junit.Test;

/**
 * @author wty
 * @date 2022/10/16 11:46
 * <p>
 * 通过实现Runnable来实现线程创建
 * <p>
 * 底层实现了静态代理
 */
public class ThreadUse02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // 没有start()方法

        // 创建Thread对象,把dog对象放入
        Thread thread = new Thread(dog);
        thread.start();

    }
}

class Dog implements Runnable { // 通过实现Runnable接口
    int times = 0;

    @Override
    public void run() { // 普通方法
        while (true) {
            times++;
            System.out.println("小狗汪汪叫了" + times + "声   " + Thread.currentThread().getName());

            // 休眠1秒
            try {
                Thread.sleep(1000); // 1000ms = 1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 10) {
                break;
            }
        }
    }
}

模拟Runnable接口(静态代理)代码示例:

package com.UseThread;

/**
 * @author wty
 * @date 2022/10/16 11:56
 * <p>
 * 模拟Runnable接口
 * 静态代理
 */
public class MoniRunnable {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

/**
 * 当作Thread来看待,模拟了一个极简的Thread类
 */
class ThreadProxy implements Runnable {
    private Runnable target = null; // target属性,类型是Runnable

    @Override
    public void run() {
        if (null != target) {
            target.run();// 动态绑定
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0(); // 真正实现真线程的方法
    }

    public void start0() {
        run();
    }
}

class Animals {
}

class Tiger extends Animals implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫!");
    }
}

多线程

多线程的使用
代码示例:

package com.UseThread;

import javax.print.attribute.standard.DocumentName;

/**
 * @author wty
 * @date 2022/10/16 16:56
 */
public class ManyThread {
    public static void main(String[] args) {
        Monkey monkey = new Monkey();
        Thread thread1 = new Thread(monkey);
        thread1.start();

        Dunkey dunkey = new Dunkey();
        Thread thread2 = new Thread(dunkey);
        thread2.start();

        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行了第" + i + "次");
        }
    }
}

class Monkey implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            ++count;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("猴子叫了" + count + "声" + Thread.currentThread().getName());
            if (10 == count) {
                break;
            }
        }
    }
}

class Dunkey implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            ++count;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("驴叫了" + count + "声" + Thread.currentThread().getName());
            if (5 == count) {
                break;
            }
        }
    }
}


继承Thread 和 实现Runnable区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable来创建线程本质上没有区别,从jdk帮助文档中我们可以看到Thread类本身就实现了Runnable接口。
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。建议使用Runnable接口

售票系统

代码示例;

package com.SoldTicket;

/**
 * @author wty
 * @date 2022/10/16 17:25
 * 模拟三个窗口同时售票
 */
public class SellTicket {
    public static void main(String[] args) {
/*        Ticket ticket1 = new Ticket();
        Ticket ticket2 = new Ticket();
        Ticket ticket3 = new Ticket();
        ticket1.start();
        ticket2.start();
        ticket3.start();*/

        Tickets tickets = new Tickets();
        new Thread(tickets).start();
        new Thread(tickets).start();
        new Thread(tickets).start();
    }
}

/**
 * 继承Thread接口
 */
class Ticket extends Thread {
    public static int ticket = 100; // 定义一个静态常量 100,票数

    @Override
    public void run() {
        while (true) {
            if (ticket <= 0) {
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("此时还剩余" + --ticket + "张票" + Thread.currentThread().getName());
        }
    }
}

/**
 * 实现Runnable接口
 */
class Tickets implements Runnable {
    public int ticket = 100; // 这里定义普通成员变量即可

    @Override
    public void run() {
        while (true) {
            if (ticket <= 0) {
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("此时还剩余" + --ticket + "张票" + Thread.currentThread().getName());
        }
    }
}

存在问题,会出现超卖情况
超卖情况
那么如何解决呢,需要看后续synchronized的知识

线程终止

  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制run方法退出的方式停止线程即通知方式

线程终止

方法一:通知法

package com.ThreadExit;

/**
 * @author wty
 * @date 2022/10/16 18:17
 */
public class ThreadExit {
    public static void main(String[] args) {
        Thread_Exit thread_exit = new Thread_Exit();
        thread_exit.start();

        // 主线程休眠
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread_exit.setLoop(false);

    }
}

/**
 * 通知的方法,通过改变变量loop通知run方法终止线程
 */
class Thread_Exit extends Thread {
    // 定义一个变量来控制循环的开关
    private boolean loop = true;
    int count = 0;

    @Override
    public void run() {
        while (loop) {
            count++;
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行第" + count + "次 " + Thread.currentThread().getName());
        }
    }

    // 定义set方法

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程的常用方法

线程的常用方法
代码示例:

package com.ThreadMethod;

/**
 * @author wty
 * @date 2022/10/16 18:34
 * 线程的常用方法
 */
public class ThreadMethod {
    public static void main(String[] args) {
        ThreadMethodExercise threadMethodExercise = new ThreadMethodExercise();
        // 设置线程名称使之与参数name相同
        threadMethodExercise.setName("ThreadMethodExercise");

        // 更改线程的优先级,优先级范围:
        // Thread.MAX_PRIORITY 10,Thread.MIN_PRIORITY 1,Thread.NORM_PRIORITY 5
        /**    源码:
         *     public static final int MIN_PRIORITY = 1;
         *     public static final int NORM_PRIORITY = 5;
         *     public static final int MAX_PRIORITY = 10;
         */
        threadMethodExercise.setPriority(Thread.NORM_PRIORITY);


        // 使该线程开始执行
        // 注意细节:start底层会创建新的线程,调用run,
        threadMethodExercise.start();

        // 通知的方式终止线程
        //threadMethodExercise.setLoop(false);

        // 返回线程的名称
        System.out.println(threadMethodExercise.getName());


        // 调用线程对象的run方法,run就是一个简单的方法调用,不会刷新线程
        // threadMethodExercise.run();

        // 获取线程的优先级
        System.out.println(threadMethodExercise.getPriority());


        for (int i = 1; i <= 5 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 中断线程,不是停止线程。一般用于中断正在休眠的线程
        // 当执行到这里时,会中断threadMethodExercise的休眠
        threadMethodExercise.interrupt();
    }
}

class ThreadMethodExercise extends Thread {
    private boolean loop = true;
    int count = 0;

    @Override
    public void run() {
        while (loop) {
            count++;
            try {
                // 在指定的毫秒内让当前线程休眠(暂停)
                // sleep:线程的静态方法,使当前线程休眠
                /**  源码:
                 *     public static native void sleep(long var0) throws InterruptedException;
                 */
                System.out.println(Thread.currentThread().getName() + "休眠中");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被Interrupt中断了");
            }
            System.out.println(Thread.currentThread().getName() + " 线程执行了第" + count + "次");
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程常用方法

代码示例:

package com.ThreadMethod;

/**
 * @author wty
 * @date 2022/10/16 19:08
 * 线程的常用方法02
 */
public class ThreadMethod02 {
    public static void main(String[] args) {
        ThreadMethodExercise02 threadMethodExercise02 = new ThreadMethodExercise02();
        Thread thread = new Thread(threadMethodExercise02);
        thread.start();

        for (int i = 1; i <= 20; i++) {
            try {
                // System.out.println(Thread.currentThread().getName() + "休眠中");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程 " + Thread.currentThread().getName() + "执行了第" + i + "次");
            if (i == 5) {
                System.out.println("----main线程礼让Thread-0线程----");
                try {
                    /**
                     * join():线程的插队
                     * 线程的插队一旦成功,则先执行完插入的线程所有的任务
                     *
                     */
                    thread.join();

                    /**
                     * Thread.yield();
                     * 线程的礼让:让出CPU,让其他线程执行,但是礼让的时间不确定。所以也不一定礼让成功
                     *
                     */
                    Thread.yield();

                    System.out.println("----Thread-0线程全部运行完毕,main接着运行----");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /**
                 * Thread.yield();
                 * 线程的礼让:让出CPU,让其他线程执行,但是礼让的时间不确定。所以也不一定礼让成功
                 *
                 */
                Thread.yield();

                System.out.println("----Thread-0线程全部运行完毕,main接着运行----");
            }
        }

    }
}

class ThreadMethodExercise02 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            count++;
            try {
                //System.out.println(Thread.currentThread().getName() + "休眠中");
                Thread.sleep(1000);


            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被Interrupt中断了");
            }
            System.out.println(Thread.currentThread().getName() + " 线程执行了第" + count + "次");
            if (20 == count) {
                break;
            }
        }
    }
}


课后练习

课后练习
代码示例;

package com.ThreadMethod;

/**
 * @author wty
 * @date 2022/10/16 19:45
 */
public class ThreadMethodHomeWork {
    public static void main(String[] args) {
        SonThread sonThread = new SonThread();
        Thread thread = new Thread(sonThread);
        //thread.start();

        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hi " + i);

            if (i == 5) {
                try {
                    thread.start(); // 启动子线程
                    thread.join(); // 启动后立即插队
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("主线程结束");

    }
}

class SonThread implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello " + i);
        }
        System.out.println("子线程结束");
    }
}

用户线程和守护线程

  1. 用户线程:可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如我们使用 Thread 创建的线程在默认情况下都属于用户线程,当线程的任务执行完毕或者通知方式结束。
  2. 守护线程:是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT 线程都是守护线程。守护线程一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
  3. 常间的守护线程:垃圾回收机制

代码示例:把一个线程设置成守护线程
把一个线程设置成守护线程
正常情况:t是m的子线程,假如m结束了,t该怎么执行怎么执行,不会结束。现在把t作为守护线程,当m结束后,t要跟着一起结束。

代码示例:

package com.WorkThread;

/**
 * @author wty
 * @date 2022/10/16 20:04
 * <p>
 * 守护线程
 */
public class WorkThread {
    public static void main(String[] args) {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        // 主线程
        // 现在需要主线程结束后,子线程跟着一起结束
        // 那么需要把子线程设置成守护线程
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("main主线程在运行~");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 守护线程
 */
class MyDaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyDaemonThread正在执行~");
        }
    }
}

线程的生命周期

JDK中用Thread.State枚举表示了线程的几种状态

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

线程转换图

线程转换图
线程转换的代码示例:

package com.ProgrammerState;

/**
 * @author wty
 * @date 2022/10/16 23:09
 */
public class ThreadState {
    public static void main(String[] args) {
        T t = new T();
        System.out.println(t.getName() + "状态:" + t.getState());
        t.start();
        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + "状态:" + t.getState());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(t.getName() + "状态:" + t.getState());
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 1; i <= 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

线程的同步

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这么理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。

###同步的具体方法–synchronized
1.同步代码块

synchronized(对象){ // 得到对象的锁,才能操作同步代码
// 需要被同步的代码
}

2.synchronized还可以放在方法声明中,表示整个方法-为同步方法

public synchronized void m (String name){
// 需要被同步的代码
}

3.就好像,某小伙伴上厕所前先把门关上,完事儿后再出来,那么其它小伙伴就可以继续使用厕所了。

这里再把上面买票问题进行梳理,用synchronized来解决,代码示例:

package com.SoldTicket;

/**
 * @author wty
 * @date 2022/10/16 17:25
 * 模拟三个窗口同时售票
 */
public class SellTicket {
    public static void main(String[] args) {
//        Ticket ticket1 = new Ticket();
//        Ticket ticket2 = new Ticket();
//        Ticket ticket3 = new Ticket();
//        ticket1.start();
//        ticket2.start();
//        ticket3.start();

        Tickets tickets = new Tickets();
        new Thread(tickets).start();
        new Thread(tickets).start();
        new Thread(tickets).start();
    }
}

/**
 * 继承Thread接口
 */
class Ticket extends Thread {
    public static int ticket = 100; // 定义一个静态常量 100,票数
    public boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }

    public synchronized void sell() {
        if (ticket <= 0) {
            loop = false;
            return;
        }


        System.out.println("此时还剩余" + --ticket + "张票" + Thread.currentThread().getName());

    }
}

/**
 * 实现Runnable接口
 */
class Tickets implements Runnable {
    public int ticket = 1000; // 这里定义普通成员变量即可
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }

    public synchronized void sell() {
        if (ticket <= 0) {
            loop = false;
            return;
        }


        System.out.println("此时还剩余" + --ticket + "张票" + Thread.currentThread().getName());
    }
}

分析同步原理

分析同步原理

互斥锁

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
  2. 每个对象都在对应于一个可称为"互斥锁"的标记,这个标记用来保证在任意时刻,能有一个线程访问该对象。
  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率降低
  5. 同步方法(非静态)的锁可以是this,也可以是 其它对象(要求是同一个对象)
  6. 同步方法(静态的)的锁为当前类本身。

代码示例:

package com.SynchronizedUse;

/**
 * @author wty
 * @date 2022/10/17 0:48
 */
public class SynchronizedUse {
    public static void main(String[] args) {
        A a = new A();
        a.start();
    }
}

class A extends Thread{
    private static int count = 0;
    Object object = new Object();
    @Override
    public void run() {

        //代码块同步
        synchronized (this){

        }

        // 同类的其它对象
        synchronized (object){

        }
    }

    public static void getExample(){
        //静态代码块同步
        synchronized (A.class){

        }
    }
}

互斥锁的注意事项

  1. 同步方法如果没有使用static修饰,默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
    (1)需要先分析上锁的代码
    (2)选择同步代码块或同步方法
    (3)要求多个线程的锁对象为同一个即可

线程的死锁

基本介绍

每个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。

代码示例:

package com.DeadLock;

/**
 * @author wty
 * @date 2022/10/17 15:36
 * <p>
 * 模拟死锁过程
 */
public class DeadLock {
    public static void main(String[] args) {
        DeadLockExercise deadLockExercise1 = new DeadLockExercise(true);
        deadLockExercise1.setName("A线程");
        DeadLockExercise deadLockExercise2 = new DeadLockExercise(false);
        deadLockExercise2.setName("B线程");

        deadLockExercise1.start();
        deadLockExercise2.start();
    }
}

class DeadLockExercise extends Thread {
    static Object object1 = new Object();
    static Object object2 = new Object();
    private boolean loop;

    @Override
    public void run() {
        /**
         * 业务逻辑分析
         * 1.如果flag为T,线程A就会先得到/持有object1对象锁,然后尝试获取object2对象锁
         * 2.如果线程A得不到object2对象锁,就会Blocked
         * 3.如果flag为F,线程B就会先得到/持有object2对象锁
         */
        if (loop) {
            synchronized (object1) {
                System.out.println(Thread.currentThread().getName() + " 1对象出口1");
                synchronized (object2) {
                    System.out.println(Thread.currentThread().getName() + " 2对象出口1");
                }
            }

        } else {
            synchronized (object2) {
                System.out.println(Thread.currentThread().getName() + " 2对象出口2");
                synchronized (object1) {
                    System.out.println(Thread.currentThread().getName() + " 1对象出口2");
                }
            }
        }

    }

    public DeadLockExercise(boolean loop) {
        this.loop = loop;
    }
}

释放锁

下面操作会释放锁

  1. 当前线程的同步方法、同步代码块执行结束
    案例:上厕所,完事出来
  2. 当前线程在同步代码块、同步方法中遇到break、return。
    案例:没有正常的完事,经理叫他修改bug,不得已出来
  3. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,并释放锁。
    案例:没有正常完事,觉得需要酝酿下,所以出来等会儿再进去。

下面操作不会释放锁

  1. 线程执行代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
    案例:上册错,太困了,在坑上眯了一会儿
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
    提示:应该尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用。

课后作业

课后作业01

课后作业01
方法一:守护线程

package com.RealHomeWork;

import org.junit.Test;

import java.util.Scanner;

/**
 * @author wty
 * @date 2022/10/17 17:03
 */
public class RealHomeWork01 {
    public static void main(String[] args) {
        Thread01 thread01 = new Thread01();
        thread01.setName("线程A");
        thread01.setDaemon(true);
        thread01.start();

        Thread02 thread02 = new Thread02();
        Thread thread = new Thread(thread02);
        thread.setName("线程B");
        thread.start();

    }
}

/**
 * 随机打印100以内的整数
 */
class Thread01 extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            randomNum();
        }
    }

    @Test
    public void randomNum() {
        System.out.println((int) (Math.random() * 100 + 1) + "  " + Thread.currentThread().getName());
    }
}

/**
 * 键盘输入Q
 */
class Thread02 implements Runnable {
    private boolean loop = true;

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            ScannerText();
        }
    }

    public void ScannerText() {
        System.out.println(Thread.currentThread().getName() + "请在键盘输入一个数:");
        Scanner scanner = new Scanner(System.in);
        char c = scanner.next().charAt(0); // 获取输入的第一个字符
        if ('\0' !=c && 'Q' == c) { // c不为空,且c是Q
            loop = false;
            return;
        }
    }
}

方法二:通过调用对象的形式

package com.RealHomeWork;

import java.util.Scanner;

/**
 * @author wty
 * @date 2022/10/17 17:51
 */
public class RealHomeWork02 {
    public static void main(String[] args) {
        A a = new A();
        a.setName("线程A");
        a.start();

        B b = new B(a);
        Thread thread = new Thread(b);
        thread.setName("线程B");
        thread.start();
    }
}

class A extends Thread {
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  " + (int) (Math.random() * 100 + 1));
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

class B implements Runnable {
    private A a;
    private Scanner scanner = new Scanner(System.in);
    private boolean flag = true;

    public B(A a) {
        this.a = a;
    }

    @Override
    public void run() {
        while (flag) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "请输入任意字符(输入Q退出)");
            char c = scanner.next().charAt(0);
            if (c != '\0' && 'Q' == c) {
                flag = false;
                a.setLoop(false);
                return;
            }
        }

    }
}

课后作业02

课后作业02

代码示例:方法一用实现Runnable接口实现

package com.RealHomeWork;

/**
 * @author wty
 * @date 2022/10/17 19:13
 */
public class RealHomeWork03 {
    public static void main(String[] args) {
        Card card = new Card();
        Thread thread1 = new Thread(card);
        thread1.setName("线程A");
        Thread thread2 = new Thread(card);
        thread2.setName("线程B");

        thread1.start();
        thread2.start();
    }
}

class Card implements Runnable {
    public boolean loop = true;
    public static int money = 10000;

    @Override
    public void run() {

        while (loop) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (this){
                money = getMoney(money);
            }
        }

    }

    public int getMoney(int money) {

        if (money <= 1000) {
            loop = false;
            System.out.println("卡里的钱已经取完!");
            return money;
        }
        money = (money - 1000);
        System.out.println(Thread.currentThread().getName() + "取了1000块,还剩下" + money + "元");
        return money;
    }
}

代码示例:用继承Thread类实现

package com.RealHomeWork;

/**
 * @author wty
 * @date 2022/10/17 22:55
 */
public class RealHomeWork04 {
    public static void main(String[] args) {
        BorrowMoney borrowMoney1 = new BorrowMoney();
        borrowMoney1.setName("线程A");
        BorrowMoney borrowMoney2 = new BorrowMoney();
        borrowMoney1.setName("线程B");

        borrowMoney1.start();
        borrowMoney2.start();
    }
}

class BorrowMoney extends Thread {
    public boolean loop = true;
    public static int money = 10000;
    public static Object object = new Object();

    @Override
    public void run() {
        while (loop) {
            synchronized (object){
                if (money <= 1000) {
                    loop = false;
                    System.out.println("钱已经全部取出!");
                    return;
                }
                money = money - 1000;
                System.out.println(Thread.currentThread().getName() + "取走了1000块,还剩" + money + "块");
            }

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心向阳光的天域

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值