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()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。


目录
相关文章
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
15天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
2天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
15 9
|
5天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
2天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
5天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
15 3
|
4天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
5天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
14 1
|
5天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
6天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
28 1