Java 多线程总结
昨天熬了个通宵,看了一晚上的视频,把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(); //静态方法,使当前正在执行的线程睡眠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=;i<;i++) {
if(i%!=) {
System.out.println("在主线程中 i=" + i);
}
}
System.out.println("主线程main结束");
//通知线程结束
runner.shutDown();
}
} class Runner implements Runnable {
//控制线程是否结束
private boolean flag = true; public void run() {
int i=;
while(flag == true) {
System.out.println("在子线程中 i=" + i++);
}
System.out.println("子线程结束");
} //设置线程结束标志
public void shutDown() {
flag = false;
}
}
【运行结果】如下:
在主线程中 i=
在子线程中 i=
在主线程中 i=
在子线程中 i=
在主线程中 i=
在子线程中 i=
在主线程中 i=
在子线程中 i=
在主线程中 i=
在子线程中 i=
在主线程中 i=
在主线程中 i=
在主线程中 i=
在主线程中 i=
主线程main结束
在子线程中 i=
子线程结束
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=;i<;i++) {
System.out.println("主线程:" + i);
}
}
} class Runner2 implements Runnable {
public void run() {
for(int i=;i<;i++) {
System.out.println("子线程:" + i);
}
}
}
【运行结果】如下:
子线程:
子线程:
子线程:
子线程:
子线程:
子线程:
子线程:
子线程:
子线程:
子线程:
主线程:
主线程:
主线程:
主线程:
主线程:
介绍完以上几个实例,我们下面对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("王某", );
MyThread2 t1 = new MyThread2("线程A", u, );
MyThread2 t2 = new MyThread2("线程B", u, -);
MyThread2 t3 = new MyThread2("线程C", u, -);
MyThread2 t4 = new MyThread2("线程D", u, -);
MyThread2 t5 = new MyThread2("线程E", u, );
MyThread2 t6 = new MyThread2("线程F", u, ); t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
} class MyThread2 extends Thread {
private User u;
private int y = ; 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运行结束,增加“”,当前用户账户余额为:
线程F运行结束,增加“”,当前用户账户余额为:
线程E运行结束,增加“”,当前用户账户余额为:
线程D运行结束,增加“-”,当前用户账户余额为:
线程C运行结束,增加“-”,当前用户账户余额为:
线程B运行结束,增加“-”,当前用户账户余额为:
下面是线程不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后再运行程序
【运行结果】如下:
线程F运行结束,增加“”,当前用户账户余额为:
线程D运行结束,增加“-”,当前用户账户余额为:
线程B运行结束,增加“-”,当前用户账户余额为:
线程E运行结束,增加“”,当前用户账户余额为:
线程C运行结束,增加“-”,当前用户账户余额为:-
线程A运行结束,增加“”,当前用户账户余额为:
很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。
注意:当去掉synchronized修饰符后,线程不在同步,每次运行的结果将都不一样,可见同步的重要性。
再把以上实例改为同步代码块方式
对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式。代码如下:
package thread;
/**
* 同步代码块
* @author Chu
*
*/
public class SynchronizedThread2 {
public static void main(String[] args) {
User u = new User("张三", );
MyThread3 t1 = new MyThread3("线程A", u, );
MyThread3 t2 = new MyThread3("线程B", u, -);
MyThread3 t3 = new MyThread3("线程C", u, -);
MyThread3 t4 = new MyThread3("线程D", u, -);
MyThread3 t5 = new MyThread3("线程E", u, );
MyThread3 t6 = new MyThread3("线程F", u, ); t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
} class MyThread3 extends Thread {
private User u;
private int y = ; 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运行结束,增加“”,当前用户账户余额为:
线程F运行结束,增加“”,当前用户账户余额为:
线程D运行结束,增加“-”,当前用户账户余额为:
线程E运行结束,增加“”,当前用户账户余额为:
线程C运行结束,增加“-”,当前用户账户余额为:
线程B运行结束,增加“-”,当前用户账户余额为:
用到线程的同步,随之可能会带来死锁问题。
导致死锁的原因:两个线程互相等待竞争资源,导致两边都无法得到资源,而使自己无法运行。
下面例举一个导致死锁的一个实例,代码如下:
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("");
synchronized(Demo1.obj2){
System.out.println("");
}
}
}
}
else{
while(true){
synchronized(Demo1.obj2){
System.out.println("");
synchronized(Demo1.obj1){
System.out.println("");
}
}
}
}
}
}
最后我再说说:生产者消费者的问题
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。
此模型将要结合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();
Consumer c1 = new Consumer(, godown);
Consumer c2 = new Consumer(, godown);
Consumer c3 = new Consumer(, godown);
Producer p1 = new Producer(, godown);
Producer p2 = new Producer(, godown);
Producer p3 = new Producer(, godown);
Producer p4 = new Producer(, godown);
Producer p5 = new Producer(, godown);
Producer p6 = new Producer(, godown);
Producer p7 = new Producer(, 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 = ; //最大库存量
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
以上这个例子仅仅是生产者消费者模型中最简单的一种表示,在这个例子中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。
转自:http://wangqiang6028.iteye.com/blog/1887342
Java 多线程总结的更多相关文章
- 40个Java多线程问题总结
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...
- Java多线程基础知识篇
这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程--让主线程等待子线程执行完毕
使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...
- Java多线程 2 线程的生命周期和状态控制
一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...
- java 多线程 1 线程 进程
Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报 分类: javaSE综合知识点(14) 版权声明:本文为博主原创文章,未经博 ...
- 一起阅读《Java多线程编程核心技术》
目录 第一章 Java多线程技能 (待续...)
- 第一章 Java多线程技能
1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...
- java从基础知识(十)java多线程(下)
首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...
随机推荐
- 安装Gradle(Windows & Linux)
Gradle 是以 Groovy 语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自动化构建工具.在github上,gradle项目很多,有的是gradel跟maven构建一块儿使用 ...
- GIT归纳整理
1. 将repo_a的分支提交到repo_b分支 repo_a:表示原始git库地址:repo_b:表示新增的git库地址. git remote add new_remote repo_b:new_ ...
- 安装Laravel框架,利用composer
学一学PHP框架--Laravel的设计思想. 先安装Laravel: Laravel的文档很全:参考 http://www.golaravel.com/ 既然文档很全,就简单说下几个重点.以下以安装 ...
- Set.js--创建无重复值的无序集合
Set 集合,不同于 Array,是一种没有重复值的集合. 以下代码出自于<JavaScript 权威指南(第六版)>P217,注意:这里并不是指 es6 / es2015 中的 Set ...
- Docker镜像拉不下来?试试这些
DaoCloud 加速器1.0(永久免费) DaoCloud是国内第一家Dock Hub加速器提供商 注意,加速器 2.0 需要使用 DaoCloud 自己的云服务器才可以使用.官方宣称会继续支持加速 ...
- SpringCloud搭建Eureka集群
第一部分:搭建Eureka Server集群 Step1:新建工程,引入依赖 依赖文件pom.xml如下 <?xml version="1.0" encoding=" ...
- 2018 C++ Teaching Assistant Summary
期末考结束后就留校开始了科研,最近刚回家休息了两三天,整理了思绪,准备补上这一篇拖延了一个多月的助教小结. 早在一年多前我上栋哥这门课时,我就十分乐意给予同学帮助,无论是技术上的,还是说思想上的(也可 ...
- MySQL RPM二进制安装
+++++++++++++++++++++++++++++++++++++++++++标题:MySQL RPM二进制安装时间:2019年2月24日内容:MySQL RPM二进制安装重点:MySQL R ...
- OracleSql语句学习(二)
--DQL语句--查询语句用来检查数据使用--SELECT子句用来指定要查询的字段,若写“*”则表示查询所有字段.FROM子句用来指定数据来源的表.--SELECT * FROM emp_weiyij ...
- js01-javascript语法标准和数据类型
语法规则 (1)JavaScript对换行.缩进.空格不敏感. 备注:每一条语句末尾要加上分号,虽然分号不是必须加的,但是为了程序今后要压缩,如果不加分号,压缩之后将不能运行. (2)所有的符号,都是 ...