Java中的线程

一.程序,进程,线程之间的关系

程序、进程和线程是计算机中重要的概念,它们描述了不同层次上的执行和管理方式。

程序是指由一系列指令组成的代码文件,它定义了算法和逻辑,用来解决特定的问题。程序本身是静态的,只有在被加载到内存并被操作系统调度执行时才会动起来。

进程是程序在执行时的一个实例,它是操作系统进行资源管理和调度的基本单位。一个进程拥有独立的内存空间和系统资源,包括打开的文件、网络连接等。进程之间相互独立,一个进程的崩溃不会影响其他进程的运行。

线程是进程内的独立执行单元,一个进程可以有多个线程。线程共享相同的内存空间和系统资源,可以与其他线程共享数据。相对于进程来说,线程的创建和销毁更加轻量级,切换和通信的开销也较小。

三者关系:一个程序在执行时被操作系统加载成一个进程,进程中可以有多个线程并行执行。程序是静态的,进程和线程是动态的执行状态。进程和线程通常是并发执行的,它们的执行顺序和并发性由操作系统的调度算法决定。不同的操作系统可能有不同的调度策略。同时,进程和线程是操作系统资源的消耗者。每个进程都有自己独立的内存空间和系统资源,而线程则共享进程的资源。因此,正确的管理进程和线程,合理分配资源,能够提高系统的性能和稳定性。

二.线程

2.1如何创建线程

在Java中,你可以通过两种方式创建线程:

继承Thread类:创建一个继承自Thread类的子类,重写run()方法,并在run()方法中定义线程的任务逻辑。然后实例化子类对象,并调用start()方法来启动线程。

以下是一个使用继承Thread类创建线程的示例代码:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程任务逻辑
        System.out.println("线程任务逻辑");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}

实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,并在run()方法中定义线程的任务逻辑。然后实例化该类对象,并将其作为参数传递给Thread类的构造方法。再调用start()方法来启动线程。

以下是一个使用实现Runnable接口创建线程的示例代码:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程任务逻辑
        System.out.println("线程任务逻辑");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start(); // 启动线程
    }
}

无论使用哪种方式创建线程,都需要调用start()方法来启动线程。线程启动后,会自动调用run()方法来执行线程任务。

三.线程中的常用方法

线程中有一些常用的方法,以下是其中一些常见的方法:
(1)start()方法:用于启动线程。调用该方法后,线程会被放入就绪队列,等待获取CPU的执行权。
(2)run()方法:线程的主体代码逻辑通常在run()方法中实现。该方法定义了线程的任务。
(3)sleep(long millis)方法:让当前线程暂停执行一段时间,单位为毫秒。在暂停期间,线程状态不会变为阻塞或就绪状态,而是保持运行状态。
(4)join()方法:等待调用该方法的线程执行完毕后再继续执行其他线程。可以通过传入参数指定最长等待时间。
(5)yield()方法:让出当前线程的CPU执行权,使得其他具有相同优先级的线程有机会执行。yield方法只是让当前线程重新回到可运行状态,而不是阻塞状态。
(6)setName():为线程设置名字。

(7)currentThread():获取当前线程。

(8)getName():获取当前线程的名字。

(10)setPriority():设置线程的优先级(从一到十,默认是5)。

         MAX_PRIORITY:取值为10,表示最高优先级。 

         MIN_PRIORITY:取值为1,表示最底优先级。

        NORM_PRIORITY:取值为5,表示默认的优先级

(11)getPriority():获取当前线程的优先级。

(12)setDaemon():设置守护线程。

线程中常用方法的具体实现:

public class Mythread1 extends Thread{
    //创建线程首先要继承Thread
    //重写run方法

    @Override
    public void run() {
        //将需要执行的代码放在run方法当中
        for (int i = 0; i < 1000; i++) {
            if (i%2==0){//获取0-1000之间的偶数
                System.out.println("MyThread:"+i);
            }
        }
    }
}
    public static void main(String[] args) {
        //main函数相当于是主线程
        //创建子线程对象
        Mythread1 mythread1 = new Mythread1();
        //切记不能直接调用子线程中的run方法,那就相当于是直接调用方法,不是启动子线程
        //mythread1.run();
        mythread1.start();//启动子线程
        for (int i = 0; i < 1000; i++) {
            if(i%2==1){//main函数让其输出奇数,区分
                System.out.println("main:"+i);
            }
        }
    }

  在这个代码中,在main函数中创建了一个线程,一个线程执行的是输出偶数,一个是偶数。这段代码运用的方法有start()。                              

public class MyRunnnable1 implements Runnable{
    //Runnable接口,要重写run方法
    //run方法中写的是要执行的逻辑(代码)
    @Override
    public void run() {
        for (int i = 0; i <1000; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            //Thread.currentThread().getName()调用此线程的名字
        }
    }
}
    public static void main(String[] args) {
        //创建Runnable接口的对象
        MyRunnnable1 myRunnnable = new MyRunnnable1();//创建了一个线程中执行的任务
        Thread thread = new Thread(myRunnnable);//此处才是创建了一个线程
        thread.setName("Run线程");
        System.out.println(thread.getName());//获取线程的名字
        System.out.println(thread.getId());//获取线程的id
        thread.setPriority(10);//设置线程优先级,默认为 5,优先级的范围是(1-10)
        thread.start();//启动线程
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
        System.out.println(thread.getPriority());//获取线程优先级
        System.out.println(Thread.currentThread().getPriority());//获取当前线程的优先级
    }

