java多线程学习

来源:互联网 发布:java spring框架详解 编辑:程序博客网 时间:2024/06/06 00:00

进程线程

什么是进程呢? 
进程就是程序(任务)的执行过程,它是动态性的,比如大家常使用的QQ、开发用的Eclipse等经过点击运行就可以看作一个进程,在磁盘中存储的相关文件不是进程;进程持有资源(共享内存,共享文件)和线程。 
那什么是线程呢? 
拿QQ来说,在你和朋友进行文字聊天的同时还可以收发文件;在使用Eclipse进行源代码编辑,后台会进行语法检验和源代码编译,这些不同的小任务就是线程。再举一个生活中的例子,可以将一个班级比作一个进程,班级中的每一个学生视作一个线程,一个班级可以有多个学生,学生是班级里的最小单元,构成了班级的最小单位,这些学生都使用共同的黑板、教室等资源。 
概括起来线程具有如下特点:

  • 线程是系统中最小的执行单元
  • 同一进程中有多个线程
  • 线程共享进程的资源

线程之间靠互斥和同步进行交互。

线程的创建

创建线程有两种常用方法,一种是继承Thread类,一种是实现Runnable方法,这两种创建线程的方法都属于java.lang包,拥有同样的run()方法。 

                                                  
这个方法提供了线程工作实际运行的代码。这两种创建方式是存在一定联系的,查看Thread类的结构如下:

public class Thread implements Runnable {}

由源码发现Thread类实现了Runnable接口,它们之间具有多态关系。若使用继承Thread 类的方式创建线程时,最大的局限就是不支持多继承(Java语言的特点是单根继承),因而可以使用Runnable接口的方式创建线程。

线程创建的两种方式代码:

1.继承Thread类

class MyThread extends Thread {
......
@Override
public void run() {
......
}
}
MyThread mt = new MyThread(); //创建线程
mt.start(); //启动线程

2.实现Runnable接口

class MyThread implements Runnable {
......
@Override
public void run() {
......
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt); //创建线程
td.start(); //启动线程

两种创建线程方式的比较: 
Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷 
Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况

非线程安全问题

线程被调用的时机是随机的。在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。 
在多线程中可能出现多个线程同时处理一个共享变量(i–,变量自减),产生非线程安全问题(非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程)

在某些JVM中,i–操作要分成如下三部(不是原子的): 
1) 取得原有i值 
2) 计算i-1 
3) 对i进行赋值 
在这三个步骤中,有多个线程同时访问,则一定会出现非线程安全问题。

为了解决如上问题,可以通过在run方法前加入synchronized关键字,是多个线程在执行run方法时,以排队的方法进行处理。

System.out.println()方法内部是同步的

public void println(String x) {
syschronized (this) {
print(x);
newLine();
}
}

但是在println()方法中使用 i– 操作是在println()之前发生的,因而有发生非线程安全问题的概率。


Java线程的生命周期



创建:新建一个线程对象,如Thread thd = new Thread()
就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)
运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法
终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态。(线程调用stop()终止的方法已被淘汰掉了)

Java线程:用户线程和守护线程

用户线程:运行在前台,执行具体的任务,程序的主线程、连接网络的子线程等都是用户线程 
守护线程:运行在后台,为其他前台线程服务; 
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作 
应用:数据库连接池中的检测线程 
JVM虚拟机启动后的检测线程

如何设置守护进程

可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程

实例:

package mkw.demo.daemon;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;
 
class DaemonThread implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("进入守护进程" + Thread.currentThread().getName());
try {
writeTofile();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("退出守护进程" + Thread.currentThread().getName());
}
 
private void writeTofile() throws Exception{
File filename = new File("d:" + File.separator + "daemon.txt");
OutputStream os = new FileOutputStream(filename,true);
int count = 0;
while(count < 10){
os.write(("\r\nword" + count).getBytes());
System.out.println("守护线程" + Thread.currentThread().getName()
+ "向文件中写入了word" + count++);
Thread.sleep(1000); //睡眠1秒
}
}
}
 
public class daemonThreadDaemon {
 
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("进入主线程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
 
Scanner sc = new Scanner(System.in);
sc.next();
 
System.out.println("退出主线程" + Thread.currentThread().getName());
}
 
}

程序运行结果:

进入主线程main
进入守护进程Thread-0
守护线程Thread-0向文件中写入了word0
守护线程Thread-0向文件中写入了word1
守护线程Thread-0向文件中写入了word2
守护线程Thread-0向文件中写入了word3
守护线程Thread-0向文件中写入了word4
退出守护进程Thread-0

注意事项:

  • setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
  • 在守护线程中产生的新线程也是守护线程
  • 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑

使用jstack生成线程快照

命令行工具(jstat.exe)、界面化工具(jvisualvm.exe) 
jstack作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息) 
目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等 
下面使用jstack命令行工具查看上一步中创建守护进程程序的快照,首先运行该程序,在任务管理器中获取程序的进程ID,然后再DOS中使用jstack命令查看

D:\Program Files\Java\jdk1.7.0_51\bin>jstack -l 71704 //-l选项查看锁信息
2016-08-29 10:32:42 //线程生成快照的时间
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.51-b03 mixed mode): //eclipse运行时使用的jre类型和版本,HotSpot为Sun公司的虚拟机,使用的是服务器版本
 
//第一个线程Thread-0就是创建的守护线程,后边跟有daemon标记,紧接着是表示线程的优先级、标识等信息
"Thread-0" daemon prio=6 tid=0x000000000bd68000 nid=0x110ac waiting on condition [0x000000000d14f000
]
java.lang.Thread.State: TIMED_WAITING (sleeping) //显示线程的状态
at java.lang.Thread.sleep(Native Method)
at mkw.demo.daemon.DaemonThread.writeTofile(daemonThreadDaemon.java:31)
at mkw.demo.daemon.DaemonThread.run(daemonThreadDaemon.java:15)
at java.lang.Thread.run(Unknown Source)
 
Locked ownable synchronizers: //线程锁信息,None未加锁,使用-l选项才会显示
- None
 
"Service Thread" daemon prio=6 tid=0x000000000bd11800 nid=0x10924 runnable [0x0000000000000000] //此处的线程是java程序运行所必须的一些线程信息
java.lang.Thread.State: RUNNABLE
 
Locked ownable synchronizers:
- None
......
//主线程main,没有daemon标识,是用户线程
"main" prio=6 tid=0x00000000025ee800 nid=0x11294 runnable [0x00000000029ff000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(Unknown Source)
at java.io.BufferedInputStream.read1(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
- locked <0x00000007cc055778> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
at sun.nio.cs.StreamDecoder.read(Unknown Source)
- locked <0x00000007cc0a19f0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(Unknown Source)
at java.io.Reader.read(Unknown Source)
at java.util.Scanner.readInput(Unknown Source)
at java.util.Scanner.next(Unknown Source)
at mkw.demo.daemon.daemonThreadDaemon.main(daemonThreadDaemon.java:47)
 
Locked ownable synchronizers:
- None

查看JDK文档中的解释

线程状态。线程可以处于下列状态之一:
NEW
至今尚未启动的线程处于这种状态。
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。

0 0
原创粉丝点击