进程与多线程(入门)

什么是线程

要了解什么是线程,得先知道什么是程序
程序:为完成特定任务,用某种语言编写的一组指令的集合。
例如,QQ,Steam,亦或者java写的helloword。
这些都是程序

了解了程序,还得清楚什么是进程
进程:就是指运行中的程序,比如双击QQ,那么就会启动了一个进程,操作系统就会为其分配该进程所需的内存空间,当又点开了一个程序,比如谷歌浏览器,那么就会又启动一个进程。
进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身产生,运行中,消亡的过程。双击启动QQ就是启动了一个进程,打开任务管理器,选中结束任务,就是进程消亡了

了解了进程,再来了解下线程
线程:线程是由进程创建的,是进程的一个实体。一个进程可以有多个线程
例如:迅雷就是一个程序,打开迅雷,就是启动一个进程,而你用迅雷下载一个资源,比如下载<流浪地球>这部电影,那么这个下载任务就是一个线程,你还可以同时下载多个电影,创建多个下载任务同时下载,这就是多线程。

单线程和多线程的概念

单线程:同一时刻,只运行执行一个线程。例如有些下载器,只运行同时执行一个下载任务,必须排队下载,(Steam的下载)
多线程:同一时刻,可以执行多个线程,比如微信可以同时打开多个聊天窗口,迅雷可以同时下载多个资源

并发与并行的概念

并发:同一时刻,多个任务交替执行,但是因为切换的速度很快,造成一种是同时执行的错觉。
例如单核CPU实现的多任务就是并发。并发就是一个人操作2个键盘,但是因为切换的速度很快,看起来像是同时操作的

并行:同一时刻,多个任务同时执行,多核CPU可以实现并行
例如:两个人操作两个键盘,他们是同时打字的

但是在电脑中有时候是并发和并行同时存在的,例如两个人在同时打字,但是其中一个人打到一半去操作另一个键盘了,敲了一会又回来了

在这里插入图片描述

线程的基本使用

在Java中创建线程有两个办法
1.实现Runnable接口,重写run方法
2.继承Thread类,重写run方法

继承Thread类创建线程

注意Thread类其实也是实现了Runnable接口,Thread重写的run方法也是Runnable接口的

在这里插入图片描述

案例演示使用线程

有以下题目,使用线程完成
有一个Cat类,要求让Cat的对象,每过一秒打印输出一个“喵喵”

思路:定义Cat类,继承Thread类,重写run方法,将逻辑写在run方法里。Thread有一个sleep方法可以睡眠
然后使用Thread类继承过来的start方法,会自动触发run方法

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        while (true) {
            System.out.println("喵喵");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

追加要求:当输出8次喵喵后,停止运行
思路,创建一个int变量,每输出一次就+1,当等于8时就break退出

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 8){
                break;
            }
        }
    }
}

注意,main方法本身也是一个线程,相当于主线程,上面就是在主线程里又开了一个线程,但是并不会造成主线程的堵塞,比如cat.start();下面还有代码的话,不会等cat.start();执行完再回来执行,而是启动了Thread线程后并发或者并行的执行。

举例说明:

public class test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 8){
                break;
            }
        }
    }
}

在这里插入图片描述
可以看到是交替执行的,单核的CPU就是并发,多核的就是并行

使用JConsole监控线程执行情况,且画出示意图

在执行的时候,打开Terminal输入JConsole,可以打开监控面板,查看线程执行情况。

注意点:当main主线程执行完毕后,这个进程并不会立刻退出,而是等所有线程都执行完毕后,才会退出
就像一个仓库管理员让他手下去关东边的灯,他自己关西边的灯,得等两个人都关了后,才离开。而不是一个人关掉后马上离开
在这里插入图片描述

在这里插入图片描述

start0方法

为什么在上面不在main方法直接调用run方法来完成呢?

这是因为run方法只是一个普通的方法,并不是创建了一个线程。如果直接在main方法中调用run方法的话,可以在JConsole监控中只能看到main线程,没有Thread线程。
而在main线程中就会造创堵塞,调用run方法下面的语句,必须得等run方法执行完毕后,才会执行。这就是没有达到多线程的效果,只是串行,没有并发或者并行。

只有使用start方法才能真正的去创建线程。
start方法底层是调用start0方法(本地方法),由start0方法调用run方法。而start0方法是JVM调动的。
当start0方法调用run方法,并不是马上执行的,而是取决于CPU。CPU会根据内存资源IO资源等等,判断让不让run方法现在执行

实现Runnable接口创建线程

