您的位置 首页 java

「真香Java面试题」JAVA 进程线程详解

线程和进程

一、进程

  1. 进程是指运行中的程序,比如我们使用 QQ ,就启动该进程分配内存空间.
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自升的产生,存在和消亡的过程

二、线程

  1. 线程是由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程一个想线程还可以创建它的子线程

三、其他概念

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

四、线程的基本使用

java 中线程来使用有两种方法

  1. 基础 Thread 类,重写run方法
  2. 实现Runnable接口,重写run方法

案例一

![image-20220327105831422](!

)

 package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * 通过继承Thread 类创建线程
 */

public class Thread01 {
    public  static  void main(String[] args) throws InterruptedException {
        //创建Cat对象,可以当做线程使用
        Cat cat = new Cat();
        //启动线程,调用start的时候还调用run方法
        cat.start();//最终执行 -> cat的run方法
        //cat.run();
        //如果这样写,run方法就是一个普通的方法,
        // 是由主线程调用了,并没有真正的开线程,
        // 会阻塞在这里,执行完毕后才会继续执行下面的代码


        //说明:当main线程启动一个子线程thread-0,主线程不会阻塞,
        // 即不会等待cat.start执行完毕后再往下执行
        //主线程中如果后面还有代码的话,还会继续执行
        //这时我们的主线程和主线程 是交替执行的
        System.out.println("继续执行~~,"+Thread.currentThread().getName());
        for (int i = 0; i < 60; i++) {
            System.out.println("主 线程  i=" + i);
            //休眠1秒
            Thread. sleep(1000);
        }
    }
}

//1.当一个类继承了 thread  类,该类就可以当做一个线程使用
//2.我们会重写run方法,写上自己的业务逻辑
//3.run方法在Thread也是实现了Runnable 接口的run方法
class Cat  extends  Thread {
    int times = 0;

    @Override
    public void run() {//重写run方法,写上自己的业务逻辑
        while (true) {
            //每隔一秒钟,输出"喵喵,我是小猫咪"
            System.out.println("喵喵,我是小猫咪" + (++times)
                    + ",线程名称=" + Thread.currentThread().getName());
            //让线程休眠1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 80) {
                break;//当times 等于80就退出这个 while 循环,这时线程就退出了
            }
        }
    }
}  
  1. 当运行这个程序代码的时候,会创建一个进程并给他分配空间
  2. 会先执行main方法,开启一个main线程
  3. 当执行到 car.start() 方法时会开启一个新的线程,这个线程是mian线程的子线程注意:这个 car.start() 是非阻塞的,即不会等到这个方法执行完后才继续往下执行 car.start() 线程开启后,会去调用run方法!!但是如果直接写car.run(),这个时候则是用的主线程main线程去调用的,并没有开启新线程,执行玩这个方法后才会往下继续执行

执行过程

 (1)
public  synchronized  void start() {
    start0();
}
(2)
//start0() 是本地方法,是 JVM 调用,底层是c/c++实现的
//真正实现多线程的是start0(),而不是run方法
 private native void start0();  

案例二

实现runnable接口

说明:

 Runnalb
  

 package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * 通过实现接口Runnable 来开发线程
 */
public class Thread02 {
    public static void main(String[] args){
        Dog dog = new Dog();
        //dog.start(); 这里不能调用start
        //创建了Thread对象,把 dog对象,实现了Runnable,放入thread
        Thread thread = new Thread(dog);
        thread.start();

        /*Tiger tiger = new Tiger();//实现了runnable接口
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
        System.out.println("哈哈哈哈");*/
        System.out.println("哈哈哈");
    }
}

class Animal{}

class Tiger extends Animal implements Runnable{

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

//模拟了最简的Thread类
class ThreadProxy implements Runnable{//可以当做ThreadProxy ,线程代理

    private Runnable target = null;//属性,类型是Runnable

    @Override
    public void run() {
        if(target != null){
            target.run();//进行动态绑定 运行类型是tiger,即实现了Runnable的类
        }
    }

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

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

    private void start0() {
        run();
    }

}

class Dog implements Runnable{

    int count = 0;
    @Override
    public void run() {//普通方法,并没有启动线程
        while(true){
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
            //休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

    }
}  
  • 把实现了runnable接口的对象传入到thread类,进行静态代理,调用start方法时,会去调用start0方法,start0方法还会调用对应该类的run方法,run方法又会去调用传入的对象的run方法,进行动态绑定

案例三

 package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * main 线程启动两个子线程
 */
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);//创建t1线程,用于输出hello,world
        Thread thread2 = new Thread(t2);//创建t2线程,用于输出hi

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

    }
}

