1概念
- 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
- 在程序中线程指的是对于多个相互独立的代码片段重叠、并行执行,这样就可以提高程序的处理效率。
- Java的线程是通过java.lang.Thread类来实现的
- 当我们执行一个类的Main方法实质上是相当于启动了一个主线程
线程和进程的区别:
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
- 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小
- 以上两点说明线程上下文切换比进程上下文切换要快得多。
- 多进程: 在操作系统中能同时运行多个任务(程序)
- 多线程: 在同一应用程序(进程)中有多个顺序流同时执行
2.线程的创建和启动
线程的创建方式一
- 定义自己的线程类实现Runnable接口
- 创建的时候利用Thread的构造函数:
thread = new Thead(Runnable子类实例) - Runnable中只有一个方法:
public void run(); 在该方法中定义线程运行主体。 - 使用Runnable接口可以为多个线程提供共享的数据。
public class CreateThreadUseRunable implements Runnable { public void run() { for (int i = 0; i < 50; i++) { System.out.println("ChildThread----------------"+i); } } public static void main(String[] args) { Thread thread = new Thread(new CreateThreadUseRunable()); //启动线程 thread.start(); for (int i = 0; i < 50; i++) { System.out.println("MainThread----------------"+i); } } }
方式二
- 定义自己的线程类继承Thread,重写他的run方法
public class MyThread extends Thread{
public void run() {
//运行主体
}
} - 创建的时候直接实例化:
Thread thread = new MyThread();
public class CreateThreadUseExtend extends Thread { public void run() { for (int i = 0; i < 50; i++) { System.out.println("ChildThread----------------"+i); } } public static void main(String[] args) { Thread thread = new CreateThreadUseExtend(); //启动线程 thread.start(); for (int i = 0; i < 50; i++) { System.out.println("MainThread----------------"+i); } } }
线程的启动
- 通过调用Thead类的start()方法来启动一个线程
- 注意是调用start()方法来启动线程,而不是调用run方法来启动线程
3.线程的调度和优先级
线程的状态
新建、就绪、运行、阻塞、死亡五种状态
线程的基本常用方法:
- start()启动线程,新生状态到就绪
- stop()终止线程,由于安全问题JDK不建议使用,切换到死亡状态
- destroy()用于破坏该线程,但不作任何清除,不建议使用,切换到死亡状态
- isAlive()判断线程是否还“活”着,即线程是否还未终止。
- getPriority()获得线程的优先级数值
- setPriority()设置线程的优先级数值
- Thread.sleep()将当前线程睡眠指定毫秒数
- join()调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行,运行状态到阻塞状态。
public class TestJoin { public static void main(String[] args) { Thread thread = new Thread(new CreateThreadUseRunable()); //启动线程 thread.start(); for (int i = 0; i < 50; i++) { System.out.println("MainThread----------------"+i); if(i==5){ try { //你会发现当主线程执行到5的时候,合并子线程到主线程,等待子线程执行完毕后,再才执行主线程 thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
- yield()让出CPU,当前线程进入就绪队列等待调度,在就绪和运行状态中切换。
public class TestYield { public static void main(String[] args) { Thread thread = new Thread(new CreateThreadUseRunable()); //启动线程 thread.start(); for (int i = 0; i < 50; i++) { System.out.println("MainThread----------------"+i); //当主线程执行到5的时候,就进入阻塞状态,自动让出cpu给子线程调用 if(i==5){ Thread.yield(); } } } }
- wait()当前线程进入对象的wait pool,变为阻塞状态。
- notify()/notifyAll()唤醒对象的wait pool中的一个/所有等待线程,由阻塞状态变为就绪状态。
线程的优先级:
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5 - 获得或设置线程对象的优先级
int getPriority();
void setPriority(int newPriority);
public class TestPriority { public static void main(String[] args) { Thread t1 = new Thread(new Thread1()); Thread t2 = new Thread(new Thread2()); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); } } class Thread1 implements Runnable{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("Thread1--------------"+i); } } } class Thread2 implements Runnable{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("Thread2--------------"+i); } } }
4.线程同步
- 同步是指多个线程希望同时访问同一资源时,确保在任意时刻始终只被一个线程访问,以保证数据的完整性
- 为什么需要同步
如一个银行账户中剩余余额为1000元
现在用户到ATM机中取钱,首先查询余额1000,取500元,余额变为500
在用户取钱的同时账户收到一笔新的入款500,首先查询余额1000,加500,余额变为1500
当ATM机执行完毕后就把余额500赋值给了账户余额,此时余额就变为了500
出现了问题,实质余额应该还是1000
- 用代码模拟一下
public class BankMoneyTest implements Runnable{ //最初余额 public static double remaing =10000; public void run() { //为了模拟出问题我执行了多次的取钱和入款操作 for(int i=0;i<10000;i++){ if(i%2==0){ add(100); }else{ reduce(100); } } } /** * 模拟入款 * @param value */ public void add(double value){ double temp = remaing; remaing = getRemain()+value; System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing); } /** * 模拟取钱 * @param value */ public void reduce(double value){ double temp = remaing; remaing = getRemain()-value; System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing); } /** * 获取余额 * @return */ public double getRemain(){ return remaing; } public static void main(String[] args) { BankMoneyTest test = new BankMoneyTest(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.start(); t2.start(); } }
执行结果不是固定的第一种结果(最后几行):
当前余额10000.0,取款100.0余额为9900.0 当前余额9900.0,入款100.0余额为10000.0 当前余额10000.0,取款100.0余额为9900.0 当前余额9900.0,入款100.0余额为10000.0 当前余额10000.0,取款100.0余额为9900.0 当前余额9900.0,入款100.0余额为10000.0 当前余额10000.0,取款100.0余额为9900.0 当前余额9900.0,入款100.0余额为10000.0 当前余额10000.0,取款100.0余额为9900.0
执行结果不是固定的第二种结果(最后几行):
当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0 当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0 当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0 当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0 当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0 当前余额9000.0,入款100.0余额为9100.0 当前余额9100.0,取款100.0余额为9000.0
执行结果不是固定的第三种结果(最后几行):
当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0 当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0 当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0 当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0 当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0 当前余额10400.0,入款100.0余额为10500.0 当前余额10500.0,取款100.0余额为10400.0
由此发现结果都出现了潜在性的问题。
怎么解决这种问题?就是使用同步,加锁
同步方法:
- 同步方法,使用synchronized关键字声明的方法叫同步方法
public synchronized void method(){} - 同步方法表示在任意时刻都只能执行类对象中多个同步方法中的一个,只有在对象当前执行的同步方法结束后,才能访问该对象的其他同步方法。
- 其实质是确保每个同步方法在执行时相对于其他同步方法是独占对象实例的访问权限,他相当于给对象加了一把锁,当一开始执行同步方法就会获得锁,当方法执行完毕后才会释放锁,其他同步方法才会可以访问。
把上面的方法加锁:
public class BankMoneyTest2 implements Runnable{ //最初余额 public static double remaing =10000; public void run() { for(int i=0;i<10000;i++){ if(i%2==0){ add(100); }else{ reduce(100); } } } /** * 模拟入款 * @param value */ public synchronized void add(double value){ double temp = remaing; remaing = getRemain()+value; System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing); } /** * 模拟取钱 * @param value */ public synchronized void reduce(double value){ double temp = remaing; remaing = getRemain()-value; System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing); } /** * 获取余额 * @return */ public synchronized double getRemain(){ return remaing; } public static void main(String[] args) { BankMoneyTest2 test = new BankMoneyTest2(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.start(); t2.start(); } }
执行结果始终是:
当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0
你会发现在执行这个方法时就不会和逻辑发生冲突
同步代码块:
- 同步代码块,使用synchronized关键字来指定程序中的语句或代码块
synchronized(obj){} - 这种方法更强大,它表示对某个指定对象将获得它所包括的语句或代码块的执行权限,而不像同步方法仅是为包含代码的对象获取锁权限。
- 如果被锁的代码块引用了其他方法,那么对应的方法也相当于加上了这个对象的锁
- 也就是说对给定对象执行同步代码块的时候,就不能执行该对象同步的其他代码块或方法
采用同步代码块修改以上例子:
public class BankMoneyTest3 implements Runnable{ //最初余额 public static double remaing =10000; public void run() { for(int i=0;i<10000;i++){ if(i%2==0){ synchronized (this) { add(100); } }else{ synchronized (this) { reduce(100); } } } } /** * 模拟入款 * @param value */ public void add(double value){ double temp = remaing; remaing = getRemain()+value; System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing); } /** * 模拟取钱 * @param value */ public void reduce(double value){ double temp = remaing; remaing = getRemain()-value; System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing); } /** * 获取余额 * @return */ public double getRemain(){ return remaing; } public static void main(String[] args) { BankMoneyTest3 test = new BankMoneyTest3(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.start(); t2.start(); } }
执行结果始终是:
当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0 当前余额10000.0,入款100.0余额为10100.0 当前余额10100.0,取款100.0余额为10000.0
你会发现在执行这个方法时也不会和逻辑发生冲突
死锁:
- 死锁的产生是指两个线程对访问资源的相互依赖而发生冲突产生的。
- 如某个线程执行给定对象obj1的同步代码块时,而在这个同步代码块中包含另一个对象obj2的同步代码块,而obj2中的同步代码块中又依赖obj1的同步代码块。这样就产生了死锁
用代码来模拟一个死锁
public class DeadSync implements Runnable{ public boolean flag ; public static Object o1 = new Object(); public static Object o2 = new Object(); public void run() { System.out.println("flag=" + flag); if(flag) { synchronized(o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized(o2) { System.out.println(flag); } } } if(!flag) { synchronized(o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized(o1) { System.out.println(flag); } } } } public static void main(String[] args) { DeadSync td1 = new DeadSync(); DeadSync td2 = new DeadSync(); td1.flag = true; td2.flag = false; Thread t1 = new Thread(td1); Thread t2 = new Thread(td2); t1.start(); t2.start(); } }
相关推荐
### Java线程入门知识点详解 #### 一、Java线程基础知识概述 **1.1 什么是线程?** 线程是程序执行流的最小单元,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Java中...
以下是对Java线程入门的详细讲解: 1. **线程的基本概念**: - 线程是操作系统分配CPU时间的基本单元,一个进程可以包含一个或多个线程。 - 在Java中,每个应用程序至少有一个主线程,它负责启动和控制其他线程。...
Java线程是多任务编程的重要概念,特别是在Java这种支持并发执行的编程语言中。线程允许程序中的不同部分并行运行,从而提高了程序的效率和响应性。在Java中,线程可以分为两种类型:用户线程(由应用程序创建)和...
本文将基于提供的"Java线程入门"资料,深入探讨Java线程编程的基本概念、创建方法以及常见操作,帮助初学者建立起扎实的线程知识体系。 一、线程基础 1. 线程定义:线程是操作系统分配CPU时间的基本单元,一个进程...
### Java线程入门知识点详解 #### 一、Java线程概览 - **目标读者**:本教程面向那些已经熟练掌握Java语言基本应用但对多线程编程尚不熟悉的程序员。 - **主要内容**:本教程将从零开始介绍Java线程的基础知识,...
Java线程入门,学习Java线程的好帮手
本书《Java线程入门》显然为初学者提供了一个良好的起点,适合对编程有一定了解的人群。书中可能涵盖了以下几个核心知识点: 1. **线程的概念与分类**:介绍什么是线程,线程如何在操作系统中运行,以及Java中线程...
### Java线程入门大全知识点详解 #### 一、线程基础概述 - **定义与特点:** - **线程**是一种比进程更细粒度的执行单元,它允许在一个进程中并发执行多个任务。 - **多线程**能够提高程序的效率和响应速度,...
本教程旨在为初学者提供一个良好的Java线程入门指导,通过详细的开发环境讲解,帮助你快速上手。 首先,了解Java开发环境是学习任何编程语言的基础。Java的开发环境主要由Java Development Kit (JDK) 组成,它包含...
本教程中的"java线程入门教程,涉及线程基本知识,显浅易懂."文件将详细讲解这些概念,并通过示例代码帮助读者理解和实践。"1-2论坛"可能包含了一些讨论和问题解答,可以帮助你解决学习过程中遇到的困惑。通过学习这...
本教程将深入浅出地介绍Java线程的基础知识,帮助初学者快速入门。 一、线程的创建 1. 继承Thread类:创建一个新的类,该类继承自Thread类,然后重写其run()方法。实例化这个子类对象后,调用start()方法即可启动...
Java多线程是Java编程中的核心概念...通过阅读《Java线程入门.pdf》这样的学习资料,结合实际编程实践,可以逐步掌握这门技术。在学习过程中,参考《E书说明.txt》中的指导,可以更好地利用提供的资源,加速学习进程。
### Java多线程入门知识点详解 #### 一、引言 多线程是现代软件开发中的重要技术之一,尤其在Java编程语言中占有极其重要的地位。对于任何一位Java程序员而言,掌握多线程的基本概念和技术是非常必要的。本文将...
尤其适合于没有 Java 线程开发经验的朋友。自己动手敲出本资源解压缩后的 chapter02 到 chanpter18,并运行,用心体会一下。也许在运行完 chapter18 的源码之后,你已经是一个对线程有着深刻理解的线程编程高手了! ...