这里的两段代码是创建线程的另外一种方式。实现的是Runnable接口。

public class MyThreadDemo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5000; i++) {
            if (i%50==0){
                MyThreadDemo.yield();//让出自己所占用的内存
            }
            System.out.println("MyThread:"+i);
        }
    }
}
    public static void main(String[] args) {
        MyThreadDemo myThreadDemo = new MyThreadDemo();
        myThreadDemo.start();
        for (int i = 0; i < 4000; i++) {
                System.out.println("main:"+i);
        }
    }

在这两段代码中用到了yeild()方法,这个方法的作用是让出自己所占用的线程。

public class MyThread extends Thread{
    //测试join
    @Override
    public void run() {
        for (int i = 0; i < 101; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.setName("xx");
        thread.join();//让其他线程等待此线程结束
        thread.start();

        MyThread thread1 = new MyThread();
        thread.setName("dd");
        thread1.start();
    }

这两段代码测试的是join的功能,等待调用该方法的线程执行完毕后再继续执行其他线程。

public class DeamonThread extends Thread {
    //测试守护线程
    @Override
    public void run() {
        int n = 0;
        while (true){
            System.out.println(Thread.currentThread().getName()+":"+n++);
        }
    }
}
    public static void main(String[] args) {
        DeamonThread deamonThread = new DeamonThread();
        deamonThread.setDaemon(true);//设置此线程为守护线程
        deamonThread.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }

这两段代码测试的是设置守护线程,守护线程就是只要当前JVM实例中尚存在 任何一个非守护线程没有结束,守护线程就全部工作。

以下两段是为了展示同步锁(sychronized)的功能:

public class PiaoThread extends Thread{
    static int num = 10;
    @Override
    public void run() {
        while(true){
            this.Buy();
        }
    }
    /*synchronized 修饰方法有两种情况:
    1.当修饰的是非静态方法时候,锁的对象是this
    2.当修饰的是静态的方法时,锁的对象是该类的class对象
    */

    public synchronized static void Buy(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+":"+num);
            num--;
        }
    }
}
    public static void main(String[] args) {
        PiaoThread piaoThread = new PiaoThread();
        piaoThread.setName("窗口一");
        piaoThread.start();
        PiaoThread piaoThread1 = new PiaoThread();
        piaoThread1.setName("窗口二");
        piaoThread1.start();

    }

设置同步锁之后,同步锁中的代码在同一时间只有一个线程可以进来。

如上两段代码的结果如下:

以下两段代码是上两段代码的Runnable接口的实现方式:

public class PiaoRun implements Runnable{
    int num = 10;
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.buy();
        }
    }
    public synchronized void buy(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+":"+num);
            num--;
        }
    }
}
    public static void main(String[] args) {
        PiaoRun piaoRun = new PiaoRun();
        Thread t1 = new Thread(piaoRun,"窗口一");
        Thread t2 = new Thread(piaoRun,"窗口二");
        t1.start();
        t2.start();
    }

 这两段代码就是Runnable接口的实现方式。

public class LockThread extends Thread{
    static int num = 10;
    static Lock lock = new ReentrantLock();
    /*ReentranLock 和 synchronized的区别
    *1.Lock 是一种Java代码底层的控制实现 ,synchronized是关键字,不依赖与Java代码,依赖与底层指令
    *2.ReentranLock只能对一段代码进行加锁,而synchronized既可以修饰方法,又可以修饰属性
    *3.Lock要自己主动加锁和释放锁,synchronized会自动加锁和释放锁
    * */
    @Override
    public void run() {
        while(true){
            //启动锁
            lock.lock();
            try {
                Thread.sleep(20);
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num--;
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();//关闭锁
            }
        }
    }
}
    public static void main(String[] args) {
        LockThread lockThread = new LockThread();
        Thread t1 = new Thread(lockThread,"窗口一");
        Thread t2 = new Thread(lockThread,"窗口二");
        t1.start();
        t2.start();
    }

以上两段代码是另外一种给代码加锁的方式。

public class SiThread extends Thread{
    Boolean b = null;
    static Object objA = new Object();
    static Object objB = new Object();
    public SiThread(Boolean b) {
        this.b = b;
    }
    @Override
    public void run() {
        if(b){
            synchronized(objA){
                System.out.println("if-objA");
                synchronized(objB){
                    System.out.println("if-objB");
                }
            }
        }else{
            synchronized (objB){
                System.out.println("else-objB");
                synchronized (objA){
                    System.out.println("else-objA");
                }
            }
        }
    }
}
    public static void main(String[] args) {
        SiThread siThread1 = new SiThread(true);
        SiThread siThread2 = new SiThread(false);
        siThread1.start();
        siThread2.start();
    }