class T1 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello,world " + (++count) + " "+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

class T2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi " + (++count) + " " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                break;
            }
        }
    }
}  
  • 若还有线程没有执行完毕,那么主进程就不会退出

五、继承Thread vs 实现Runnable有什么区别

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

/**
 * @author DL5O
 * @version 1.0
 * 使用多线程,模拟三个窗口同时售票
 * 总票数 100 张
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一种方式
        //测试
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        System.out.println("===使用接口的方式来售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一个线程-窗口
        thread2.start();//第二个线程-窗口
        thread3.start();//第三个线程-窗口
    }
}


//使用第一种继承thread的方式
class SellTicket01 extends Thread{
    private static int ticketNum = 100;//让多个线程共享 ticketNum
    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}

//实现接口的方式
class SellTicket02 implements Runnable{
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}  
  • 会出现超卖,和票数对不上的情况,这时候程序就会存在很大的问题=> 引出互斥等概念

六、线程的退出

基本说明:

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

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadExitTest_ {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.start();

        Thread.sleep(10*1000);
        test.setLoop(false);
    }
}
class Test extends Thread{
    private boolean loop = true;
    private int cnt = 0;
    @Override
    public void run() {
        while(loop){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Test 执行中" + (++cnt));
        }
        System.out.println("Test 运行结束");
    }

    public boolean isLoop() {
        return loop;
    }

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

    public int getCnt() {
        return cnt;
    }

    public void setCnt(int cnt) {
        this.cnt = cnt;
    }
}  

七、线程常用方法

常用方法第一组

  1. setName:给线程设置名称
  2. getName:返回线程的名称
  3. start:使线程开始执行
  4. run:线程调用的run方法
  5. setPriority:设置线程的优先级
  6. getPriority:获取线程的优先级
  7. sleep:让正在执行的线程休眠几秒
  8. interrupt:中断线程

  1. start底层会创建新的线程,会调用run,run本身不会启动新线程,不会启动新线程
  2. 线程优先级的范围
  3. interrupt ,中断线程,但并没有真正结束线程。所以一般用于中断正在休眠线程
  4. sleep :线程的静态方法,使当前线程休眠

案例:

ThreadMethod01

 package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("大龙");
        t.setPriority(Thread.MIN_PRIORITY);//设置最小的优先级
        t.start();//启动子线程

        //主线程打印5 个hi, 然后我就中断 子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + (i+1));
        }

        System.out.println(t.getName() + " 线程的优先级 = " + t.getPriority());//1
        t.interrupt();//中断该线程,当执行到这里,就会中断,t线程的休眠

    }
}

class T extends Thread {//自定义的线程类

    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + (i + 1));

            }
            try {
                System.out.println("大龙休息中~~");
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                //InterruptedException 是捕获到一个终端异常
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}  

常用方法第二组

注意:

  • 在某一个线程后使用了类名.join方法,那么会cpu会全部去执行 这个插队的线程,并且当这个线程全部执行完后,才会继续执行原来的线程
 package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        Test test = new Test();
        test.start();
        while (true){
            System.out.println("hello " + (++count));
            Thread.sleep(1000);
            if(count == 5){
                System.out.println("让test 线程先执行");
//                test.join();//线程插队一定会成功
                Thread.yield();//线程礼让

                //让test 线程先执行,执行后可以看到,当执行join后,主线程就不再执行,
                // 当这个test线程执行完毕后,主线程才继续执行
            }
            if(count == 20){
                break;
            }
        }


    }
}

class Test extends Thread{
    private int count = 0;
    @Override
    public void run() {
        while(true){
            System.out.println("hi " + (++count));
            try {
                Thread.sleep( 1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 20){
                break;
            }
        }

    }
}  

课堂练习

 package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        T01 t01 = new T01();
        for (int i = 1; i <= 10; i++) {
            System.out.println("hi " + i);
            if (i == 5) {
                Thread thread = new Thread(t01);
                thread.start();
                thread.join();
                System.out.println("子线程结束...");
            }
            Thread.sleep(1000);
        }
        System.out.println("主线程结束...");
    }
}

class T01 implements Runnable{

    @Override
    public void run() {

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

八、用户线程和守护线程

  1. 用户线程: 也叫 工作线程 ,当线程的任务执行完或通知方式结束
  2. 守护线程: 一般是为工作线程服务的, 当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程: 垃圾回收机制
 package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 * 守护线程的设置
 */
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        Test01 test01 = new Test01();
        myDaemonThread.setDaemon(true);
        //如果我们希望当main线程结束后,子线程可以自动退出
        //只需将子线程设为守护线程即可
        myDaemonThread.start();
        test01.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("我是主线程 " + i);
            Thread.sleep(1000);
        }

    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; ; i++ ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是MyDaemonThread 守护线程 " + i);
        }
    }
}

