昨天熬了个通宵,看了一晚上的视频,把java 的多线程相关技术重新复习了一遍,下面对学习过程中遇到的知识点进行下总结。
首先我们先来了解一下进程、线程、并发执行的概念:
进程是指:一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。
在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。
线程的状态
1、线程共有下面4种状态:
- 新建状态(New):新创建了一个线程对象,当你用new创建一个线程时,该线程尚未运行。
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
a. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
b. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM把该线程放入锁。
c. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):
a. 由于run方法的正常退出而自然死亡;
b. 没有捕获到的异常事件终止了run方法的执行,从而导致线程突然死亡
2、若要确定某个线程当前是否活着,可以使用 isAlive 方法。
如果该线程是可运行线程或者被中断线程,那么该方法返回true;如果该线程仍然是个新建线程,或者该线程是个死线程,那么该方法返回false
3、注意:你无法确定一个活线程究竟是处于可运行状态还是被中断状态,也无法确定一个可运行线程是否正处在运行之中。另外,你也无法对尚未成为可运行的线程与已经死掉的线程进行区分。
4、线程必须退出中断状态,并且返回到可运行状态,方法是使用与进入中断状态相反的过程:
a. 如果线程已经处于睡眠状态,就必须经过规定的毫秒数
b. 如果线程正在等待输入或输出操作完成,那么必须等待该操作完成
c. 如果线程调用了wait方法,那么另外一个线程必须调用notifyAll或者notify方法
d. 如果线程正在等待另一个线程拥有的对象锁,那么另一个线程必须放弃该锁的所有权
5、下面这副图很好的反映了线程在不同情况下的状态变化。
了解完多线程的相关知识,下面来介绍一下在java中多线程的实现方式
JAVA多线程实现方式
JAVA多线程实现方式主要有以下三种:
1、继承Thread类
2、实现Runnable接口
3、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。其中最常用的也是前两种实现方式。下面对前两种实现方式分别做下讲解。
1、继承Thread类实现多线程
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
例如:
package thread; public class MyThread extends Thread { public void run() { System.out.println("run()方法正在执行"); } }
启动线程方式如下:
MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start();
2、实现Runnable接口方式实现多线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口。
方法如下:
package thread; class OtherClass{ public void print(String str){ System.out.println(str); } } public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("run()正在执行"); } }
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例。
具体方法如下:
MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() { if (target != null) { target.run(); } }
学会了线程的创建方式,下面我们在举几个线程状态转换的例子
3、线程状态的转换实例
package thread; public class ThreadStateDemo extends Thread { Thread thread; public ThreadStateDemo() { thread = new Thread(this); System.out.println("创建一个线程:thread"); thread.start(); } public void run() { try { System.out.println("线程thread正在运行!"); System.out.println("线程thread睡眠3秒中...!"); Thread.sleep(3000); //静态方法,使当前正在执行的线程睡眠3秒 System.out.println("线程thread在睡眠后重新运行!"); }catch(InterruptedException e) { System.out.println("线程被中断"); } } public static void main(String[] args) { new ThreadStateDemo(); System.out.println("主线程main结束!"); } }
【运行结果】如下:
创建一个线程:thread 主线程main结束! 线程thread正在运行! 线程thread睡眠3秒中...! 线程thread在睡眠后重新运行!
终止线程的实例:
package thread; public class ThreadShutDownDemo { public static void main(String args[]) { Runner runner = new Runner(); Thread thread = new Thread(runner); thread.start(); for(int i=0;i<10;i++) { if(i%10!=0) { System.out.println("在主线程中 i=" + i); } } System.out.println("主线程main结束"); //通知线程结束 runner.shutDown(); } } class Runner implements Runnable { //控制线程是否结束 private boolean flag = true; public void run() { int i=0; while(flag == true) { System.out.println("在子线程中 i=" + i++); } System.out.println("子线程结束"); } //设置线程结束标志 public void shutDown() { flag = false; } }
【运行结果】如下:
在主线程中 i=1 在子线程中 i=0 在主线程中 i=2 在子线程中 i=1 在主线程中 i=3 在子线程中 i=2 在主线程中 i=4 在子线程中 i=3 在主线程中 i=5 在子线程中 i=4 在主线程中 i=6 在主线程中 i=7 在主线程中 i=8 在主线程中 i=9 主线程main结束 在子线程中 i=5 子线程结束
join()方法实例:
package thread; public class TheadJoinDemo { public static void main(String[] args) { Runner2 r = new Runner2(); Thread t = new Thread(r); t.start(); try { t.join();//主线程main将中断,直到线程t执行完毕 }catch(InterruptedException e) { } for(int i=0;i<5;i++) { System.out.println("主线程:" + i); } } } class Runner2 implements Runnable { public void run() { for(int i=0;i<10;i++) { System.out.println("子线程:" + i); } } }
【运行结果】如下:
子线程:0 子线程:1 子线程:2 子线程:3 子线程:4 子线程:5 子线程:6 子线程:7 子线程:8 子线程:9 主线程:0 主线程:1 主线程:2 主线程:3 主线程:4
介绍完以上几个实例,我们下面对sleep()、wait()、yeid()、join()几个方法进行下区别总结
sleep方法与wait方法的区别:
- sleep方法是静态方法,wait方法是非静态方法。
- sleep方法在时间到后会自己“醒来”,但wait不能,必须由其它线程通过notify(All)方法让它“醒来”。
- sleep方法通常用在不需要等待资源情况下的阻塞,像等待线程、数据库连接的情况一般用wait。
sleep/wait与yeld方法的区别:
调用sleep或wait方法后,线程即进入block状态,而调用yeld方法后,线程进入runnable状态。
wait与join方法的区别:
- wait方法体现了线程之间的互斥关系,而join方法体现了线程之间的同步关系。
- wait方法必须由其它线程来解锁,而join方法不需要,只要被等待线程执行完毕,当前线程自动变为就绪。
- join方法的一个用途就是让子线程在完成业务逻辑执行之前,主线程一直等待直到所有子线程执行完毕。
线程的同步问题
在实际应用中,我们通常会遇到多线程安全问题。多线程安全问题:当多条语句在操作同一线程共享数据是,一个线程对多条语句只执行了一部分,还没有执行完, 此时另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java 对于多线程的安全提供了专业的解决方式。
线程的同步是保证多线程安全访问竞争资源的一种手段,对于同步,在具体的Java代码中需要完成一下两个操作:
synchronized(对象){ 代码块 ... }
同步的前提:
1、必须要有两个或者两个以上的线程运行;
2、必须是多个线程使用同一个锁;
好处:解决了多线程的安全问题;
弊端:多个线程需要判断锁,较为消耗资源;
注意: 非静态同步函数的对象锁为this,静态同步函数所使用的锁是该方法所在类的字节码文件对象,即类名.class,静态方法里的同步锁都是使用的是类的字节码对象。
//静态同步函数锁 public static synchronized void show(){ ticket++; System.out.println(Thread.currentThread().getName()+"runtime..."+ticket--); }
下面来例举一个线程同步的例子:(同步方法)
package thread; public class SynchronizedThread { public static void main(String[] args) { User u = new User("王某", 100); MyThread2 t1 = new MyThread2("线程A", u, 10); MyThread2 t2 = new MyThread2("线程B", u, -50); MyThread2 t3 = new MyThread2("线程C", u, -60); MyThread2 t4 = new MyThread2("线程D", u, -40); MyThread2 t5 = new MyThread2("线程E", u, 20); MyThread2 t6 = new MyThread2("线程F", u, 28); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread2 extends Thread { private User u; private int y = 0; MyThread2(String name, User u, int y) { super(name); this.u = u; this.y = y; } public void run() { u.oper(y); } } class User { private String code; private int cash; User(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * @param x 添加x万元 */ public synchronized void oper(int x) { try { Thread.sleep(10L); this.cash += x; System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }
【运行结果】如下:
线程A运行结束,增加“10”,当前用户账户余额为:110 线程F运行结束,增加“28”,当前用户账户余额为:138 线程E运行结束,增加“20”,当前用户账户余额为:158 线程D运行结束,增加“-40”,当前用户账户余额为:118 线程C运行结束,增加“-60”,当前用户账户余额为:58 线程B运行结束,增加“-50”,当前用户账户余额为:8
下面是线程不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后再运行程序
【运行结果】如下:
线程F运行结束,增加“28”,当前用户账户余额为:128 线程D运行结束,增加“-40”,当前用户账户余额为:88 线程B运行结束,增加“-50”,当前用户账户余额为:38 线程E运行结束,增加“20”,当前用户账户余额为:58 线程C运行结束,增加“-60”,当前用户账户余额为:-2 线程A运行结束,增加“10”,当前用户账户余额为:8
很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。
注意:当去掉synchronized修饰符后,线程不在同步,每次运行的结果将都不一样,可见同步的重要性。
再把以上实例改为同步代码块方式
对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式。代码如下:
package thread; /** * 同步代码块 * @author Chu * */ public class SynchronizedThread2 { public static void main(String[] args) { User u = new User("张三", 100); MyThread3 t1 = new MyThread3("线程A", u, 10); MyThread3 t2 = new MyThread3("线程B", u, -50); MyThread3 t3 = new MyThread3("线程C", u, -60); MyThread3 t4 = new MyThread3("线程D", u, -40); MyThread3 t5 = new MyThread3("线程E", u, 20); MyThread3 t6 = new MyThread3("线程F", u, 28); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread3 extends Thread { private User u; private int y = 0; MyThread3(String name, User u, int y) { super(name); this.u = u; this.y = y; } public void run() { u.oper(y); } } class User2 { private String code; private int cash; User2(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * @param x 添加x万元 */ public void oper(int x) { try { Thread.sleep(10L); synchronized (this) { this.cash += x; System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); } Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }
【运行结果】如下:
线程A运行结束,增加“10”,当前用户账户余额为:110 线程F运行结束,增加“28”,当前用户账户余额为:138 线程D运行结束,增加“-40”,当前用户账户余额为:98 线程E运行结束,增加“20”,当前用户账户余额为:118 线程C运行结束,增加“-60”,当前用户账户余额为:58 线程B运行结束,增加“-50”,当前用户账户余额为:8
用到线程的同步,随之可能会带来死锁问题。
导致死锁的原因:两个线程互相等待竞争资源,导致两边都无法得到资源,而使自己无法运行。
下面例举一个导致死锁的一个实例,代码如下:
package thread; class Demo1{ static Object obj1=new Object(); static Object obj2=new Object(); } class Demo2 implements Runnable{ boolean flag; Demo2(boolean flag){ this.flag=flag; } @Override public void run(){ if(flag){ while(true){ synchronized(Demo1.obj1){ System.out.println("1"); synchronized(Demo1.obj2){ System.out.println("2"); } } } } else{ while(true){ synchronized(Demo1.obj2){ System.out.println("2"); synchronized(Demo1.obj1){ System.out.println("1"); } } } } } }
最后我再说说:生产者消费者的问题
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
具体实现代码如下:
package thread; /** * Java线程:生产者消费者模型 * @author Chu 2013-06-15 05:32:29 */ public class ProductTest { public static void main(String[] args) { Godown godown = new Godown(20); Consumer c1 = new Consumer(80, godown); Consumer c2 = new Consumer(30, godown); Consumer c3 = new Consumer(20, godown); Producer p1 = new Producer(5, godown); Producer p2 = new Producer(5, godown); Producer p3 = new Producer(5, godown); Producer p4 = new Producer(10, godown); Producer p5 = new Producer(20, godown); Producer p6 = new Producer(35, godown); Producer p7 = new Producer(50, godown); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } } /** 仓库 */ class Godown { public static final int max_size = 100; //最大库存量 public int curnum; //当前库存量 Godown() { } Godown(int curnum) { this.curnum = curnum; } /** * 生产指定数量的产品 * @param neednum */ public synchronized void produce(int neednum) { //测试是否需要生产 while (neednum + curnum > max_size) { System.out.println("要生产的产品数量" + neednum + "超过剩余库存量" + (max_size - curnum) + ",暂时不能执行生产任务!"); try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足生产条件,则进行生产,这里简单的更改当前库存量 curnum += neednum; System.out.println("已经生产了" + neednum + "个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } /** * 消费指定数量的产品 * @param neednum */ public synchronized void consume(int neednum) { //测试是否可消费 while (curnum < neednum) { try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足消费条件,则进行消费,这里简单的更改当前库存量 curnum -= neednum; System.out.println("已经消费了" + neednum + "个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } } /** 生产者 */ class Producer extends Thread { //生产产品的数量 private int neednum; //仓库 private Godown godown; Producer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } public void run() { //生产指定数量的产品 godown.produce(neednum); } } /** 消费者 */ class Consumer extends Thread { //生产产品的数量 private int neednum; //仓库 private Godown godown; Consumer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } public void run() { //消费指定数量的产品 godown.consume(neednum); } }
【运行结果】如下:
已经消费了20个产品,现仓储量为0 已经生产了5个产品,现仓储量为5 已经生产了5个产品,现仓储量为10 已经生产了5个产品,现仓储量为15 已经生产了20个产品,现仓储量为35 已经生产了50个产品,现仓储量为85 已经消费了80个产品,现仓储量为5 已经生产了10个产品,现仓储量为15 已经生产了35个产品,现仓储量为50 已经消费了30个产品,现仓储量为20
以上这个例子仅仅是生产者消费者模型中最简单的一种表示,在这个例子中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。
相关推荐
【JAVA多线程总结】 Java 多线程是Java编程中的关键特性,它允许程序同时执行多个任务,提高系统的效率和响应性。本篇总结涵盖了Java多线程的基础概念、创建与启动、线程调度、同步与协作以及新特性。 **一、Java...
Java多线程是Java编程语言中的一个重要特性,它允许开发者创建并发执行的多个线程,从而提高程序的执行效率和响应速度。Java中实现多线程主要有两种方式:继承Thread类和实现Runnable接口。 ### 继承Thread类 在...
Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,提高了程序的效率和响应速度。本文将深入探讨Java多线程的相关知识点,包括线程的创建、线程的状态、同步机制以及线程安全问题。 1. **线程的创建*...
配合`JAVA多线程总结.ppt`,你可以得到一个更直观和简洁的概览,快速回顾和掌握上述关键知识点。虽然中文版翻译可能存在不足,但原版英文书籍通常能更准确地传达作者的意图和细节,值得深入阅读。
Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...
Java多线程是指在Java语言中同时运行多个线程,从而实现对任务的并行处理。这是Java中一个非常重要的概念和技能,尤其在需要高并发处理和优化性能的场景中显得尤为重要。以下将详细梳理Java多线程编程中的一些关键...
Java多线程是Java编程语言中的一个重要特性,它允许在单个程序中同时执行多个代码路径,从而提高程序的效率和响应性。在Java中,线程生命周期包括六种状态:新建状态(NEW)、可运行状态(RUNNABLE)、休眠状态...
Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...
java多线程思维导图总结【超详细,可下载】
### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...
### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...
### Java多线程分页查询知识点详解 #### 一、背景与需求分析 在实际的软件开发过程中,尤其是在处理大量数据时,如何高效地进行数据查询成为了一个关键问题。例如,在一个用户众多的社交平台上,当用户需要查看...
java多线程全面总结,简单的介绍多线程技术中的各种应用问题,是你对多线程有更多的认识!
JAVA 多线程总结 扩展java lang Thread类 实现java lang Runnable接口
总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...
总结一下,Java多线程涉及的内容广泛,包括线程的基本概念、创建、状态转换、调度和优先级管理。理解并掌握这些知识点对于编写高效并发的Java程序至关重要,也是面试中必不可少的技术点。在实际编程中,合理利用多...
总结来说,这个实验源码涵盖了Java多线程的基础和应用,包括线程的创建、运行、同步、通信,以及网络编程和数据库操作。通过这些实验,学生可以深入理解Java并发编程的核心概念,并掌握实际开发中的多线程设计技巧。
Java多线程程序设计是Java开发中的重要组成部分,它允许程序在同一时间执行多个任务,从而提高了系统的效率和响应性。本文将深入探讨Java多线程的相关概念和实现方式。 一、理解多线程 1. **线程定义**:线程是一...