java基础(19)(线程与进程,创建线程的方式,线程的状态,火车卖票问题,同步机制,死锁)

本文详细介绍了Java中的并发与并行概念,线程与进程的区别,线程调度方式,以及Java程序的运行原理。讲解了如何通过继承Thread类和实现Runnable接口创建线程,对比了两者区别。此外,还深入探讨了线程状态方法,包括sleep、join、setDaemon、setPriority和yield。展示了线程安全问题,通过同步代码块、锁(ReentrantLock)解决并发问题,以及如何避免死锁。最后提到了一些线程安全的类,如Vector和StringBuffer。

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

线程
并发与并行

    并发:指两个或多个事件在同一时间段内发生

    并行:指两个或多个事件在同一时刻发生

线程与进程

    进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

    线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    一个程序运行过至少有一个进程,一个进程可以包含多个线程。

线程调度

    分时调度:所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

    抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

java程序运行原理

        Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
        该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
        一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。

JVM是多线程吗

    JVM启动至少启动了垃圾回收线程和主线程,所以是所以是多线程的。

    main方法的代码执行的位置就是在主线程(路径)

多线程的创建
一 继承Thread类

    定义Thread的子类,重写该类的run方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

    创建Thread子类的实例,即创建了线程对象

    调用线程对象的start()方法来启动该线程。

代码演示

    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello world");
            }
        }
    }
     
    public class ExtendsThreadTest {
        public static void main(String[] args) {
            /**
             * 1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
             *
             * 2. 创建Thread子类的实例,即创建了线程对象
             *
             * 3. 调用线程对象的start()方法来启动该线程
             */
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }

二 实现Runnable接口

    定义Runnable接口的实现类,并重写该接口的run方法,该run()方法的方法体同样是该线程的线程执行体

    创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

    调用线程对象的start()方法来启动线程。

代码演示

    public class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello world");
            }
        }
    }
     
    public class ImplementRunnableTest {
        public static void main(String[] args) {
            /**
             * 1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
             *
             * 2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
             *
             * 3.调用线程对象的start()方法来启动线程。
             */
            MyThread myThread = new MyThread();
            Thread thread = new Thread(myThread);
            thread.start();
        }
    }

Thread和Runnable的区别

    继承Thread:由于子类重写了Thread类的run方法,当调用start()时直接找子类的run方法

    实现Runnable:构造函数中传入了Runnable的引用,有个成员变量记住了它,调用run方法时内部判断成员变量是否为空。

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享

    实现Runnable接口比继承Thread类所具有的优势

    适合多个相同的程序代码的线程去共享同一个资源

    可以避免java中的单继承的局限性

    增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立

    线程池只能存放实现Runnable或Callable类线程,不能直接放入继承Thread的类。

Thread类的介绍
构造方法


方法名    说明
public Thread()    分配一个新的线程对象
public Thread(String name)    分配一个指定名字的新的线程对象
public Thread(Runnable target)    分配一个带有指定目标新的线程对象
public Thread(Runnable target,String name)    分配一个带有指定目标新的线程对象并指定名字

代码演示

    public class MyThread extends Thread {
        public MyThread() {
        }
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(getName()+":hello world");
            }
        }
    }
     
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello world");
            }
        }
    }
     
    public class ThreadTest01 {
        public static void main(String[] args) {
            /**
             * public Thread()分配一个新的线程对象
             * public Thread(String name)分配一个指定名字的新的线程对象
             * public Thread(Runnable target)分配一个带有指定目标新的线程对象
             * public Thread(Runnable target,String name)分配一个带有指定目标新的线程对象并指定名字
             */
     
            //public Thread()分配一个新的线程对象
            MyThread myThread = new MyThread();
            myThread.start();
     
            //public Thread(String name)分配一个指定名字的新的线程对象
            MyThread tom = new MyThread("tom");
            tom.start();
     
            //public Thread(Runnable target)分配一个带有指定目标新的线程对象
            MyRunnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
     
            //public Thread(Runnable target,String name)分配一个带有指定目标新的线程对象并指定名字
            MyRunnable myRunnable1 = new MyRunnable();
            Thread jack = new Thread(myRunnable1, "jack");
            jack.start();
        }
    }

成员方法


方法名    说明
public String getName()    获取当前线程名称。
public void setName(String name)    设置线程的名称
public void start()    Java虚拟机调用此线程的run方法
public void run()    此线程要执行的任务在此处定义代码
public static Thread currentThread()    返回对当前正在执行的线程对象的引用