上面说了继承Thread类创建线程,但是由于Java只能单继承的特性,在实际的开发中可能没办法再去继承Thread类。这时旧需要实现Runnable接口来创建线程。

但是因为如果是继承Runnable接口的话,由于Runnable接口中只有一个run方法可以重写,没有start方法来触发run方法,无法创建线程。
为了解决这个问题就需要将创建线程的对象放入倒Thread的实例中,然后直接使用Thread对象调用start方法。因为多态绑定的特性,底层还是跑的实现Runnable接口的类实例。
代码演示:
例如每隔1秒输出一个“你好”

public class test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("你好"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10 ){
                break;
            }

        }
    }
}

下面简单了解一下,Thread是怎么将dog的run方法调用起来的
当dog传入Thread时,Thread的底层使用了线程代理模式

线程代理模式,简单模拟

class ThreadProxy implements Runnable{
    private  Runnable target;

    public ThreadProxy(Runnable target) {
        this.target = target;//将传入的对象赋给target
    }

    @Override
    public void run() {
        if (target!=null){//如果传入的对象不为空
            target.run();//调用传入对象的run方法
        }
    }
    public void start(){
        start0();//调用本类的start方法
    }
    public void start0(){
        run();//调用本类的run方法
    }
}

ThreadProxy类可以当成Thread类理解,当然这里只是简单演示而已。
可以看到ThreadProxy里有一个Runnable类型的变量target
构造器会把传入的对象给到target
然后ThreadProxy的run方法会判断target是不是空,如果不是就调用target的run方法,也就是传入对象的run方法

而在main方法创建ThreadProxy实例将要创建线程的类传入时,调用start,而start又会调用start0,start0就会调用ThreadProxy的run方法,最终调用传入对象的run方法

多个子线程案例

要求从main线程创建两个子线程
第一个子线程:每秒输出“你好”,输出10次退出
第二个子线程:每秒输出“hello”,输出20次退出

public class test {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("你好"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10 ){
                break;
            }

        }
    }
}
class T2 implements Runnable{
    @Override
    public void run() {
        int nums = 0;
        while (true){
            System.out.println("hello"+(++nums)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 20 ){
                break;
            }

        }
    }
}

继承Thread和实现Runnable的区别

  1. 从java的设计上来看,继承Thread或者实现Runnable接口来创建线程本质上没有区别
  2. 但是由于java只能单继承的特性,实现Runnable接口方式更加适合多个线程共享一个资源的情况,避免了单继承的限制。

线程控制

多线程售票问题

有这么一道题目,要求三个线程同时售卖票,当票<=0时就停止售卖,分别用继承Thread和实现Runnable接口两种方式实现

继承Thread

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        ticket t2 = new ticket();
        ticket t3 = new ticket();
        t1.start();
        t2.start();
        t3.start();

    }
}
class ticket extends Thread{
    public static int tickets = 100;//初始剩余票数
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
        }
    }
}

在这里插入图片描述
运行结果可以看到超卖了2张。

实现Runnable接口

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}
class ticket implements Runnable{
    public  int tickets = 100;//初始剩余票数
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
        }
    }
}

在这里插入图片描述

可以看到上面两种方法也超卖了2张,这是因为3个子线程同时进入while循环,当还剩余1张票的时候,3个子线程都能通过if语句,但是当第一个子线程再售出1张票之后,第2个和第3个子线程由于已经通过了if语句,所以也会进行售票,从而导致超卖2张。

下面随着继续深入学习来解决超卖问题

线程终止

假设有一个子线程一直在不停的跑,我们需要在main线程让这个子线程停下来。

从上面的案例可以分析,假设这个子线程是通过whlie循环在不停的跑,而while能不停的跑是因为放进去的boolean值一直是true。
所以如果想让子线程停下来,就得把true改成false。如果要改子线程的布尔值,就得提供一个方法让main线程去操作。

代码实现

