当前位置: 首页 > article >正文

Java基础语法练习43(线程)

目录

一、基本概念

二.线程的基本使用方法

 1.继承Thread 类,重写 run 方法

 2.实现 Runnable 接口,重写 run 方法

3.练习题(多窗口售卖火车票-有超卖的问题)

 三、线程终止

四、线程常用方法

1.第一组线程常用方法:

2.用户线程和守护线程

五、Synchronized 

1.线程同步机制:

2. Synchronized 的实现方法:

六、互斥锁

1.基本介绍:

七、线程的死锁

八、释放锁

九、测试题

题目1:

 题目2:


一、基本概念

进程:

  1. 进程是指运行中的程序,比如我们使用 QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。

 线程:

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

总结:进程:使用QQ  ;      线程:在QQ中可以同时聊天、下载图片、下载文件等 

并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核 cpu 实现的多任务就是并发。

并行:同一个时刻,多个任务同时执行。多核 cpu 可以实现并行。并发和并行,

二.线程的基本使用方法

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

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

 1.继承Thread 类,重写 run 方法

基本示例代码如下:

public class ThreadUse001 {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        cat.start();

        // 4.当main线程启动一个子线程Thread-0, 主线程不会阻塞, 会继续执行
        // 5.主线程结束并不影响子线程结束,程序未必结束
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程里的嘿嘿"+"  主线程的名字为:"+Thread.currentThread().getName());
            Thread.sleep(10);
        }

    }
}


//通过继承Thread类创建线程

//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());
            try {
                sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (times == 100){
                break;
            }

        }
    }
}

 2.实现 Runnable 接口,重写 run 方法

基本示例代码如下:

public class ThreadUse002 {
    public static void main(String[] args) {

        Dog dog = new Dog();
        //dog.start();这里不能调用start
        //需采用以下方法
        Thread t1 = new Thread(dog);
        t1.start();
    }
}


class Dog implements Runnable {

    int times = 0;
    public void run() {    //普通方法,并不会真正的开启一个多线程
        while(true){

            System.out.println("Dog run times: " + (++times)+"线程的名字为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if(times == 20){
                break;
            }

        }


    }
}

3.练习题(多窗口售卖火车票-有超卖的问题)

代码如下:

public class ThreadUse004 {
    public static void main(String[] args) {
/*

        //测试使用Thread 方式
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();
        //这里我们会出现超卖..
        //比如还有2张票,但是同时通过了if的判断条件
        sellTicket01.start();//启动售票线程
        sellTicket02.start();//启动售票线程
        sellTicket03.start();//启动售票线程

*/

        //还是会出现超卖的情况
        System.out.println("===使用实现接口方式来售票=====");
        SellTicket02 sellTicket04 = new SellTicket02();
        new Thread(sellTicket04).start();//第 1 个线程-窗口
        new Thread(sellTicket04).start();//第 2 个线程-窗口
        new Thread(sellTicket04).start();//第 3 个线程-窗口



    }
}


//使用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;//让多个线程共享 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));//1- 0--1--2
        }
    }
}

 三、线程终止

1.基本说明:

       1)当线程完成任务后,会自动退出。

       2)还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式

 示例代码如下:

public class ThreadExit001 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
        Thread.sleep(1000); //mian线程休眠1秒,然后通知退出线程
        t.setLoop(false);

    }
}



class T extends Thread{

    private boolean loop=true;

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

