黑马程序员-------java 多线程

本文深入探讨了多线程的概念、如何在自定义代码中创建线程,以及多线程在ASP.Net和Unity开发中的应用。详细讲解了线程与进程的区别、线程的四种运行状态、获取线程对象与名称的方法,同时提供了解决同步问题的策略,如使用同步代码块和静态同步函数。最后,文章通过实例展示了多线程安全问题及解决方法,包括死锁问题及其避免策略。

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

----------------------ASP.Net+Unity开发 .Net培训 期待与您交流! ---------------------


进程和线程的区别

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,它只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可以与同属一个进程的其他的线程共享进程所拥有的全部资源。
    一个线程可以创建和撤销另一个线程;同一个线程中的多个线程之间可以并发执行。
    一个程序至少有一个进程,一个进程至少有一个线程。
    进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大提高了程序运行效率。在执行过程中,每个独立的线程有一个程序运行入口,顺序执行序列和程序出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    从逻辑角度看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但是操作系统并没有将多个线程看作多个独立应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

如何在自定义的代码中自定义多个线程
    接口:Runnable
    类:java.lang.Thread

    创建新执行线程有两种方法:

    第一种方法:
        定义一个继承Thread类的类并且实现public void run()方法,
        然后再主线程main方法中调用继承类的start方法来执行新线程。
        注意:run方法和start方法的区别。

线程4种运行状态

    被创建
    运行
    睡眠
    阻塞
    终结
    
获取线程对象以及名称
    thread.getName();        //thread 代表线程对象,方法得到线程对象的默认名称
    thread.setName();        //thread 代表线程对象,方法设置线程对象的默认名称
    static Thread Thread.currentThread()    // 返回当前的线程对象


售票的例子
    java.lang.IllegalThreadStateException  :线程状态异常,
                                当一个继承了Thread类的线程对象在主线程中调用start()方法多次时会出现这个异常。

    介绍Runnable接口

    public interface Runnable
         Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个run方法。

         1.定义一个实现了Runnable接口的类
         2,覆盖Runnable接口中的run方法
         3,通过Thread类建立线程对象
         4,将Runnable接口的子类对象作为实际参数传递给Thread的构造函数。
         5,调用该Thread类的start方法开启线程并调用Runnable接口子类run方法。


继承Thread 和 实现Runnable接口  2种方法实现线程的区别:

    1,实现Runnable接口 可以 避免了单继承的局限性
    2,继承Thread,线程代码存放在Thread子类的run方法中;实现Runnable,线程代码存放在接口子类的run方法中。

多线程的安全问题:

    当多条语句在操作同一个线程共享数据时,一个线程的多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据错误。
解决办法:
    对多条操作共享数据的语句,只允许一个线程执行完才允许别的线程来执行。

同步代码块
    //对象的存在代表一个标志位,0,1,代表线程是否可以执行代码块中的代码,
    //对象 就是 一个锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取了CPU的执行权也不能执行    
    synchronized(对象){
        需要被同步的代码
    }

同步的前提

    1,必须要有两个或者两个以上的线程。
    2,必须是多个线程使用同一个锁。

优点:解决了多线程的安全问题。
缺点:多个线程都需要判断锁,更多的消耗了资源

如何找问题:
    明确哪些代码是多线程代码
    明确共享数据
    明确多线程哪些语句是操作共享数据的

同步方法(同步函数)
    public synchronized  method(args)    //将synchronized作为修饰符加到方法名之前
同步函数用的是什么锁?
    同步函数需要被对象调用,那么函数都有一个所属对象引用。就是this
    所以同步函数的锁就是this、

静态同步函数的锁不是this
    静态方法中不可能定义this,静态方法是由类而不是类对象来调用
    那么静态同步函数的锁是什么?
    静态的同步方法,是用的锁是该方法所在的类的字节码文件对象,就是   类名.class  

扩展(非常重要知识):

    单例设计模式
    |    饿汉式
    |    懒汉式
    
    1,饿汉式
 
       class  Single{
            private static final Single s = new Single();
            private Single(){}
            public static Single getInstance(){
                return s;
            }
        }


    2,懒汉式
        //懒汉式在多线程访问时,会出现同步安全问题,需要添加同步代码块
        class Single{
            private static Single s = null;
            private Single(){}
            public static Single getInstance(){
                //延迟加载实例对象
                if(s == null){
                    s = new Single();
                }
                return s;
            }
        }


        //修改之后的懒汉式
 
       class Single{
            private static Single s = null;
            private Single(){}
            public static Single getInstance(){
                if(s == null){
                    synchronized(Single.class){
                        if(s==null){
                            s = new Single();
                        }
                    }
                }
                return s;
            }
        }


死锁

    同步中嵌套同步,而同步锁却不同
    重点:
        写一个关于死锁的代码

线程间通信
    多个线程操作同一个资源,操作的动作不同。

重点:等待唤醒机制    

    wait;
    notify;
    notifyAll;
    等方法 都需要使用在同步之中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
    为什么这些操作线程的方法要定义Object类中呢?
    因为这些方法在操作同步中的线程时,都必须要标识他们所操作的线程持有的锁。
    不可以对不同锁中的线程进行唤醒。
    也就是说,等待和唤醒必须是同一个锁。

