Java线程详解(一)

简介: Java线程详解

Java线程详解


程序、进程、线程的概念


程序、进程、线程的概念

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

进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

如:运行中的QQ,运行中的MP3播放器

程序是静态的,进程是动态的

线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

若一个程序可同一时间执行多个线程,就是支持多线程的


Java中多线程的创建和使用


一、定义线程


1、继承java.lang.Thread类。


1 ) 定义子类继承Thread类。

2) 子类中重写Thread类中的run方法。

3) 创建Thread子类对象,即创建了线程对象。

4) 调用线程对象start方法:启动线程,调用run方法。


2、实现java.lang.Runnable接口。


1)定义子类,实现Runnable接口。

2)子类中重写Runnable接口中的run方法。

3)通过Thread类含参构造器创建线程对象。

4)将Runnable接口的子类对象作为实际参数传递给

Thread类的构造方法中。


5)调用Thread类的start方法:开启线程,调用

Runnable子类接口的run方法。


2.1、继承方式和实现方式的联系与区别


区别


继承Thread: 线程代码存放Thread子类run方法中。

实现Runnable:线程代码存在接口的子类的run方法。


实现方法的好处


避免了单继承的局限性

多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。


3.通过Callable和Future创建线程


通过Callable和Future创建线程的具体步骤和具体代码如下:


• 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

• 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

• 使用FutureTask对象作为Thread对象的target创建并启动新线程。

• 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中,Callable接口(也只有一个方法)

4. 通过线程池来创建线程

① new ThreadPoolExecutor(…);

② 创建任务Task implements Callable,重写run()方法;

③ 通过线程池的execute()或submit()将任务command传入线程池;

④ 获取返回值:


实现Callable接口,重写call()方法

class CallableImpl implements Callable

定义线程池

ThreadPoolExecutor executor

利用submit()方法提交任务

Future<?> future = executor.submit(new CallableImpl());

利用FutureTask类get()方法获取返回值

res = task.get();

这里future申明为Future对象,但是它是由FutureTask实现的,也可以直接申明为FutureTask future:

6)关闭线程池 .shutdown();


二、实例化线程


1、如果是扩展java.lang.Thread类的线程,则直接new即可。


2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:


  Thread(Runnable target)
  Thread(Runnable target, String name)
  Thread(ThreadGroup group, Runnable target)
  Thread(ThreadGroup group, Runnable target, String name)
  Thread(ThreadGroup group, Runnable target, String name, long stackSize)



三、Thread类的有关方法


1.start():启动线程并执行相应的run()方法

2.run():子线程要执行的代码放入run()方法中

3.currentThread():静态的,调取当前的线程

4.getName():获取此线程的名字

5.setName():设置此线程的名字

6.yield():调用此方法的线程释放当前CPU的执行权

暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法


7.join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,

当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止

低优先级的线程也可以获得执行

8.isAlive():判断当前线程是否还存活

9.sleep(long l):显式的让当前线程睡眠l毫秒

令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。

抛出InterruptedException异常


10.线程通信:wait() notify() notifyAll()


设置线程的优先级

getPriority() :返回线程优先值

setPriority(int newPriority) :改变线程的优先级


四、代码实现


继承于Thread类


//模拟火车站售票窗口,开启三个窗口售票,总票数为100张
//存在线程的安全问题(之后例子用线程同步解决)
class Window extends Thread {
    //静态变量,保证票数统一
    static int ticket = 100;
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "售票,票号为:"
                        + ticket--);
            } else {
                break;
            }
        }
    }
}
public class TestWindow {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        w1.start();
        w2.start();
        w3.start();  
    }
}


实现Runnable接口


//使用实现Runnable接口的方式,售票
/*
 * 此程序存在线程的安全问题:打印车票时,会出现重票、错票
 */
