首页 > 编程语言 >JAVA多线程

JAVA多线程

时间:2024-09-23 21:47:30浏览次数:1  
标签:JAVA Thread void 线程 new ticket 多线程 public

一、并发和并行

       并发:同一时刻,多个指令在单个CPU上交替执行。
       并行:同一时刻,多个指令在多个CPU上同时执行。

二、多线程的实现方式

1. 继承Thread类的方式进行实现。

public class ThreadDemo {
    public static void main ( String[] args ) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 3.创建子类的对象,并启动线程
        t1.setName( "线程1" );
        t2.setName( "线程2" );
         
        t1.start();
        t2.start();
    }
}
public class MyThread extends Thread{
    // 1.定义一个类继承Thread
    public  void run() {
        // 2.重写run方法
        for (int i = 1; i <= 10; i++) {
            System.out.println( "Hello World " + getName() );
        }
    }
}

       运行后可以发现,两个线程交替执行,即为并发。

2. 实现Runnable接口的方式进行实现。

public class ThreadDemo {
    public static void main ( String[] args ) {
        MyRun mr = new MyRun( );
        // 3.创建类的对象
        
        Thread t1 = new Thread( mr );
        Thread t2 = new Thread( mr );
        // 4.创建Thread对象,并开启线程
        
        t1.setName( "线程1" );
        t2.setName( "线程2" );
        
        t1.start( );
        t2.start( );
    }
}
public class MyRun implements Runnable {
    // 1.自己定义一个类实现Runnable接口
    @Override
    public void run ( ) {
        // 2.重写run方法
        for ( int i = 1; i <= 10; i++ ) {
            System.out.println( "Hello World " + Thread.currentThread( ).getName( ) );
        }
    }
}

       实现Runnable接口的类无法直接调用getName()方法,但是可以通过currentThread()方法获取当前线程的对象,再调用getName()方法。

3. 利用Callable接口和Future接口方式实现

       第三种方法是对前面两种方法的补充,比前面两种方法多了一个返回值,获取多线程运行的结果。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class ThreadDemo {
    public static void main ( String[] args ) throws ExecutionException, InterruptedException {
        MyCall mc = new MyCall( );
        // 3.创建MyCall的对象(表示多线程要执行的任务)
        FutureTask< Integer > ft = new FutureTask<>( mc );
        // 4.创建FutureTask的对象(管理多线程的结果)
        
        Thread t1 = new Thread( ft );
        // 5.创建Thread的对象,并启动
        t1.start( );
        
        Integer res = ft.get( );
        System.out.println( res );
    }
}
import java.util.concurrent.Callable;

public class MyCall implements Callable {
    // 1.创建一个类实现Callable接口
    public Integer call ( ) {
        // 2.重写call方法
        Integer sum = 0;
        for ( int i = 1; i <= 10; i++ ) {
            sum += i;
        }
        return sum;
    }
}

4.多线程三种实现方式对比

image

三、多线程中常用的成员方法

  • String getName() :返回线程名称

  • void setName(String name) :设置线程名(构造方法也可以设置名字)

  • static Thread currentThread() :获取当前线程的对象

  • static void sleep(long time) :让线程休眠time毫秒,休眠时间一到,线程立即恢复可运行状态(就绪),注意不是运行状态

  • setPriority(int newPriority) :设置线程的优先级

  • final int getPriority() :获取线程的优先级

  • final void setDaemon(boolean on) :设置为守护线程,JAVA中的线程主要分为两类:用户线程和守护线程,在其他非守护线程执行完毕之后,守护线程就会陆续结束(不是直接结束)。

  • public static void yield() :礼让线程,让当前正在执行的线程暂停,但不阻塞,即为将线程从运行状态转化为就绪状态,让CPU重新调度。

  • public static void join() :插队线程,使调用当前方法的线程排在当前线程前面,执行完调用当前方法的线程再执行当前线程。

四、线程的调度

  • setPriority(int newPriority) :设置线程的优先级
  • final int getPriority() :获取线程的优先级
           线程的调度主要分为两种:抢占式调度(随机性)和非抢占式调度(所有线程轮流执行),其中对于强制式调度,线程的优先级越高,抢到CPU的概率越大,线程的优先级分为1~10,没有设置就默认为5。