等待的线程都存放到线程池之中,唤醒时一般唤醒第一个等待的线程

    生产者消费者问题(当出现多个生产者消费者时,要注意几个问题点)
            
       public class WaitNotifyDemo {
                    public static void main(String[] args) {
                        Resource res = new Resource("商品");
                        Producer pro = new Producer(res);
                        Customer cus = new Customer(res);
                        new Thread(pro).start();
                        new Thread(cus).start();
                        new Thread(pro).start();
                        new Thread(cus).start();
                    }
                }
                class Producer implements Runnable {
                    private Resource res;
                    Producer(Resource res) {
                        this.res = res;
                    }
                    @Override
                    public void run() {
                        while (true) {
                            res.setRes();
                        }
                    }
                }
                class Customer implements Runnable {
                    private Resource res;
                    Customer(Resource res) {
                        this.res = res;
                    }
                    @Override
                    public void run() {
                        while (true) {
                            res.getRes();
                        }
                    }
                }
                class Resource {
                    private String name;
                    private int count;
                    private boolean flag; // flag表示是否存在资源
                    Resource(String name) {
                        this.name = name;
                        count = 0;
                        flag = false;
                    }
                    public void setFlag(boolean flag) {
                        this.flag = flag;
                    }
                    public synchronized void setRes() {
                            while(this.flag) {        //注意要使用while而不是使用if来控制判断,当线程从wait状态醒来需要重新判断flag
                                try {
                                    wait();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            System.out.println("生产者---------" + Thread.currentThread().getName()
                                    + this.name + " " + (++count));
                            setFlag(true);
                            notifyAll();
                    }
                    public synchronized void getRes() {
                            while (!this.flag) {
                                try {
                                    wait();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            System.out.println("消费者" + Thread.currentThread().getName()
                                    + this.name + " " + count);
                            setFlag(false);
                            notifyAll();
                    }
                }



生产者消费者 jdk1.5 升级方法

    将同步synchronized替换成显示的Lock操作。
    将Object中wait,notify,notifyAll,替换了Condition对象来操作
    Condition对象可以通过 Lock锁来获取,
    lock锁一定需要释放,所以需要使用 try{}finally{lock.unlock();}来释放

java.util.concurrent.locks包
    public interface Lock
        Lock实现提供了比使用synchronized方法和语句可以获取的更广泛打锁定操作。
        此实现允许更灵活打结构,可以具有差别很大打属性,可以支持多个相关的
        Condition对象
        其实现类: ReentrantLock
        方法:    public void lock();        获取锁
                public  void unlock();        释放锁
                public Condition newCondition()    返回绑定到此Lock实例的新Condition实例
    public interface Condition
        Condition  将Object监视器方法(wait,notify,notifyAl)分解成截然不同的对象,
        以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set)。
        其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。
        方法:    public void await();    线程等待
                public void signal();    唤醒线程
                public void signalAll();    唤醒所有线程
    

停止线程(注意:Thread.stop方法已经过时不再使用)

    如何停止线程?只有一种方法,run方法结束。
    1.定义循环结束标记
        因为线程运行代码一般都是循环,只要控制了循环就可以让线程结束。

        特殊情况:
            当线程处于冻结状态或者说是中断状态(如wait状态。。),就不会读取到循环控制标记的改变,也就无法让线程结束。
    2 使用interrupt(中断)方法
        该方法是结束线程的冻结状态,使得线程回到运行状态;同时线程上会产生一个 InterruptedException异常。

        当没有指定打方式让冻结打线程恢复到运行状态时,需要对冻结状态进行清除。

Thread 的其他一些方法
    1.public final void setDaemon(boolean on)        //on为true时,表示标记为守护线程
        将一个线程标记为守护线程或者用户线程。
        注意:该方法必须在线程启动之前调用。

        守护线程:也叫后台线程。
                后台线程的特点:后台线程的开启和运行都和前台线程没有区别,
                            只有后台线程的关闭和前台线程不同;
                            当 主线程和前台线程 都结束之后,后台线程会自动关闭。
    
    2.public final void join()
        throws InterruptedException
            
        例子:
            当main线程向下运行的时候,运行到 t1.join();
            意思是:线程t1要加入到main线程中来,线程t1要霸占main线程的cpu执行权,
            然后main线程会处于冻结状态,一直到t1线程结束,main线程才会恢复运行状态。
        
    3.public String toString()
            返回该线程的字符串表示形式,包括线程名称,优先级和线程组。
    4.public void setPriority(int newPriority)
            设置优先级,总共就10个级别。
                Thread.MAX_PRIORITY    最大优先级
                Thread.NOR_PRIORITY    正常优先级
                Thread.MIN_PRIORITY    最小优先级
    4.public static void yield()
        暂停当前正在执行的线程对象,并执行其他线程。

使用匿名内部类来运行线程

  new Thread(){
        public void run(){
            while(true){
                ..........................
            }
        }
    }.start();

    new Thread(
        new Runnable(){
            public void run(){
                while(true){
                ...................................
                }
            }
        }

    ).start();


 

----------------------ASP.Net+Unity开发 .Net培训 期待与您交流! ---------------------



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值