进程是程序向操作系统申请资源(如内存和文件句柄)的基本单位。线程是进程中可独立执行的最小单位。
一个进程可以包含多个线程,同一个进程中的所有线程共享该进程的资源。
java线程API简介
在java平台中创建一个线程就是创建一个Thread类或其子类,run方法是该线程的主要执行逻辑;启动一个线程是手动调用线程的start方法,该方法的作用实际是请求java虚拟机运行相应的线程,但是这个线程具体何时能够运行是由线程调度器决定的,这个线程可能会立马被jvm执行,也可能等一会才会被执行,甚至不会被执行,这是由jvm来控制。
package JavaCoreThreadPatten.capter01;
import java.util.concurrent.TimeUnit;
public class WelcomeApp {
public static void main(String[] args){
Thread thread = new WelcomeThread();
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.start();
System.out.println("父线程:"+Thread.currentThread().getName());
}
}
class WelcomeThread extends Thread{
@Override
public void run() {
System.out.println("子线程:"+Thread.currentThread().getName());
}
}
package JavaCoreThreadPatten.capter01;
public class WelcomeApp1 {
public static void main(String[] args){
Thread thread = new Thread(new WelcomeThread1());
thread.start();
System.out.println("父线程:"+Thread.currentThread().getName());
}
}
class WelcomeThread1 implements Runnable{
@Override
public void run() {
System.out.println("子线程:"+Thread.currentThread().getName());
}
}
运行结束的线程所占用的资源会如同其他java对象一样被java虚拟机回收。
线程属于一次性用品,不能重复调用线程的start方法使其重复运行。
在java平台中,一个线程就是一个对象,对象的创建离不开内存空间的分配。创建一个线程与创建其他类型的java对象所不同的是,java虚拟机会对每个线程分配调用栈所需的内存空间。调用栈用于跟踪java代码间的调用关系以及java代码对本地代码的调用。java平台中的每个线程可能还有一个内核线程与之对应,因此创建线程对象比创建其他类型的对象成本要高一些。
package JavaCoreThreadPatten.capter01;
import java.util.Random;
public class ThreadCreationCmp {
/**
* 执行结果:
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterThread :10
CounterTask :1060
CounterTask :1124
CounterTask :1138
CounterTask :1149
CounterTask :1165
CounterTask :1166
CounterTask :1174
CounterTask :1178
CounterTask :1189
CounterTask :1189
CounterTask :1189
CounterTask :1195
* @param args
*/
public static void main(String[] args){
Thread t;
CountingTask countingTask = new CountingTask();
//获取处理器个数
final int numberOfProceesors = Runtime.getRuntime().availableProcessors();
for(int i=0;i<2*numberOfProceesors;i++){
//直接创建一个线程,共用一个runnable实例,以runnable的形式启动线程
t = new Thread(countingTask);
t.start();
}
for(int i=0;i<2*numberOfProceesors;i++){
//以子类的形式直接启动线程
t = new CounterThread();
t.start();
}
}
static class Counter{
private int count = 0;
public void increace(){
count++;
}
public int getCount(){
return count;
}
}
static class CountingTask implements Runnable{
Counter counter = new Counter();
@Override
public void run() {
for(int i=0;i<100;i++){
counter.increace();
doSomething();
}
System.out.println("CounterTask :"+counter.getCount());
}
private void doSomething(){
Random random = new Random();
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class CounterThread extends Thread{
Counter counter = new Counter();
@Override
public void run() {
for(int i=0;i<10;i++){
doSomething();
counter.increace();
}
System.out.println("CounterThread :"+counter.getCount());
}
private void doSomething(){
Random random = new Random();
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的属性包括线程的编号、名称、类别、优先级。线程的类别需在线程启动前进行设置,即对setDaemon方法的调用必须在对start方法的调用之前,true标识为守护线程,否则为用户线程。
java线程的优先级属性本质上只是一个给线程调度器的提示信息,以便于线程调度器决定优先调度哪些线程运行。但是并不能保证线程按照其优先级高低的顺序运行。java线程的优先级使用不当或者滥用则可能导致某些线程无法得到运行,即产生线程饥饿。
java中线程分为守护线程和用户线程。用户线程会阻止java虚拟机的正常停止,即一个java虚拟机只有在其所有用户线程都运行结束的情况下才能正常停止。而守护线程则不会影响java虚拟机的正常停止,即应用程序中有守护线程在运行时也不影响java虚拟机的正常停止。
线程类的常用方法
run():一般由java虚拟机直接调用,用于实现线程任务处理逻辑。
start():启动相应线程,只能被调一次,该方法被调用不代表线程已经被启动。
join():等待相应的线程运行结束,若线程A调用线程B的join方法,那么线程A的运行会被暂停,直到线程B运行结束。相当于执行该方法的线程告诉线程调度器:先暂停一下,等另一个线程运行结束后再继续运行。
yield():使当前线程主动放弃其对处理器的占用,这可能会导致当前线程被暂停。相当于执行该方法的线程告诉线程调度器:不着急,如果别人需要处理器资源先给他用,当没有其他人用,我也不介意继续占用。
package JavaCoreThreadPatten.capter01;
/**
* 实现简单的计时器
*/
public class SimpleTimer {
private static int i = 60;
public static void main(String[] args){
while (true){
if (i==0){
break;
}else {
System.out.println("wait me :"+ i +" second");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDown();
}
}
private static void countDown(){
i--;
}
}
线程的层次关系
假设线程A所执行的代码创建了线程B,那么B是A的子线程,A是B的父线程。
线程的生命周期
java线程的状态通过Thread.getState()来获取,包括以下几种:
NEW:一个已创建而未启动的线程处于该状态。由于一个线程实例只能够被启动一次,因此一个线程只可能有一次处于该状态。
RUNNABLE:包括两个子状态:READY和RUNNING。前者标识处于该状态的线程可以被线程调度器进行调度而使之处于RUNNING状态。后者表示处于该状态的线程正在运行。执行Thread.yield()的线程,其状态可能会由RUNNING转换为READY。处于READY子状态的线程也被称为活跃线程。
BLOCKED:一个线程发起一个阻塞式I/O操作后,或者申请一个由其他线程持有的独占资源(锁)时,相应的线程会处于该状态。处于BLOCKED状态的线程并不会占用处理器资源。当阻塞式I/O操作完成后,或者线程获得了其申请的资源,该线程的状态又可以转换为RUNNING。
WAITING:一个线程执行了某些特定方法之后就会处于这种等待其他线程执行另一些特定操作的状态。能够使其执行线程变更未WAITING状态的方法包括:Object.wait(),Thread.join()和LockSuppot.park(Object)。能够使相应的线程从WAITING变更为RUNNABLE的相应方法包括:Object.notify()/notifyAll()和LockSuppot.unpark(Object)。
TIMED_WAITING:该状态和WAITING类似,差别在于处于该状态的线程并非无限制的等待其他线程执行特定操作,而是处于带有时间限制的等待操作。当其他线程没有在指定时间内执行该线程所期望的特定操作时,该线程的状态自动转换为RUNNABLE。
TERMINATED:已经执行结束的线程处于该状态。由于一个线程实例只能够被启动一次,因此一个线程也只有可能有一次处于该状态。
线程的监视
对线程进行监视的主要途径是获取并查看程序的线程转储(Thread Dump)。
package JavaCoreThreadPatten.capter01;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 使用多线程下载文件
*/
public class FileDownloaderApp {
public static void main(String[] args){
Thread thread = null;
for(String url:args){
thread = new Thread(new FileDownloader(url));
thread.start();
}
}
static class FileDownloader implements Runnable{
private final String url;
public FileDownloader(String url){
this.url = url;
}
@Override
public void run() {
String fileBaseName = url.substring(url.lastIndexOf('/')+1);
try {
URL url = new URL(this.url);
String localFileName = System.getProperty("java.io.tmpdir")+"/viscent-"+fileBaseName;
download(url,localFileName);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private void download(URL url,String dir){
//将文件保存到指定地址中
}
}
}
多线程编程的优势和风险
优势:1.提高系统吞吐率;2.提高响应性;3.充分利用多核处理器资源;4.最小化对系统资源的使用;5.简化程序的结构。
风险:1.线程安全问题;2.线程活性问题:死锁、活锁(一个线程一直尝试某个操作但就是无法进展)、线程饥饿(某些线程永远无法获取处理器执行的计划而永远处于RUNNABLE状态的READY子状态);3.上下文切换;4.可靠性