java多线程创建与安全解决问题

    科技2025-01-17  5

    java多线程

    线程知识中最重要的是掌握线程的创建与使用及其线程安全问题(线程同步)


    1.线程基本概念

    1.1 程序、进程、线程

    程序:程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

    进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期 如:运行中的QQ,运行中的MP3播放器 程序是静态的,进程是动态的 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

    线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。 若一个进程同一时间并行执行多个线程,就是支持多线程的 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小 一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

    多线程程序的优点:

    提高应用程序的响应。对图形化界面更有意义,可增强用户体验。提高计算机系统CPU的利用率改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

    2.线程的创建与使用

    API中创建线程的两种方式

    JDK1.5之前创建新执行线程有两种方法: 方式一: 继承Thread类的方式 方式二:实现Runnable接口的方式

    2.1 方式一: 继承Thread类

    定义子类继承Thread类。子类中重写Thread类中的run方法。创建Thread子类对象,即创建了线程对象。调用线程对象start方法:启动线程,调用run方法。

    具体代码:

    public class Thread2 { public static void main(String[] args) { thread6 t1 = new thread6();//3.创建Thread类子类对象 t1.start();//4.调用线程对象start方法:启动线程,调用run方法 for(int j = 0;j < 100;j++) { System.out.println(j); } } } class thread6 extends Thread{// 1.定义子类继承Thread public void run() {//2.重写run方法 for(int i = 0;i < 100;i++) if(i % 2 ==0) { System.out.println("********" + i + "*****"); } } }

    注意点:

    如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。想要启动多线程,必须调用start方法。一个线程对象只能调用一次start() 方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

    2.2 方式二:实现Runnable接口

    定义子类,实现Runnable接口。子类中重写Runnable接口中的run方法。通过Thread类含参构造器创建线程对象。将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

    代码实现:

    public class Thread3 { public static void main(String[] args) { thread7 thread = new thread7();//3.创建线程对象 Thread t1 = new Thread(thread);//4.将Runnable接口的子类对象作为实际参数传递给Thread类构造器中 t1.start();//5.调用Thread中start()方法:开启线程 for(int j = 0;j < 100; j ++) { System.out.println(j); } } } class thread7 implements Runnable{//1.定义子类 实现Runnable接口 public void run() {//2.重写Runnable接口中run()方法 for(int i = 0;i < 100;i++) { System.out.println("******" + i + "*******"); } } }

    2.3 继承方式与实现方式的联系与区别

    区别:

    继承Thread:线程代码存放Thread子类run方法 中。 实现Runnable:线程代码存在接口的子类的run方法。

    实现方式的好处

    避免了单继承的局限性 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

    2.4 Thread类的相关方法

    void start(): 启动线程,并执行对象的run()方法 run(): 线程在被调度时执行的操作 String getName(): 返回线程的名称 void setName(String name):设置该线程名称 static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类 static void yield(): 线程让步暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法 join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止,低优先级的线程也可以获得执行 static void sleep(long millis): (指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。 抛出InterruptedException异常 stop(): 强制线程生命期结束,不推荐使用 boolean isAlive(): 返回boolean,判断线程是否还活着

    3.线程的生命周期

    线程的生命周期

    JDK中用Thread.State类定义了线程的几种状态 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

    新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态 死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    4.线程的同步(解决多线程安全问题)

    方式一:同步代码块 方式二:同步方法 方式三:Lock锁

    4.1 方式一:同步代码块

    同步代码块: synchronized (对象){ // 需要被同步的代码; }

    4.2 方式二:同步方法

    synchronized还可以放在方法声明中,表示整个方法为同步方法。 例如: public synchronized void show (String name){ ….

    4.3 方式三: Lock锁

    Lock(锁) class A{ private final ReentrantLock lock = new ReenTrantLock(); public void m(){ lock.lock(); try{ //保证线程安全的代码; } finally{ lock.unlock(); } } } 注意:如果同步代码有异常,要将unlock()写入finally语句块

    4.4 synchronized 与 Lock 的对比

    Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放Lock只有代码块锁,synchronized有代码块锁和方法锁使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)

    5.线程通信

    wait() 与 notify() 和 notifyAll()

    wait(): 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

    notify(): 唤醒正在排队等待同步资源的线程中优先级最高者结束等待 notifyAll (): 唤醒正在排队等待资源的所有线程结束等待.  这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。

    6.jdk 5.0 后新的两种创建线程方式

    6.1 .1新增方式一:实现Callable接口

    1.与使用Runnable相比, Callable功能更强大些 2.相比run()方法,可以有返回值 3.方法可以抛出异常 4.支持泛型的返回值 5.需要借助FutureTask类,比如获取返回结果

    6.1.2 新增方式一:实现Callable接口

    Future接口 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。 FutrueTask是Futrue接口的唯一的实现类 FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

    6.2 新增方式二:使用线程池

    背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。 好处: 1.提高响应速度(减少了创建新线程的时间) 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建) 3.便于线程管理

    6.3 线程池方法:

    corePoolSize: 核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时间后会终止…

    线程池相关API

    JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors ExecutorService:真正的线程池接口。常见子类hreadPoolExecutor void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable Future submit(Callable task):执行任务,有返回值,一般又来执行Callable void shutdown() :关闭连接池 Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。

    四种线程创建与使用代码

    class MyThread01 extends Thread {//方式1 @Override public void run() { System.out.println("-----MyThread01"); } } class MyThread02 implements Runnable {//方式2 public void run() { System.out.println("-----MyThread02"); } } class MyThread03 implements Callable<Integer> {//方式3 @Override public Integer call() throws Exception { System.out.println("-----MyThread03"); return 200; } } public class ThreadNew { public static void main(String[] args) { new MyThread01().start(); new Thread(new MyThread02()).start(); FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread03()); new Thread(futureTask).start(); try { Integer value = futureTask.get(); System.out.println(value); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
    Processed: 0.009, SQL: 8