java第一阶段-JavaSE-Day23-多线程(二)

本文详细介绍了Java线程的常用方法,包括Thread.sleep(), 线程优先级设置,线程礼让,线程中断和后台线程。通过示例代码展示了它们的用法。此外,文章还深入探讨了多线程安全问题,如线程同步的必要性,同步代码块、同步方法和Lock锁的使用,并通过卖票案例说明了线程安全问题及其解决方案。最后,提到了线程死锁的概念和现象,并简述了线程的生命周期状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线程Thread类常用方法

(一)线程API之线程休眠

        1、方法介绍

 

        代码示例:

package com.offcn.demo01;

public class UseSleep {
    public static void main(String[] args) {
        new Thread("线程1"){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"-----上课好困---"+i);
                }
            }
        }.start();

        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"----学习java---"+i);

                }
            }
        };
        Thread t = new Thread(r,"线程2");
        t.start();
    }
}

(二)线程API之线程优先级

        1、方法介绍

 

        设置了线程优先级【线程的执行java遵循抢占式机制,不随着人的意志为转移,规律人为决定不了】,表现就是在一段时间内,开始的时候高优先级执行次数多点,结束的时候优先级低的执行的次数多一点

        尽量的要前期执行优先级高的【不是百分百】也就是,不一定优先级高的线程先执行完

        代码示例:

package com.offcn.demo01;

public class demo01 {
    public static void main(String[] args) {
        Thread t1 = new Thread("线程一"){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(getName()+"---线程一---"+i);
                }
            }
        };
        t1.setPriority(Thread.MAX_PRIORITY);
        Thread t2 = new Thread("线程二"){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(getName()+"---线程二---"+i);
                }
            }
        };
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

(三)线程API之线程礼让

        1、方法介绍

 

        代码示例:

package com.offcn.demo01;

public class Demo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread("线程1"){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {

                    if(i%2==0){
                        Thread.yield();
                    }
                    System.out.println(getName()+"---线程一---"+i);
                }
            }
        };
        Thread t2 = new Thread("线程2"){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(getName()+"---线程二---"+i);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

(四)线程API之线程中断【对线程的休眠、等待等状态的中断】

        1、方法介绍

 

        代码示例:

package com.offcn.demo01;

import java.util.Scanner;

import static java.lang.Thread.sleep;

public class Demo03 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(500000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程");

            }
        };
        t.start();
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        if(s.equals("Y")){
            t.interrupt();
        }else {
            System.out.println("继续等待");
        }
    }
}

(五)线程API之后台线程【守护线程】

        1、方法介绍

 

        解释:

        守护线程为了保护其他线程执行而存在的,为其他线程提供良好的运行环境,如果被保护的执行线程已经消失(任务结束),守护线程在不久的将来也会跟着消失

        代码示例:

package com.offcn.demo01;

public class Demo04 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 366; i++) {
                    System.out.println("被守护线程"+i);
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("守护线程");
                }
            }
        };
        t2.setDaemon(true);
        t1.start();
        t2.start();
    }
}

二、多线程安全问题

(一)问题描述

        1、买票的案例

        案例需求:

        某电影院目前正在上映一部电影,共100张票,而它由3个窗口卖票,设计一个影院卖票程序

        实现步骤:

        定义一个类Sell实现Runnable接口,里面定义一个成员变量:private int ticket=100;

        在Sell类中重写run()方法实现卖票:

                判断票数大于0,就卖票,并告知是哪个窗口卖的

                卖了票之后,总票数要减1

                票卖没了,线程停止

        定义测试类,里面有main方法:

                创建Sell对象

                创建三个Thread类对象,把Sell对象作为构造方法的参数,并给树对应窗口名称

                启动线程

        代码示例:(注意这个案例只是为了证明多线程的安全问题)

package com.offcn.demo02;

public class Sell implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"卖出"+ticket+"号票,剩余"+(ticket-1)+"张");
            ticket--;
        }
    }
}
package com.offcn.demo02;