class Test01 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 15 ; i++ ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是Test01 用户线程 " + i);
        }
    }
}  

总结:

 对象名.setDaemon(true)
  

九、 线程的生命周期

线程状态转换图:

九、线程同步机制

关键字:Synchronized

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

同步具体方法-Synchronized

  1. 同步代码块 synchronized (对象){ //获得对象的锁,才能操作同步代码 //需要被同步代码; }
  2. synchronized还可以放在方法声明中,表示整个方法-为同步方法
  3. public synchronized void m (String name){ //需要被同步的代码 }
  4. 如何理解: 就好像某个人要上厕所,上厕所前需要把门关上(上锁),完事之后再出来(解锁),那么其他小伙伴就可以在使用厕所了
  5. 使用 synchronized 解决售票
 package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 使用多线程,模拟三个窗口同时售票
 * 总票数 100 张
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一种方式
        //测试
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        /*System.out.println("===使用接口的方式来售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一个线程-窗口
        thread2.start();//第二个线程-窗口
        thread3.start();//第三个线程-窗口*/

        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();//第一个线程-窗口
        thread2.start();//第二个线程-窗口
        thread3.start();//第三个线程-窗口
    }
}


//实现接口的方式,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;

    public synchronized void sell() {
        //同步方法,在同一个时刻只能有一个线程来执行我们的run方法
        if (ticketNum <= 0) {
            loop = false;
            System.out.println("票已售空,售票结束..");
            return;
        }

        //休眠50毫秒
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口 " + Thread.currentThread().getName()
                + " 售出一张票," + "剩余票数=" + (--ticketNum));
    }


    @Override
    public void run() {

        while (loop) {
            sell();
        }
    }
}


//使用第一种继承thread的方式
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}

//实现接口的方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}  

十、互斥锁

基本介绍:

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

注意事项:

  1. 同步方法如果没有使用static修饰: 默认锁对象是: this
  2. 如果方法使用了static修饰: 默认是锁对象是: 当前类.class
  3. 实现的落地步骤:需要先分析上锁的代码选择 同步代码块 或同步方法 要求多个线程的锁对象为同一个即可!!!! 即共享的资源上线程同步只会发生在共享同一个资源时
 package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 使用多线程,模拟三个窗口同时售票
 * 总票数 100 张
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一种方式
        //测试
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        /*System.out.println("===使用接口的方式来售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一个线程-窗口
        thread2.start();//第二个线程-窗口
        thread3.start();//第三个线程-窗口*/

        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();//第一个线程-窗口
        thread2.start();//第二个线程-窗口
        thread3.start();//第三个线程-窗口
    }
}


//实现接口的方式,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;
    Object obj =new Object();//都是同一个对象

    //同步方法(静态的) 的锁为当前类
    //1.他的锁是加载我们这个类上的 SellTicket03.Class 上
    //2.如果要在静态方法中实现一个同步代码块.
    //      synchronized (SellTicket03.class)
    public synchronized static void m1(){

    }
    public static void m2(){
        synchronized (SellTicket03.class){
            System.out.println("m2");
        }
    }

    //是在方法上加的锁
    //说明
    //1.public synchronized void sell()就是一个同步方法
    //2.这时锁是在this对象
    //3.也可以在代码块上写 synchronized,同步代码块,互斥锁还是在this对象
    public /*synchronized*/ void sell() {//同步方法,在同一个时刻只能有一个线程来执行我们的run方法
        synchronized (/*this*/obj){
            if (ticketNum <= 0) {
                loop = false;
                System.out.println("票已售空,售票结束..");
                return;
            }

            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票," + "剩余票数=" + (--ticketNum));
        }
    }


    @Override
    public void run() {

        while (loop) {
            sell();
        }
    }
}


//使用第一种继承thread的方式
//new SellTicket01().start()
//new SellTicket01().start() this只对当前的对象有效
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum

    //锁一般用于共享的只有
    public void m1(){
        synchronized (this){
            System.out.println("hello");
        }
    }

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}

//实现接口的方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName()
                    + " 售出一张票" + "剩余票数=" + (--ticketNum));
        }
    }
}  

十一、线程的死锁

基本介绍:

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

 package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 模拟线程死锁
 */