public class test4 {
    public static void main(String[] args) throws InterruptedException {
        RR rr = new RR();
        rr.start();
        Thread.sleep(1000);
        rr.setLoop(false);
    }
}
class RR extends Thread{
    boolean loop = true;
    int nums = 0;
    @Override
    public void run() {
        while(loop){
            System.out.println("滴滴"+(++nums));
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

控制线程的常用方法

控制线程的常用方法一般都是Thread的方法

setName:设置线程的名称,使之与参数name相同
getName:返回该线程的名称
start:让线程开始执行,Java虚拟机底层调用该线程的start0方法
run:调用线程对象的run方法
setPriority:设置线程的优先级
getPriority:获取线程的优先级
sleep:在指定毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt:中断线程(一般用来中断休眠,唤醒线程继续执行)

常用方法注意点

  1. start方法的底层会创建新的线程,调用run,但是run方法只是一个普通的方法,不会启动新线程
  2. 线程优先级的范围:MAX_Priority 10 MIN_Priority 1 NORM_Priority 5
  3. sleep方法是线程的静态方法,让当前线程休眠

使用演示:

public class test {
    public static void main(String[] args) throws Exception{
        Cat cat = new Cat();
        cat.start();
        for (int i = 10; i > 0; i--) {
            Thread.sleep(1000);
            System.out.println("唤醒倒计时"+i);
        }
        cat.interrupt();//当输出10次i后,发出interrupted中断信号
    }
}
class Cat extends Thread{
    @Override
    public void run() {
        int nums = 0;
        Thread.currentThread().setName("CatThread");//设置线程名字
        Thread.currentThread().setPriority(MIN_PRIORITY);//设置线程优先级
        while (true) {
            System.out.println("喵喵"+(++nums));
            try {
                sleep(50000);
            } catch (InterruptedException e) {
                System.out.println("中断了睡眠");//当接收到interrupted要求中断时,就catch这个语句,然后重新进入循环
                System.out.println(Thread.currentThread().getName());//输出线程的名字
                System.out.println(Thread.currentThread().getPriority());//输出线程优先级
            }
            if (nums == 50){
                break;
            }
        }
    }
}

线程插队

除了上面的方法还有两个方法比较重要

yield:线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务,再回来执行被插队的线程任务

案例演示:

在main主线程执行数包子1~50,同时创建子线程数包子1 ~ 70.

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        steamed s = new steamed();
        s.start();
        int num = 0;
        while (true){
            if (num == 10){
                s.join();//当主线程数到10个包子的时候,就让子线程插队先执行掉
            }
            System.out.println("主线程数包子"+(++num));
            if (num == 50){
                break;
            }
        }
    }
}
class steamed extends Thread{
    int nums = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("子线程数包子"+(++nums));
            if (nums == 70){
                break;
            }
        }
    }
}

线程插队练习
要求:
主线程每隔1秒输出 hi ,输出10次
当输出到hi 5时,启动一个子线程(实现Runnable),每隔1秒输出hello,输出10次hello后退出。
子线程推出后,主线程再继续执行输出

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        steamed s = new steamed();
        Thread thread = new Thread(s);
        int num = 0;
        while (true){
            if (num == 5) {
                thread.start();
                thread.join();
            }
            if (num == 10){
                break;
            }
            System.out.println("hi"+(++num));
            Thread.sleep(1000);
        }
    }
}
class steamed implements Runnable{
    int nums = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("hello"+(++nums));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (nums == 10){
                System.out.println("子线程结束");
                break;
            }
        }
    }
}

用户线程和守护线程

用户线程:也叫工作线程,结束方式是当线程的任务执行完或者通知结束
守护线程:一般是为用户线程服务的,结束方式是当所有的用户线程结束后,守护线程就会自动结束

常见的守护线程:垃圾回收机制

例如在main方法每秒输出一个hi,共输出10次。然后创建一个子线程每秒输出一个hello,共输出50次
按照正常的情况,肯定是main线程先结束,子线程线程继续运行。但是只要将子线程设置成一个守护线程,那么只要main线程结束,子线程就会结束

设置守护线程。Thread.setDaemon(true);

案例演示