代码演示

    public class MyThread extends Thread{
        public MyThread() {
        }
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(getName()+":hello world");
            }
        }
    }
     
    public class MembersMethod {
        public static void main(String[] args) {
            /**
             * public String getName()获取当前线程名称。
             * public void setName(String name)设置线程的名称
             * public void start()Java虚拟机调用此线程的run方法
             * public void run()此线程要执行的任务在此处定义代码
             * public static Thread currentThread()返回对当前正在执行的线程对象的引用
             */
     
            MyThread myThread = new MyThread();
            myThread.setName("rose");
            myThread.start();
            Thread thread = MyThread.currentThread();
            System.out.println(thread);
        }
    }

线程状态方法


方法名    说明
public static void sleep(long millis)    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public final void join()    等待该线程终止
public final void setDaemon(boolean on)    将该线程标记为守护线程或用户线程
public final void setPriority(int newPriority)    更改线程的优先级。默认为5, 最小级别:0 ,最大级别:10
public static void yield()    暂停当前正在执行的线程对象,并执行其他线程。

代码演示

    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 8; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+":hello world");
            }
        }
    }
     
    public class ThreadStateSleepTest {
        public static void main(String[] args) {
            /**
             * 线程状态方法
             * 方法名说明public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
             * public final void join()等待该线程终止
             * public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程
             * public final void setPriority(int newPriority)更改线程的优先级。默认为5, 最小级别:0 ,最大级别:10
             * public static void yield()暂停当前正在执行的线程对象,并执行其他线程
              */
            MyThread myThread1 = new MyThread();
            MyThread myThread2 = new MyThread();
            MyThread myThread3 = new MyThread();
            myThread1.setName("tom");
            myThread2.setName("jack");
            myThread3.setName("rose");
     
            myThread1.start();
            myThread2.start();
            myThread3.start();
     
        }
    }
     
    //join
     
    public class ThreadStateJoinTest {
        public static void main(String[] args) {
            /**
             * public final void join()等待该线程终止
             */
            MyThread myThread1 = new MyThread();
            MyThread myThread2 = new MyThread();
            MyThread myThread3 = new MyThread();
     
            myThread1.setName("张无忌");
            myThread2.setName("赵敏");
            myThread3.setName("周芷若");
     
            myThread1.start();
            try {
                //public final void join()等待该线程终止
                //只有myThread1线程执行完毕,其他线程才能争夺资源
                myThread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myThread2.start();
            myThread3.start();
        }
    }
     
    //setDaemon
     
    public class ThreadStateSetDaemonTest {
        public static void main(String[] args) {
            /**
             * public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程
             * public final void setPriority(int newPriority)更改线程的优先级。默认为5, 最小级别:0 ,最大级别:10
             * public static void yield()暂停当前正在执行的线程对象,并执行其他线程
             */
            MyThread mt1 = new MyThread();
            MyThread mt2 = new MyThread();
            MyThread mt3 = new MyThread();
     
            mt2.setName("关羽");
            mt3.setName("张飞");
    //        mt1.setName("刘备");
    //        mt1.setDaemon(true);
            Thread.currentThread().setName("刘备");
     
    //        mt1.start();
            mt2.start();
            mt3.start();
     
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
     
     
    //setPriority
     
    public class ThreadStateSetPriorityTest {
        public static void main(String[] args) {
            /**
             * public final void setPriority(int newPriority)更改线程的优先级。默认为5, 最小级别:0 ,最大级别:10
             */
            MyThread mt1 = new MyThread();
            MyThread mt2 = new MyThread();
            MyThread mt3 = new MyThread();
     
            mt1.setName("飞机");
            mt2.setName("高铁");
            mt3.setName("轮船");
     
            mt1.setPriority(10);
            mt2.setPriority(5);
            mt3.setPriority(1);
     
            mt1.start();
            mt2.start();
            mt3.start();
        }
    }
     
     
    //yield
    public class ThreadStateYieldTest {
        public static void main(String[] args) {
            /**
             * public static void yield()暂停当前正在执行的线程对象,并执行其他线程
             */
            YieldThread yt1 = new YieldThread();
            YieldThread yt2 = new YieldThread();
     
            yt1.setName("小明");
            yt2.setName("小花");
     
            yt1.start();
            yt2.start();
        }
    }
     
    public class YieldThread extends Thread{
        @Override
        public void run() {
            System.out.println(getName()+"-->start");
            Thread.yield();
            System.out.println(getName()+"-->end");
        }
    }


     
     
     

线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

火车站卖票问题

    当不添加同步代码块的适合,买票会出现两个问题

    相同的票数,可以被卖两次

    不存在的票如-1票却出现了

    分析出现的问题

    线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

    为了解决以上问题,使用同步代码快来解决

线程同步

    当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

    要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。

实现同步的方式

    同步代码快

    同步方法

    Lock锁等

同步代码快的实现方法

    synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

代码演示

    public class RunnableImpl implements Runnable {
        //模拟买票
        //定义一个多个线程共享的票源
        private int ticket = 100;
     
        //创建锁对象
        Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (obj) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "票");
                        ticket--;
                    }
                }
            }
        }
     
        /**
         * 售票员员3-->正在卖第0票
         * 售票员员2-->正在卖第-1票
         */
     
        //出现了以上问题
        //解决问题
        //1.同步代码块synchronized关键字
        //2.使用同步方法
        // public synchronized void run() {
     
    }
     
    public class SellTicketTest {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            Thread t1 = new Thread(runnable, "售票员员1");
            Thread t2 = new Thread(runnable, "售票员员2");
            Thread t3 = new Thread(runnable, "售票员员3");
            t1.start();
            t2.start();
            t3.start();
        }
    }