以上的两段代码就是所谓的死锁,两段代码都在等待对方正在运行的线程,但是都不放手,就会造成死锁,同时程序也不会停止,结果如下:

public class WaitThread extends Thread{
    static Object object = new Object();
    static int num = 1;
    @Override
    public void run() {
        while (num<=100){
            synchronized (object){
               object.notify();//唤醒被wait的线程,优先优先级高的线程
                System.out.println(Thread.currentThread().getName()+":"+num);
                num++;
                try {
                    object.wait();//调用此方法会让当前正在执行的线程进入阻塞状态,并释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
    public static void main(String[] args) {
        WaitThread waitThread1 = new WaitThread();
        WaitThread waitThread2 = new WaitThread();
        waitThread1.start();
        waitThread2.start();
    }

以上这两段代码就是Object类中的notify和wait方法,一个是让线程处于阻塞状态,释放当前锁,一个是唤醒被wait的线程,如果有多个被wait的线程,首先唤醒优先级最高的一个。notifyall()此方法式用于唤醒全部被wait的方法。

注意:这三个方法必须处在同步代码块中。

四.多线程

4.1多线程的概念

多线程是指在一个程序中同时运行多个线程,每个线程都可以独立执行不同的任务或代码片段。与传统的单线程程序相比,多线程能够充分利用计算机的多核处理器和资源,并提高程序的并发性和响应性。在多线程环境下,每个线程都有自己的执行路径和执行状态,并且可以独立地进行操作和执行代码块。不同线程之间可以并行执行,从而实现同时处理多个任务、提高系统吞吐量和资源利用率。

4.2多线程的优缺点

多线程编程有以下优点和缺点:

优点:

  1. 提高程序的响应性:多线程可以将耗时的操作放在后台线程中执行,使得主线程可以及时响应用户的输入和请求,提升程序的交互性和用户体验。
  2. 提高系统资源利用率:通过并行执行多个任务,充分利用多核处理器或多CPU的计算能力,加快任务处理速度,提高系统的吞吐量和资源利用效率。
  3. 改善程序设计结构:使用多线程可以将复杂的任务拆分成多个独立的子任务,并发地进行处理,使程序的结构更清晰、模块化,便于维护和扩展。

缺点:

  1. 多线程编程复杂性高:线程之间的并发操作存在数据共享和同步等问题,需要合理地设计和管理线程之间的协作和数据同步,对开发人员要求较高,容易出现各种并发问题,如死锁、竞态条件等。
  2. 资源占用和调度开销:每个线程都会占用一定的内存空间和CPU调度开销,当线程数量过多时,会增加系统的负担,甚至导致系统资源的不足和性能下降。
  3. 可能引发安全问题:多线程程序在处理共享数据时需要正确地进行同步操作,否则可能会引发数据不一致、并发访问冲突等线程安全问题,增加了程序设计和调试的复杂性。

因此,在使用多线程编程时,需要仔细权衡其优点和缺点,并合理设计和管理多线程的使用,以避免潜在的问题和风险。

4.3线程同步

线程同步是指多个线程之间协调和同步彼此的行为,以保证它们按照预期的顺序执行和访问共享资源,避免出现并发操作导致的数据不一致、竞争条件等问题。

并发与并行:

 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

并发:在一个时间段内一次执行操作.例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行。

多线程同步:

 多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间要有先来后到;

同步就是排队+锁:

几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。

 4.4线程通信

线程通信是指多个线程之间进行信息交换、数据传递或协调操作的过程。在多线程编程中,不同的线程可能需要共享数据、协调执行顺序或进行协作任务,因此需要一种机制来实现线程之间的交互和通信。

常见的线程通信方式包括:

  1. 共享内存:多个线程通过访问共享内存区域来进行数据共享。线程可以读取和修改共享内存中的数据,但需要注意线程安全性和同步问题,以避免竞态条件或数据不一致的情况发生。

  2. 互斥锁(Mutex):通过互斥锁来保护共享资源的访问,确保同一时间只有一个线程能够操作共享资源。当一个线程获取到互斥锁时,其他线程需要等待,直到该线程释放锁。

  3. 条件变量(Condition Variable):线程可以使用条件变量进行等待和唤醒的操作。一个线程可以在满足特定条件之前等待,并在其他线程满足条件后发出信号通知等待线程继续执行。

  4. 信号量(Semaphore):通过信号量来控制对资源的访问数量。信号量可以用于限制同时访问某个资源的线程数量,或者在某个事件满足时发出通知。

  5. 消息队列(Message Queue):线程之间可以通过消息队列发送和接收消息。一个线程可以将消息放入队列中,另一个线程则从队列中获取消息进行处理。

  6. 管道(Pipe):管道是一种单向的通信机制,可用于实现两个线程之间的通信。一个线程将数据写入管道的写端口,另一个线程则从管道的读端口读取数据。

这些线程通信方式可以根据具体需求和场景进行选择和组合使用,以实现线程之间的协作和信息交换。在实际应用中,需要注意线程安全性、同步问题和避免死锁等相关技术细节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值