class Window1 implements Runnable {
    int ticket = 100;
    public void run() {
        while (true) {
            if (ticket > 0) {
                //线程睡眠10秒,暴露重票、错票问题
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "售票,票号为:"
                        + ticket--);
            } else {
                break;
            }
        }
    }
}
public class TestWindow1 {
    public static void main(String[] args) {
        Window1 w = new Window1();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}


线程的调度

调度策略

时间片:


1.png


抢占式:高优先级的线程抢占CPU

Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略


线程的优先级


线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:

static int MAX_PRIORITY

线程可以具有的最高优先级。

static int MIN_PRIORITY

线程可以具有的最低优先级。

static int NORM_PRIORITY

分配给线程的默认优先级。

涉及的方法:

getPriority() :返回线程优先值

setPriority(int newPriority) :改变线程的优先级

线程创建时继承父线程的优先级


1.线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。

在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。

注意:线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。


2.当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:


一是选择一个线程运行,直到它阻塞或者运行完成为止。

二是时间分片,为池内的每个线程提供均等的运行机会。

设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:

Thread t = new MyThread();

t.setPriority(8);

t.start();

3.线程优先级为110之间的正整数,JVM从不会改变一个线程的优先级。然而,110之间的值是没有保证的。一些JVM可能不能识别10个不同的值,

而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。


代码实现:

//创建多线程的方式一:继承于Thread类
class PrintNum extends Thread{
   public void run(){
      //子线程执行的代码
      for(int i = 1;i <= 100;i++){
       System.out.println(Thread.currentThread().getName() + ":" + i);
      }
   }
   public PrintNum(String name){
      super(name);
   }
}
public class TestThread {
   public static void main(String[] args) {
      PrintNum p1 = new PrintNum("线程1");
      PrintNum p2 = new PrintNum("线程2");
       //优先级高的获取CPU执行几率高
      p1.setPriority(Thread.MAX_PRIORITY);//10
      p2.setPriority(Thread.MIN_PRIORITY);//1
      p1.start();
      p2.start();
   }
}



线程的生命周期


2.png


一、线程状态


线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:


1、新建状态:线程对象已经创建,还没有在其上调用start()方法。


2、就绪状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。


3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。


4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。


5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。


二、阻止线程执行


1、睡眠


Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。


注意:


1、线程睡眠是帮助所有线程获得运行机会的最好方法。


2、线程睡眠到期自动苏醒,并返回到就绪状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。


3、sleep()是静态方法,只能控制当前正在运行的线程。


下面给个代码实现:


**


//完成1-100之间自然数的输出。
class PrintNum extends Thread {
    public void run() {
     // 子线程执行的代码
     for (int i = 0; i <= 100; i++) {
       if ((i) % 10 == 0) {
       System.out.println(Thread.currentThread().getName() + ":-------------" + i);
      }
      System.out.print(Thread.currentThread().getName() + ":" + i);
       try {
        Thread.sleep(1);
        System.out.print("    线程睡眠1毫秒!\n");
        }catch (InterruptedException e) {
         e.printStackTrace();
         }
       }
    }
    public PrintNum(String name) {
        super(name);
    }
}
public class TestThread {
   public static void main(String[] args) {
     PrintNum p1 = new PrintNum("线程");
     p1.start();
   }
}


**

结果:


线程:-------------0
线程:0    线程睡眠1毫秒!
线程:1    线程睡眠1毫秒!
线程:2    线程睡眠1毫秒!
线程:3    线程睡眠1毫秒!
线程:4    线程睡眠1毫秒!
线程:5    线程睡眠1毫秒!
线程:6    线程睡眠1毫秒!
线程:7    线程睡眠1毫秒!
线程:8    线程睡眠1毫秒!
线程:9    线程睡眠1毫秒!
线程:-------------10
线程:10    线程睡眠1毫秒!
线程:11    线程睡眠1毫秒!
线程:12    线程睡眠1毫秒!
线程:13    线程睡眠1毫秒!
线程:14    线程睡眠1毫秒!
线程:15    线程睡眠1毫秒!
线程:16    线程睡眠1毫秒!
线程:17    线程睡眠1毫秒!
线程:18    线程睡眠1毫秒!
线程:19    线程睡眠1毫秒!
线程:-------------20
线程:20    线程睡眠1毫秒!
线程:21    线程睡眠1毫秒!
线程:22    线程睡眠1毫秒!
线程:23    线程睡眠1毫秒!
线程:24    线程睡眠1毫秒!
线程:25    线程睡眠1毫秒!
线程:26    线程睡眠1毫秒!
线程:27    线程睡眠1毫秒!
线程:28    线程睡眠1毫秒!
线程:29    线程睡眠1毫秒!
线程:-------------30
线程:30    线程睡眠1毫秒!
线程:31    线程睡眠1毫秒!
线程:32    线程睡眠1毫秒!
线程:33    线程睡眠1毫秒!
线程:34    线程睡眠1毫秒!
线程:35    线程睡眠1毫秒!
线程:36    线程睡眠1毫秒!
线程:37    线程睡眠1毫秒!
线程:38    线程睡眠1毫秒!
线程:39    线程睡眠1毫秒!
线程:-------------40
线程:40    线程睡眠1毫秒!
线程:41    线程睡眠1毫秒!
线程:42    线程睡眠1毫秒!
线程:43    线程睡眠1毫秒!
线程:44    线程睡眠1毫秒!
线程:45    线程睡眠1毫秒!
线程:46    线程睡眠1毫秒!
线程:47    线程睡眠1毫秒!
线程:48    线程睡眠1毫秒!
线程:49    线程睡眠1毫秒!
线程:-------------50
线程:50    线程睡眠1毫秒!
线程:51    线程睡眠1毫秒!
线程:52    线程睡眠1毫秒!
线程:53    线程睡眠1毫秒!
线程:54    线程睡眠1毫秒!
线程:55    线程睡眠1毫秒!
线程:56    线程睡眠1毫秒!
线程:57    线程睡眠1毫秒!
线程:58    线程睡眠1毫秒!
线程:59    线程睡眠1毫秒!
线程:-------------60
线程:60    线程睡眠1毫秒!
线程:61    线程睡眠1毫秒!
线程:62    线程睡眠1毫秒!
线程:63    线程睡眠1毫秒!
线程:64    线程睡眠1毫秒!
线程:65    线程睡眠1毫秒!
线程:66    线程睡眠1毫秒!
线程:67    线程睡眠1毫秒!
线程:68    线程睡眠1毫秒!
线程:69    线程睡眠1毫秒!
线程:-------------70
线程:70    线程睡眠1毫秒!
线程:71    线程睡眠1毫秒!
线程:72    线程睡眠1毫秒!
线程:73    线程睡眠1毫秒!
线程:74    线程睡眠1毫秒!
线程:75    线程睡眠1毫秒!
线程:76    线程睡眠1毫秒!
线程:77    线程睡眠1毫秒!
线程:78    线程睡眠1毫秒!
线程:79    线程睡眠1毫秒!
线程:-------------80
线程:80    线程睡眠1毫秒!
线程:81    线程睡眠1毫秒!
线程:82    线程睡眠1毫秒!
线程:83    线程睡眠1毫秒!
线程:84    线程睡眠1毫秒!
线程:85    线程睡眠1毫秒!
线程:86    线程睡眠1毫秒!
线程:87    线程睡眠1毫秒!
线程:88    线程睡眠1毫秒!
线程:89    线程睡眠1毫秒!
线程:-------------90
线程:90    线程睡眠1毫秒!
线程:91    线程睡眠1毫秒!
线程:92    线程睡眠1毫秒!
线程:93    线程睡眠1毫秒!
线程:94    线程睡眠1毫秒!
线程:95    线程睡眠1毫秒!
线程:96    线程睡眠1毫秒!
线程:97    线程睡眠1毫秒!
线程:98    线程睡眠1毫秒!
线程:99    线程睡眠1毫秒!
线程:-------------100
线程:100    线程睡眠1毫秒!


2、线程让步yield()

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。

因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()

达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态

转到就绪状态,但有可能没有效果。


3、join()方法


Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。例如:


    Thread t = new MyThread();
    t.start();
    t.join();


另外,join()方法还有带超时限制的重载版本。例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。

线程的加入join()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。


目录
相关文章
|
13天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
13天前
|
算法 Java 开发者
Java中的多线程编程:概念、实现与性能优化
【4月更文挑战第9天】在Java编程中,多线程是一种强大的工具,它允许开发者创建并发执行的程序,提高系统的响应性和吞吐量。本文将深入探讨Java多线程的核心概念,包括线程的生命周期、线程同步机制以及线程池的使用。接着,我们将展示如何通过继承Thread类和实现Runnable接口来创建线程,并讨论各自的优缺点。此外,文章还将介绍高级主题,如死锁的预防、避免和检测,以及如何使用并发集合和原子变量来提高多线程程序的性能和安全性。最后,我们将提供一些实用的性能优化技巧,帮助开发者编写出更高效、更稳定的多线程应用程序。
|
11天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
4天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
4天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
5天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
5天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
6天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
6天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
13 1
|
6天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
6 0

热门文章

最新文章