public class Demo01 {
    public static void main(String[] args) {
        Sell s = new Sell();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

        2、案例中出现的问题

 

        卖票出现了问题:

                ①相同的票出现了多次

                ②出现了负数的票

        问题产生的原因:线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题【多线程在同时操作同一个数据时就发生安全问题】

        3、解决方案:保证线程执行任务的原子(完整性)操作

        原子操作就是不可分割的操作,例如:一个售票窗口在售票的过程中的代码就是一个不可分割的操作。

        就是一条线程在操作某个任务的时候,一定要保证他的操作必须完全都结束,其他的线程才有机会做这个操作

        体现:让cpu变得同步【有序】

        处理方案:

                ①同步代码块

                ②同步方法

                ③Lock接口

(二)同步代码块

        1、格式

        synchronized(锁对象){

                原子操作的代码;【有可能出现线程安全问题的代码】

        }

        2、说明

        ①锁对象可以是任何引用类型的对象,只要是一个java对象就可以当做锁对象

        ②锁对象一旦被确定下来要保证唯一性,锁对象只能有一份

        3、同步代码块执行原理

        一段代码一旦使用同步代码块之后, 线程只要进入同步代码块,就要先获取锁对象,只有拿到锁对象才能进入同 步代码块执行任务, 在这个线程执行任务期间,CPU可能会把资源切换到其他线程,即使其他线程也要进入这个 同步代码块,因为锁对象已经被之前的线程拿走了,它没有锁对象,依然进不来, 只能等之前线程执行完,把锁 对象归还,其他线程才有机会拿到锁对象,执行里面任务.

        4、优缺点

        好处:解决了多线程的数据安全问题

        弊端:当线程很多时,因为每个线程执行任务时都要先判断是否有锁对象可用,拿锁对象,执行完之后把锁对象归还,这就是耗费资源的,无形之中降低程序的运行效率,这个弊端可以完全忽略

        代码示例:(以卖票为例)

package com.offcn.demo03;

public class Sell implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0){
            synchronized ("A") {
                if(ticket>0) {
                    System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
                    ticket--;
                }
            }
        }
    }
}
package com.offcn.demo03;
public class Demo01 {
    public static void main(String[] args) {
        Sell s = new Sell();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

(三)同步方法

        1、格式

        修饰符 synchronized 返回值类型 方法名(方法参数){

                方法体;

        }

        2、什么时候用

        如果你发现你写的一个成员方法,这个方法内部所有的代码都使用同代码块给包裹了,并且使用的锁对象是 this的时候.就可以使用同步成员方法简化这个方法

package com.offcn.demo04;

public class Sell implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0){
            sell();
        }
    }
    public synchronized void sell(){
        if(ticket>0) {
            System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
            ticket--;
        }
    }
}
package com.offcn.demo04;


public class Demo01 {
    public static void main(String[] args) {
        Sell s = new Sell();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

        3、同步静态方法

        与同步方法类似,在synchronized前加static,成员便来那个也要是静态的。

     

package com.offcn.demo05;

public class Sell implements Runnable {
    private static int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0){
            sell();
        }
    }
    public static synchronized void sell(){

            if(ticket>0) {
                System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
                ticket--;
            }

    }
}
package com.offcn.demo05;

import java.util.Hashtable;

public class Demo01 {
    public static void main(String[] args) {
        Sell s = new Sell();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

4、两个同步方法的锁对象

①普通同步方法的锁对象是this

②静态同步方法的锁对象是 类名.class

(四)Lock锁

1、概述

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更加清晰的表达如何加锁和释放锁,jdk1.5之后提供了一个新的锁对象Lock

2、使用方法

 

 

代码示例:

package com.offcn.demo06;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Sell implements Runnable {
    private int ticket = 100;
    private Lock l = new ReentrantLock();

    @Override
    public void run() {
        while(ticket > 0){
            l.lock();
            if(ticket>0) {
                System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
                ticket--;
            }
            l.unlock();
        }
    }
}
package com.offcn.demo06;



public class Demo01 {
    public static void main(String[] args) {
        Sell s = new Sell();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

三、线程死锁的概述以及现象演示

        1、概述

        线程死锁是指由于两个或者多个线程互相持有对方所需要的锁资源,导致这些线程处于等待状态,无法继续执行

        实际工作不要写出死锁,要规避死锁

 

代码示例:

package com.offcn.demo07;

public class SeeDeadLock {
    private static final String L1 = "1";
    private static final String L2 = "2";

    public static void main(String[] args) {
        new Thread("线程1"){
            @Override
            public void run() {
                synchronized (L1){
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (L2){
                        System.out.println("任务1");
                    }
                }
            }
        }.start();
        new Thread("线程2"){
            @Override
            public void run() {
                synchronized (L2){
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (L1){
                        System.out.println("任务2");
                    }
                }
            }
        }.start();
    }
}

四、线程状态

        1、概述

        线程生命周期,线程对象从生到死的一个过程,当线程被创建并启动以后,他既不是一启动就进入了执行状态,也不是一直处于执行状态,线程对象在不同时期有不同的状态

        2、线程状态图示

 

        在java代码中对线程的状态有更加详细的描述,是通过Thread类中,调用getState()此方法可以返回线程的各种状态,提供一个枚举类型描述线程的各种状态

 

3、

 

getState()获取线程当前的状态值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

皇正经

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

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

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

打赏作者

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

抵扣说明:

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

余额充值