public class test5 {
    public static void main(String[] args) throws InterruptedException {
        guard guard = new guard();
        guard.setDaemon(true);
        guard.start();
        for (int i = 1; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println("hi"+(i));
        }
    }
}
class guard extends Thread{
    int num = 0;
    @Override
    public void run() {
        while (true){
            if (num == 50){
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello"+(++num));
        }
    }
}

线程的生命周期

一个线程在创建-启动-消亡,会有好几种状态
JDK中用Thread.State枚举,表示了线程的几种状态

NEW:尚未启动的线程
RUNNABLE:在Java虚拟机中执行的线程,此状态还细分为两种Ready,准备执行但还没执行,Running正在执行
BLOCKED:被阻塞等待监视器锁定的线程
WATING:正在等待另一个线程执行特定动作的线程
TIMED_WATING:等待另一个线程执行动作达到指定时间的线程
TERMINATED:已退出的线程

线程的状态会因为各种方法对线程的操作,改变线程的状态

图解
在这里插入图片描述

代码演示查看线程状态

Thread.getState(),可以获取线程状态

public class test5 {
    public static void main(String[] args) throws InterruptedException {
        guard guard = new guard();
        System.out.println("状态="+guard.getState());
        guard.start();
        while (guard.getState() != Thread.State.TERMINATED){
            Thread.sleep(1000);
            System.out.println("状态="+guard.getState());
        }
    }
}
class guard extends Thread{
    int num = 0;
    @Override
    public void run() {
        while (true){
            if (num == 10){
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello"+(++num));
        }
    }
}

状态=NEW
状态=TIMED_WAITING
hello1
状态=TIMED_WAITING
hello2
状态=TIMED_WAITING
hello3
状态=RUNNABLE
hello4
hello5
状态=RUNNABLE
状态=RUNNABLE
hello6
hello7
状态=RUNNABLE
状态=TIMED_WAITING
hello8
hello9
状态=RUNNABLE
状态=RUNNABLE
hello10
状态=TERMINATED

线程同步

线程同步机制:
在多线程开发中,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多只能有一个线程访问,以保证数据的完整性

简单的说:线程同步就是一个线程在对一个内存进行操作时,其他线程都无法对这个内存地址进行操作,直到该线程完成操作,其他线程才可以对该线程操作。

就像只有一个马桶的卫生间,有很多人都要上厕所,但是必须一个个排队,进去一个就锁门。别人进不去

synchronized

synchronized关键字就相当于这个锁,synchronized可以修饰代码块,也可以放在方法声明中:

synchronized(对象){
//需同步代码
}

public synchronized 返回类型 方法名 (){
//需同步代码
}

使用synchronized修饰的方法,在同一时间只运行一个线程进入,将其他线程堵塞在后面,一个线程操作完出来了,另一个才能进去。因此这个特性可以用来解决上面的多线程售票超卖的问题

synchronized解决多线程售票超卖问题

继承Thread类

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        ticket t2 = new ticket();
        ticket t3 = new ticket();
        t1.start();
        t2.start();
        t3.start();

    }
}
class ticket extends Thread{
    public static int tickets = 100;//初始剩余票数
    public synchronized static void seep(){
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
    }
    @Override
    public void run() {
        while (tickets >0) {
            seep();
        }
    }
}

需要将synchronized修饰的方法写成静态的不然就是同时打开3个seep方法在售票,依旧会超卖。因为继承Thread类创建了3个对象
如果是实现Runnable接口就只需要创建一个对象就能创建3个线程,就不用写成静态的

实现Runnable接口的

public class test3 {
    public static void main(String[] args) {
        ticket t1 = new ticket();
        new Thread(t1).start();
        new Thread(t1).start();
        new Thread(t1).start();

    }
}
class ticket implements Runnable{
    public static int tickets = 100;//初始剩余票数
    public synchronized  void seep(){
            if (tickets <= 0) {//当剩余票少于等于0时就停止售票
                System.out.println("停止售票");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
    }
    @Override
    public void run() {
        while (tickets >0) {
            seep();
        }
    }
}

互斥锁

上面用synchronized解决了多线程售票超卖的问题。通俗的说就是在不能多线程一起操作的地方给他加一把锁,只有拿到这个锁,才能进去操作,当一个线程拿到锁进去操作,剩下的线程就没有锁可以拿了,自然就无法进去。

在这里插入图片描述

如果是一个循环,那么一个线程拿到锁之后,进去操作完,就会返回来继续和其他线程抢锁

基本介绍