public class DeadLock {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}

//线程
class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        //1.如果flag为T,线程就会先得到/持有 o1的对象锁,然后会尝试去得到o2的对先生
        //  当持有到 o2的对象锁时,才会继续往下执行
        //2.如果线程A 得不到o2的对象锁,就会Blocked
        //3.如果flag 为 false,线程会去得到o2的对象锁,
        //4.如果线程B 拿不到o1的对象锁,就会block
        if(flag){
            synchronized(o1){//对象互斥锁
                System.out.println(Thread.currentThread().getName()+"进入1");
                //拿到o1的锁之后会阻塞在这里,直到拿到o2的锁,
                // 此时第二个线程thread2进入,拿到o2的锁,进入堵塞状态,因为一直拿不到o1的锁,
                // o1又因为拿不到o2锁,释放不了,故成为了死锁
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"进行2");
                }
            }
        }else{
            synchronized (o2){

                //第二个线程 o1的锁已经被拿了,
                //这时,又想去拿o1的锁,但是o1在第36行阻塞到了,没有拿到o2的锁,没有释放,故这里也会被阻塞
                //那么这时就会产生一种死锁的情况,要避免
                System.out.println(Thread.currentThread().getName()+"进入3");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"进行4");
                }
            }
        }
    }
}  

十二、释放锁

下面操作会释放锁

  1. 当线程的同步方法、同步代码块 执行结束
  2. 当前线程在同步代码块、同步方法中遇到 break、return
  3. 当前线程在同步代码块、同步方法中出现了 未处理的error Exception ,导致 异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法, 当前线程暂停 ,也会释放锁

下面操作不会释放锁

  1. 线程执行同步代码块或同步方法时,程序调用 Thread.sleep() Thread.yield() 暂停当前线程的执行,不会释放锁
  2. 线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将会被挂起,该现场不会释放锁
  3. 提示:应尽量避免使用 suspend() resume() 来控制线程,方法不再推荐使用了

作业

 package com.hspedu.homework;

import java.util.Random;
import java.util.Scanner;

/**
 * @author DL5O
 * @version 1.0
 */
public class Homework01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.setName("A");
        b.setName("B");
//        a.setDaemon(true);
        a.start();
        b.start();

    }
}

class A extends Thread {
    private int num;
    static boolean loop = true;
    private Random random;

    public A() {
        random = new Random();
    }

    public void showNum() {
        //nextInt(int bound)
        //返回伪随机的,均匀分布 int值介于0(含)和指定值(不包括),从该随机数生成器的序列绘制。
        num = random.nextInt(101);//生成0~100的随机数
        //num = (int)Math.random()*(100-0+1)+0;
        System.out.println("随机数:" + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void run() {
        while (loop){
            showNum();
        }
    }
}

//用来控制A线程退出的线程B
class B extends Thread {
    private String str;
    private A a;
    Scanner scanner;

    public B(A a) {
        this.a = a;
        scanner = new Scanner(System.in);
    }

    @Override
    public void run() {
        synchronized (B.class) {
            while (true) {
                str = scanner.next();
                if (str.equals("Q")) {
                    a.loop = false;
                    break;
                }
            }
        }
    }
}  

 package com.hspedu.homework;

/**
 * @author DL5O
 * @version 1.0
 */
public class Homework02 {
    public static void main(String[] args) {
        Withdraw withdraw = new Withdraw();
        Thread A = new Thread(withdraw);
        Thread B = new Thread(withdraw);
        A.setName("A");
        B.setName("B");
        A.start();
        B.start();
    }
}

class Withdraw  implements Runnable {
    private int money = 10000;
    private boolean loop = true;

    public void deal() {

        //1.这里使用了synchronized 实现了线程同步
        //2.当多个线程执行到这里的时候,就回去争夺 this对象锁
        //3.那个线程执行到 this对象锁,就执行这个代码块,执行玩后,会释放这个锁,准备继续争夺
        //4.争夺不到就阻塞到这里,就blocked
        //5.this 是一个非公平锁

        synchronized (/*Withdraw.class*/this){
            if (money <= 0) {
                loop = false;
                System.out.println("余额不足");
                return;
            }
            money -= 1000;
            System.out.println(Thread.currentThread().getName() + "取走了1000,剩余" + money);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (loop) {
            deal();
        }
    }
}  

文章来源:智云一二三科技

文章标题:「真香Java面试题」JAVA 进程线程详解

文章地址:https://www.zhihuclub.com/198495.shtml

关于作者: 智云科技

热门文章

网站地图