五、线程的生命周期

       线程的生命周期中主要经过五种状态,新建、就绪、运行、阻塞和死亡。
image

六、线程安全问题

       当多线程操作同一个数据的时候会出现问题,如下:

public class MyThread extends Thread {
    static int ticket = 0;
    
    @Override
    public void run ( ) {
        while ( true ) {
            if ( ticket < 100 ) {
                try {
                    Thread.sleep( 100 );
                } catch ( InterruptedException e ) {
                    throw new RuntimeException( e );
                }
            }
            ticket++;
            System.out.println( getName( ) + "正在售卖第" + ticket + "张票!!!" );
        }
    }
}

1. synchronized

       保证线程安全可以使用synchronized关键字(自动锁)。
       使用synchronized的条件

  1. 必须有两个或两个以上的线程。
  2. 同一时间只有一个线程能够执行同步代码
  3. 多个线程想要同步时,必须共用同一把锁,即为synchronized(对象)括号内的对象必须是同一个对象。

       使用synchronized的过程

  1. 只有抢到锁的线程才能执行同步代码块,其余的线程即使抢到了CPU执行权,也只能等待锁的释放。
  2. 代码执行完毕或程序抛出异常都会释放锁,然后还未执行同步代码块的线程争抢锁,谁抢到谁就能运行同步代码块。

1.1 同步代码块

synchronized(对象){
        //可能会发生线程安全问题的代码
        }
//这里的对象可以是任意对象(但是必须是同一个对象),我们可以用 Object obj =  new Object()里面的obj放入括号中
public class MyThread extends Thread {
    static int ticket = 0;
    static Object obj = new Object( );
    
    @Override
    public void run ( ) {
        while ( true ) {
            synchronized ( MyThread.class ) {
                if ( ticket < 100 ) {
                    try {
                        Thread.sleep( 100 );
                    } catch ( InterruptedException e ) {
                        throw new RuntimeException( e );
                    }
                    ticket++;
                    System.out.println( getName( ) + "正在售卖第" + ticket + "张票!!!" );
                } else {
                    break;
                }
                
            }
        }
    }
}

1.2 同步方法

       即为把synchronized关键字加到方法上,格式修饰符 synchronized 返回值类型 方法名(方法参数){},特点:

  1. 同步方法是锁住方法里的所有代码
  2. 锁对象不能自己指定(JAVA已经规定好的),非静态方法的锁对象是this,静态方法的锁对象是当前类字节码文件对象。
public class MyRunnable implements Runnable {
    int ticket = 0;
    
    @Override
    public void run ( ) {
        while ( true ) {
            if ( method( ) ) break;
        }
    }
    
    /**
     * ctrl + alt + m选中代码块生成方法
     * @return
     */
    private synchronized boolean method ( ) {
        if ( ticket < 100 ) {
            try {
                Thread.sleep( 10 );
            } catch ( InterruptedException e ) {
                throw new RuntimeException( e );
            }
            ticket++;
            System.out.println( Thread.currentThread( ).getName( ) + "正在售卖第" + ticket + "张票!!!" );
        } else {
            return true;
        }
        return false;
    }
    
}

1.3 补充StringBuilder和StringBuffer

       StringBuilder的实例用于多个线程是不安全的,如果需要这样的同步则使用StringBuffer。

2. lock锁

       在JDK5以后提供了一个新的锁对象lock,实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作,lock中提供了获得锁和释放锁的方法(所以可以手动上锁和手动释放)。lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

public class MyThread extends Thread {
    static int ticket = 0;
    static Lock lock = new ReentrantLock( );
    
    @Override
    public void run ( ) {
        while ( true ) {
            lock.lock( );
            try {
                if ( ticket < 100 ) {
                    Thread.sleep( 1000 );
                    ticket++;
                    System.out.println( getName( ) + "正在售卖第" + ticket + "张票!!!" );
                } else {
                    break;
                }
            } catch ( InterruptedException e ) {
                throw new RuntimeException( e );
            } finally {
                // 在break之后也要关锁
                lock.unlock( );
            }
        }
    }
}