  1. Java语言中,引入了互斥锁的概念,用于保证共享数据操作的完整性
  2. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任何同一时刻,只能有一个线程访问该对象。
  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任何时刻只能有一个线程访问
  4. 同步的局限性:会导致程序的效率降低
  5. 同步方法(非静态)的锁可以是this,也可以是其他对象,但是必须是同一个对象,不然就相当于有很多把锁,那就会有很多线程一起进入
  6. 同步方法(静态的)锁,为当前类本身,用类名.class表示,因为静态比对象创建更前面,无法找到对象加锁

注意点:这个锁是加在对象或者类身上的,相当于一个管理员带着这把锁。只有找到管理员拿到他身上的锁,才能进去。当锁被一个线程拿走了,其他线程就没锁了。

如果是继承Thread类创建的线程,就得将同步方法变成静态的,因为在启动的时候是创建一个对象启动一次,如果不变成静态的,就相当于有多个管理员多把锁。相当于只有一个马桶但是有很多门。当然静态同步方法就得将锁放在类本身上

如果是实现Runnable接口创建的线程,同步方法不用变成静态,因为启动的时候只创建一个对象,所以同步方法是多个线程共享的,相当于只有一个管理员一个锁,锁可以放在对象身上。

细节:
同步方法如果没有使用static修饰,默认锁对象为this
如果方法使用static修饰,默认锁对象是当前类本身,类.class

注意上面说的锁是一种非公平锁,不会比较谁等待的时间久下一个就是谁拿到锁,也有可能一个线程拿到锁执行完回来后,又是它拿到锁

线程死锁

上面了解到了互斥锁,下面了解一下一种风险,死锁。

死锁:多个线程同时占用了对象的锁资源,但不肯想让,导致一直堵塞BLOCKED,形成死锁。

通俗的说就是
A线程手里有一把锁,但是想要继续执行,需要B线程手里的锁。
B线程手里也有一把锁,但是想要继续执行,需要A线程手里的锁。

代码演示:

public class test6 {
    public static void main(String[] args) {
        deadlock A = new deadlock(true);
        deadlock B = new deadlock(false);
        A.start();
        B.start();
    }
}
class deadlock extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean loop;

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

    @Override
    public void run() {
        if (loop){
            synchronized (o1){
                System.out.println("A线程拿到o1,进入1");
                synchronized (o2){
                    System.out.println("A线程拿到o2,进入2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println("B线程拿到o2,进入3");
                synchronized (o1){
                    System.out.println("B线程拿到o1,进入4");
                }
            }
        }
    }
}

在这里插入图片描述

从上面代码可以分析,
当传入的是true时会先拿到o1锁,再去抢o2锁,才能执行完毕
反之当传入的是false时会先拿到o2锁,再去抢o1锁,才能执行完毕

只要两个线程执行run方法的时间是同时的就会出现死锁,o1锁被A拿着,B执行不了下面的,o2锁被B拿着,A执行不了下面的。就会一直卡着堵塞BLOCKED,形成死锁。在开发者要避免此种情况

释放锁

当一个线程抢到锁进入同步代码块操作时,此时锁是被该线程拿到的,别人拿不到,正常情况下,只有当该线程将语句执行完,才会释放锁,但是也有有其他情况会导致提前释放锁

以下情况会导致释放锁

  • 线程将同步代码块执行完毕
  • 当前线程在同步代码块中遇到break或者return
  • 在执行同步代码块时遇到未处理的Error或者Exception,导致异常结束
  • 在执行同步代码块时执行了线程对象的wait()方法,当前线程暂停,且释放锁

以下情况不会导致提前释放锁

  • 同步代码块调用Thread.sleep()和Thread.yield()方法,不会释放锁
  • 同步代码块时,其他线程调用了该线程的suspend()方法,将线程挂起

多线程练习

一:按照要求编程
1)在main线程中启动两个线程
2)第一个子线程循环随机打印100以内的整数
3)直到第二个子线程从键盘读取了"Q"命令,第一个子线程就停止

思路: random方法随机生成整数,Scanner键盘按Q就让第一个子线程停止

public class test7 {
    public static void main(String[] args) {
        A a = new A();
        new Thread(a).start();
        B b = new B();
        new Thread(b).start();
    }
}
class A implements Runnable{
    static boolean loop = true;
    public void setLoop(Boolean loop){
        this.loop = loop;
    }
    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Random().nextInt(100));//随机打印0~100的整数
        }
    }
}

class B  implements Runnable  {
    @Override
    public void run() {
        while (new A().loop){
            System.out.println("请输入字符:");
            char i = new Scanner(System.in).next().toUpperCase().charAt(0);
            if (i == 'Q'){
                new A().setLoop(false);
                break;
            }
        }
    }
}

二:按照要求编程
1)有2个用户分别从同一个卡上取钱(总余额10000)
2)每次都取1000,当余额不足时,就不能取款了
3)不能出现超取现象

public class test8 {
    public static void main(String[] args) {
        ATM atm = new ATM();
        new Thread(atm).start();
        new Thread(atm).start();
        new Thread(atm).start();
    }
}
class ATM implements Runnable{
    static int wallet = 10000;
    public void withdrawMoney() throws InterruptedException {
        synchronized (this){
            if (wallet <= 0){
                System.out.println("余额不足");
                return;
            }
            Thread.sleep(500);
            wallet-=1000;
            System.out.println("线程"+Thread.currentThread().getName()+"取了1000,余额剩余"+wallet);
        }
    }
    @Override
    public void run() {
        while (wallet > 0){
            try {
                withdrawMoney();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值