使用同步锁的方式

    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

    在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (BLOCKED)。

代码演示

    public class RunnableImpl implements Runnable {
        //模拟买票
     
        //定义一个多个线程共享的票源
        private int ticket = 100;
     
        Lock lock = new ReentrantLock();
     
        //创建锁对象
        Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                    lock.lock();
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "票");
                            ticket--;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            lock.unlock();
                    }
     
                }
            }
        }
     
        /**
         * 售票员员3-->正在卖第0票
         * 售票员员2-->正在卖第-1票
         */
     
        //出现了以上问题
        //解决问题
        //1.同步代码块synchronized关键字
        //2.使用同步方法
        //3.加锁
     
    }
     
    public class SellTicketTest {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            Thread t1 = new Thread(runnable, "售票员员1");
            Thread t2 = new Thread(runnable, "售票员员2");
            Thread t3 = new Thread(runnable, "售票员员3");
            t1.start();
            t2.start();
            t3.start();
            
        }
    }

锁的总结

    同步中,所最好是同一个对象,如果不是同一个对象,还是会有线程安全问题

    锁:this代表当前对象

    锁:当前类字节码对象(类名.class)

Lock锁

    java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

lock接口的方法


方法名    说明
public void lock()    加同步锁。
public void unlock()    释放同步锁。

代码演示

    /*
        卖票案例出现了线程安全问题
        卖出了不存在的票和重复的票
        解决线程安全问题的三种方案:使用Lock锁
        java.util.concurrent.locks.Lock接口
        Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
        Lock接口中的方法:
            void lock()获取锁。
            void unlock()  释放锁。
        java.util.concurrent.locks.ReentrantLock implements Lock接口
        使用步骤:
            1.在成员位置创建一个ReentrantLock对象
            2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
     */
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private  int ticket = 100;
     
        //1.在成员位置创建一个ReentrantLock对象
        Lock l = new ReentrantLock();
     
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while(true){
                //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
                l.lock();
     
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                        //票存在,卖票 ticket--
                        System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                        l.unlock();//无论程序是否异常,都会把锁释放
                    }
                }
            }
        }
    }

死锁

    当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他同步。这时会引发一种现象:程序出现无限等待,这种现象我们称为死锁

    简单理解:指两个线程或多个线程相互持有对方所需要的资源,导致线程都处于等待状态,无法往下执行,这就是死锁

代码演示

    public class LockA {
        private LockA(){}
        public static final LockA locka = new LockA();
    }
     
    public class LockB {
        public LockB() {
        }
        public   static final LockB lockb = new LockB(){};
    }
     
     
    public class DeadLock implements Runnable {
            private int i = 0;
     
        /**
         * 死锁的出现主要是因为同步中嵌套同步了,我们只需要保证不让它们进行嵌套即可解决死锁的出现!
         */
     
        @Override
        public void run() {
            while (true){
                if (i%2 == 0){
                    synchronized (LockA.locka){
                        System.out.println("if....locka");
                        synchronized (LockB.lockb){
                            System.out.println("if....lockb");
                        }
                    }
                }else {
                    synchronized (LockB.lockb){
                        System.out.println("else....lockb");
                      /*  synchronized (LockA.locka){
                            System.out.println("else....locka");
                        }*/
                    }
                }
                i++;
            }
        }
    }
     
    public class DeadLockTest {
        public static void main(String[] args) {
            DeadLock deadLock = new DeadLock();
            Thread thread1 = new Thread(deadLock);
            Thread thread2 = new Thread(deadLock);
            thread1.start();
            thread2.start();
        }
    }

解决死锁问题

    死锁的出现主要是因为同步中嵌套同步了,我们只需要保证不让它们进行嵌套即可解决死锁的出现!

线程安全的类

    Vector,StringBuffer,Hashtable

    vector是线程安全的,ArrayList是线程不安全的

    StringBuffer是线程安全的,StringBuilder是线程不安全的

    Hashtable是线程安全的,HashMap是线程不安全的。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值