七、死锁

       在多线程编程中,防止多线程竞争共享资源导致数据错乱,我们会在操作共享资源之前加上互斥锁,只有成功拿到锁的线程,才能操作共享数据,获取不到锁的线程只能等待,直到锁被释放。
       死锁是指在一组进程中,每个进程都在等待其他进程释放资源,而这些资源又被这组进程中的其他进程占有,导致所有进程都无法向前推进的状态。这种情况下,进程无限期地等待一个永远不会发生的事件,从而系统资源得不到有效利用,甚至可能导致系统崩溃。
       产生死锁的条件:

  1. 互斥条件:进程对所分配到的资源具有排他性,即一次只有一个进程能使用。
  2. 请求与保持条件:继承至少已经持有一个资源但又提出新的资源请求,而该资源已被其他进程占有。
  3. 不可抢占条件:进程已经获得的资源在未使用完之前,不得被其他进程强行夺走。
  4. 循环等待条件:在发生死锁时,必然存在一个进程-资源的环形链。

模拟代码如下

public class MyThread extends Thread {
    static Object obj1 = new Object( );
    static Object obj2 = new Object( );
    
    @Override
    public void run ( ) {
        while ( true ) {
            if ( "a".equals( getName( ) ) ) {
                synchronized ( obj1 ) {
                    System.out.println( "a拿到了锁A" );
                    synchronized ( obj2 ) {
                        System.out.println( "a拿到了锁B,执行完一轮" );
                    }
                }
            } else if ( "b".equals( getName( ) ) ) {
                synchronized ( obj2 ) {
                    System.out.println( "b拿到了锁B" );
                    synchronized ( obj1 ) {
                        System.out.println( "b拿到锁A,执行完一轮" );
                    }
                }
            }
        }
    }
}

八、等待唤醒机制

       等待唤醒机制是多线程之间的协作机制,当一个线程执行完规定操作后,进入等待状态,等待其他线程执行完自己的指定代码后再来唤醒进入刚刚进入等待的线程。

1.直接实现

2.阻塞队列实现等待唤醒机制

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main ( String[] args ) {
        ArrayBlockingQueue<String>queue = new ArrayBlockingQueue<>( 1 );
        // 创建阻塞队列对象
        // ArrayBlockingQueue底层是数组,所以需要指定队列长度
        
        Cook cook = new Cook( queue );
        Foodie foodie = new Foodie( queue );
        // 创建线程对象,并传入阻塞队列
        
        cook.start();
        foodie.start();
        // 开启线程
    }
}
import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread {
    ArrayBlockingQueue< String > queue;
    
    public Cook ( ArrayBlockingQueue< String > queue ) {
        this.queue = queue;
    }
    
    @Override
    public void run ( ) {
        while ( true ) {
            try {
                queue.put( "面条" );
                // 不需要使用锁,队列内部有锁,锁被拿到后,队列会一直判断队列是否是满的,当队列
                // 不是满的的时候,会结束循环,然后释放锁
                System.out.println( "厨师制作了一碗面条" );
            } catch ( InterruptedException e ) {
                throw new RuntimeException( e );
            }
            
        }
    }
}
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    ArrayBlockingQueue queue;
    
    public Foodie ( ArrayBlockingQueue queue ) {
        this.queue = queue;
    }
    
    @Override
    public void run ( ) {
        while ( true ) {
            try {
                String food = (String) queue.take( );
                // take方法底层同样有锁,所以外面就不用加锁了,锁的嵌套容易导致死锁
                System.out.println( "客人吃了一碗面" );
            } catch ( InterruptedException e ) {
                throw new RuntimeException( e );
            }
        }
    }
}

       此时会发现输出的结果可能会出现连续两个厨师做面条,因为输出语句不在锁内,释放锁之后,可能会导致CPU被另一个线程抢到,所以可能会先输出客人吃面条再输出厨师做面条。
       锁的嵌套容易导致死锁,所以不能随便嵌套锁。

九、线程的状态