    public void run(){
        while(loop){
            System.out.println("T1线程在运行,名字为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

四、线程常用方法

1.第一组线程常用方法:

  1. setName // 设置线程名称,使之与参数 name 相同
  2. getName // 返回该线程的名称
  3. start // 使该线程开始执行;Java 虚拟机底层调用该线程的 start0 方法
  4. run // 调用线程对象 run 方法;
  5. setPriority // 更改线程的优先级
  6. getPriority // 获取线程的优先级
  7. sleep // 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt // 中断线程
  9. yield: 线程的礼让。让出 cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  10. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

示例代码如下:

public class ThreadUse005 {
    public static void main(String[] args) throws InterruptedException {
        T11 t11 = new T11();
        t11.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
            Thread.sleep(100);

            if(i==4){
                System.out.println("主线程循环5次后 让 子线程先运行");
                //t11.join();//插队
                Thread.yield();//礼让,如果CPU资源多,不一定礼让成功
                System.out.println("子线程运行完了,现在主线程接着运行");
            }

        }

    }
}

class T11 extends Thread {
    public void run() {

        for (int i = 0; i < 10; i++) {
            System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

2.用户线程和守护线程

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

示例代码如下:

public class ThreadUse005 {
    public static void main(String[] args) throws InterruptedException {
        T11 t11 = new T11();

        t11.setDaemon(true);//守护线程   主线程结束,子线程也随之结束
        t11.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
            Thread.sleep(100);

        }

    }

}


class T11 extends Thread {
    public void run() {

        for (int i = 0; ; i++) {
            System.out.println("第"+(i+1)+"次循环,线程名字为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

五、Synchronized 

1.线程同步机制:

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

2. Synchronized 的实现方法:

  1. 同步代码块
    synchronized (对象) { // 得到对象的锁,才能操作同步代码
    // 需要被同步代码;
    }
  2. synchronized 还可以放在方法声明中,表示整个方法 - 为同步方法
    public synchronized void m (String name){
    // 需要被同步的代码
    }

示例代码如下:



public class Synchronized0001 {
    public static void main(String[] args) {
        //测试一把
        SellTicket002 sellTicket002 = new SellTicket002();
        new Thread(sellTicket002).start();//第 1 个线程-窗口
        new Thread(sellTicket002).start();//第 2 个线程-窗口
        new Thread(sellTicket002).start();//第 3 个线程-窗口

    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket002 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean flag = true;

    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            flag = false;
            return;
        }
        //休眠50毫秒, 模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + " 剩余票数="+(--ticketNum));//1- 0--1--2
    }

    @Override
    public void run() {
        while (flag) {

            sell();

        }
    }
}

六、互斥锁

1.基本介绍:

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

注意事项和细节:

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

七、线程的死锁

基本介绍:

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

例如:

妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业. 

示例代码如下:

public class DeadLock01 {
    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
    static Object o2 = new Object();
    boolean flag;
    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }
    @Override
    public void run() {
        //下面业务逻辑的分析
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入 1");
                synchronized (o2) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 2");
                }
            }
        } else {synchronized (o2) { //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
            System.out.println(Thread.currentThread().getName() + " 进入 3");
            synchronized (o1) { // 这里获得 li 对象的监视权
                System.out.println(Thread.currentThread().getName() + " 进入 4");
            }
        }
        }
    }
}

八、释放锁

下面操作会释放锁:

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

 下面操作不会释放锁:

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

九、测试题

题目1:

(1) 在 main 方法中启动两个线程
(2) 第 1 个线程循环随机打印 100 以内的整数
(3) 直到第 2 个线程从键盘读取了 “Q” 命令。

import java.util.Scanner;

public class HomeWork01 {
    public static void main(String[] args) {
        A a = new A();
        a.start();

        B b = new B(a);
        b.start();
    }
}

class A extends Thread{

    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void run(){
        while(flag){
            //p = (int) (Math.random()*3+0); //整数  0,1,2
            System.out.println((int) (Math.random()*100+1));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class B extends Thread {

   private A a;
   Scanner sc = new Scanner(System.in);

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

    boolean flag = true;
    public void run(){
        System.out.println("请输入Q进行线程终止");
        char c = sc.next().toUpperCase().charAt(0);
        if(c == 'Q'){
            a.setFlag(false);
        }

    }

}

 题目2:

(1) 有 2 个用户分别从同一个卡上取钱(总额:10000)
(2) 每次都取 1000,当余额不足时,就不能取款了
(3) 不能出现超取现象 => 线程同步问题

package Thread0002;

public class HomeWork02 {
    public static void main(String[] args) {

        T t = new T();
        Thread t1 = new Thread(t);
        t1.setName("t1");
        Thread t2 = new Thread(t);
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

//1.因为这里涉及到多个线程共享资源,所以使用实现Runable方式
class T implements Runnable {

    private int money = 10000;
    @Override
    public void run() {
        while (true) {

            //1.使用synchronized实现线程同步
            //2.当多个线程执行到这时。要抢this这个锁

            synchronized (this) {

                //判断余额
                if (money < 1000) {
                    System.out.println("余额不足");
                    break;
                }
                money = money - 1000;

                System.out.println(Thread.currentThread().getName() + "取了1000" + " 当前余额为" + money);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }

    }
}


http://www.kler.cn/a/586577.html

相关文章:

  • 网络实验操作-VLAN
  • lws-minimal-ws-server前端分析
  • 考研数学复习误区:如何避免无效学习?
  • 3.4 基于TSX的渲染函数类型安全实践
  • Julia语言的饼图
  • Java常见的几种内存溢出及解决方法
  • BSides-Vancouver-2018 ftp匿名访问、hydra爆破22端口、nc瑞士军刀、提权
  • C++类对象创建全解析:从构造函数到内存管理
  • SQL Server语法实战指南:核心语法、注意事项与高频问题解析
  • Java基础-List、Set、Map
  • MATLAB中enumeration函数用法
  • 【FPGA实战】Verilog实现DE2-115的流水灯控制
  • 四、Jmeter工具接口脚本编写
  • 用Python实现持续集成与部署(CI/CD)流程:自动化测试、构建与部署
  • 什么是强哈希算法pbkdf2(Password-Based Key Derivation Function)
  • 向量数据库:A Brief Introduction
  • 04_Linux驱动_05_pinctrl子系统
  • 阿里云oss开发实践:大文件分片、断点续传、实时进度 React+Node+Socket.IO
  • 【接口封装】——22、读写文件
  • iOS底层原理系列03-Objective-C运行时机制