image
       在JAVA(虚拟机)中,线程有六个状态:NEW(新建)、RUNNABLE(就绪)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(计时等待)、TERMINATED(死亡)。没有运行状态,因为线程抢到CPU的执行权后,线程就会交给操作系统管理。

  1. 新建状态(NEW) -> 创建线程对象
  2. 就绪状态(RUNNABLE) -> start方法
  3. 阻塞状态(BLOCKED) -> 无法获得锁对象
  4. 等待状态(WAITING) -> wait方法
  5. 计时等待(TIMED_WAITING) -> sleep方法
  6. 结束状态(TERMINATED) -> 全部代码运行完毕

标签:JAVA,Thread,void,线程,new,ticket,多线程,public
From: https://www.cnblogs.com/againss/p/18424875

相关文章

  • JavaScript数据类型转换 数字类型转换
    除了在算术函数和表达式中,会自动进行number类型转换之外,可以使用Number(Value)进行显式的转换。 数字类型转换规则类型类型转换后undefinedNaNnull0true1false0string去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读......
  • Java基于Springboot+Vue的教学资源库设计和实现
    项目说明社会的进步,教育行业发展迅速,人们对教育越来越重视,在当今网络普及的情况下,教学模式也开始逐渐网络化,各大高校开始网络教学模式。本文研究的教学资源库系统基于Springboot框架,采用Java技术和MYSQL数据库设计开发。在系统的整个开发过程中,首先对系统进行了需......
  • Java基于Springboot+Vue的师生共评的作业管理系统设计和实现
    项目说明随着信息互联网信息的飞速发展,无纸化作业变成了一种趋势,针对这个问题开发一个专门适应师生作业交流形式的网站。本文介绍了师生共评的作业管理系统的开发全过程。通过分析企业对于师生共评的作业管理系统的需求,创建了一个计算机管理师生共评的作业管理系统的方......
  • Java基于Springboot+Vue的宠物咖啡馆平台设计和实现
    项目说明随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了基于SpringBoot的宠物咖啡馆平台的设计与实现的开发全过程。通过分析基于SpringBoot的宠物咖啡馆平台的设计与实现管理的不足,创建了一个计算机管理基于SpringBoo......
  • Java基础练习(每日五题)
    1,通过代码编写,输一段话:“今天是学习的第一天”packagejava4;publicclasspractise{publicstaticvoidmain(String[]args){System.out.println("今天是学习的第一天");}}2,拼接打印:“XXX:我已经学习了JavaX年,我期望的工资是XXX”packagejava4;......
  • JavaSE——数据类型与变量
    文章目录一、字面常量字面常量的分类:二、数据类型三、变量1、变量概念2.语法格式3.整形变量3.1整形变量3.2长整型变量3.3短整型变量3.4字节型变量4.浮点型变量4.1双精度浮点型变量4.2单精度浮点型5.字符型变量6.布尔型变量一、字面常量publicclass......
  • Java 学习路线图
    基础阶段学习重点:掌握Java基本语法,如变量、数据类型、运算符、控制流语句(条件判断、循环等)。理解面向对象编程的基本概念,包括类、对象、封装、继承、多态等。熟悉常用的Java类库,如字符串处理、数组操作、集合框架等。学习网站及资源:哔哩哔哩:有大量的Java基础教程视频,......
  • 什么是原子操作?Java如何实现原子操作?
    1.什么是原子操作?我们在学习MYSQL时就了解过原子性,即整个事务是不可分割的最小单位,事务中任何一个语句执行失败,所有已经执行成功的语句也要回滚,整个数据库状态要恢复到执行任务前的状态。Java中的原子性其实就是和数据库中说的相似,就是不可在分割,在我们的多线程里面就是相当于一......
  • JAVA基础之八-方法变量作用域和编译器
    本文主要讨论方法中变量作用域。不涉及类属性变量、静态变量、线程变量共享等。虽然知道某类变量的作用域非常重要,但是没有太多需要说的,因为许多东西是显而易见,不言自明。 在大部分情况下,或者在老一点版本中,java语法看起来都比较正常,或者说相对古典。但是随着JAVA版本的迭代,......
  • JavaScript 学习路线图
    基础阶段主要内容:掌握JavaScript的基本语法,如变量、数据类型(字符串、数字、布尔、对象、数组等)、运算符等。理解程序的控制流,包括条件语句(如if-else)、循环语句(如for、while)。学会使用函数来封装代码,理解函数的参数、返回值以及作用域等概念。学习网站:W3Schools:https://w......