Java多线程编程— 概念以及常用控制
多线程能满足程序员编写非常有效率的程序来达到充分利用CPU的目的,因为CPU的空闲时间能够保持在最低限度。有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。线程的运行中需要使用计算机的内存资源和CPU。
一、 进程与线程的概念
这两者的概念,这里只给出自己狭隘的理解:
进程:进程是一个独立的活动的实体,是系统资源分配的基本单元。它可以申请和拥有系统资源。每个进程都具有独立的代码和数据空间(进程上下文)。进程的切换会有较大的开销。
进程,是一个“执行中的程序”。程序是一个静态的没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。也就是说,进程是正在运行的程序的实例(an instance of a computer program that is being executed)。例如,你运行一个qq,就会启动一个进程,再次运行qq,就会再启动一个进程。
线程: 其实,60年代,进程不仅是资源分配的基本单元,还是资源调度的基本单元。然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
也就是说,现在,线程才是资源(cpu)调度的基本单元,它是一个程序内部的控制流程。线程是进程内部的更小的单元,它基本不占用系统资源。一个进程内的多个线程是为了协同工作来处理一件事情。
简单总结来说就是,进程是为了分配得到资源,然后由它里面的线程利用资源来处理事情。进程是一个壳子,实际干事的都是线程。(例如,我们的main函数作为主线程)。二者较为深入一点的总结:http://wangzhipeng0713.blog.163.com/blog/static/1944751652015522359459/
二、 线程的创建和启动
2.1 方式一:线程类实现Runnable接口
定义线程类:
[java] view plaincopy
/**
* 定义线程类(实现Runnable接口)
*
* @author wangzhipeng
*
*/
public class Runner1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("New Thread Runner1-->" + i);
}
}
}
测试类:
[java] view plaincopy
public class TestThread1 {
// 1、main方法为主线程
public static void main(String[] args) {
// 2、启动第二个线程
Runner1 runner1 = new Runner1();
Thread thread = new Thread(runner1, "zhipeng");// 注意,Runner类实现了Runnable接口,启动线程时需要用一个Thread类将其包起来
thread.start();// 调用start方法,使得线程进入“就绪”状态
for (int i = 0; i < 100; i++) {
System.out.println("【Main】 Thread-->" + i);
}
}
}
2.2 方式二:线程类继承Thread类并重写其run方法
定义线程类:
[java] view plaincopy
/**
* 定义线程类(继承Thread类)
*
* @author wangzhipeng
*
*/
public class Runner2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("New Thread Runner1-->" + i);
}
}
}
测试类:
[java] view plaincopy
public class TestThread2 {
// main方法为主线程
public static void main(String[] args) {
// 1、线程类继承Thread类的start写法
Runner2 runner2 = new Runner2();
runner2.start();// 这里需要注意:因为Runner2已经是一个线程类了,所以不需要再对它进行包装,直接调用start即可
// 2、线程类实现Runnable接口的start写法(需要用Thread类包装)
// Thread thread = new Thread(runner1);
// thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("【Main】 Thread-->" + i);
}
}
}
运行结果:
2.3 小结
虽然两种方式都能达到相同的效果,但我们一般不采用继承的方式实现多线程,因为一旦继承了Thread类,你的类就无法再继承其它的类。而实现了Runnable接口后,你还可以实现其它接口或继承其它类。也就是说面向接口编程比较灵活。
三、 线程的状态转换
这里需要注意的是,线程调用start()方法后,是进入“就绪状态”,而不是“运行状态”。也就是说,是线程告诉操作系统,我已准被调度所需要的一切事物,只有在被调度后线程才进入到运行状态。
四、 线程控制的基本方法
这里主要简单介绍sleep/join/yield/以及线程的优先级,至于wait与notify/notifyAll这一对重要的方法会在后面一篇文章,线程同步问题中详细介绍。
4.1 sleep方法
很简单很常用,是Thread类的静态方法:
示例程序
线程类:
[java] view plaincopy
import java.util.Date;
/**
* 通过继承Thread类实现线程类
*
* @author wangzhipeng
*
*/
public class MyThread extends Thread {
public void run() {
/**
* 每一秒钟输出一下当前日期
*/
while (true) {
System.out.println("---->" + new Date());
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
Sleep测试类:
[java] view plaincopy
public class TestInterrupt {
public static void main(String[] argStrings) {
MyThread myThread = new MyThread();
myThread.start();// 启动第二个线程
try {
Thread.sleep(5000);// 主线程5秒钟后终止第二个线程
myThread.interrupt();// 一般不用这种方式终止线程--比较粗暴
// myThread.stop();//更加不用--更加粗暴
} catch (InterruptedException e) {
return;
}
}
}
这里需要注意“终止线程”的方式,上面提到了interrupt()与stop()两种方式都是比较粗暴的方式,即强行终止,一般不采用。而是在线程类中定义一个信号量,然后客户端通过给该信号量赋值来“温和”地控制线程的终止。例如给下面的线程类中的flag赋值false即可终止线程。
[java] view plaincopy
public class MyThread extends Thread {
boolean flag = true;// 定义信号量来控制线程的终止
public void run() {
/**
* 每一秒钟输出一下当前日期
*/
while (flag) {
System.out.println("---->" + new Date());
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
4.2 join方法
join()代表将第二线程合并到主线程,也就是将第二线程与主线程顺序执行,而不是并发执行。
join(5000) 代表前5秒钟将第二线程合并到主线程,5秒过后,第二线程与主线程并发执行。
示例程序
线程类:
[java] view plaincopy
/**
* 定义线程类(继承Thread类)
*
* @author wangzhipeng
*
*/
public class Mythread2 extends Thread {
Mythread2(String s) {
super(s);
}
/**
* 每一秒钟输出一下当前线程的名称
*/
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("I am " + getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
join方法测试类:
[java] view plaincopy
/**
* join方法测试
*
* @author wangzhipeng
*
*/
public class TestJoin {
public static void main(String[] args) {
Mythread2 mythread2 = new Mythread2("zhpeng");
mythread2.start();// 启动第二线程
try {
mythread2.join();// 将第二线程合并到主线程,也就是将第二线程与主线程顺序执行,而不是并发执行
// mythread2.join(5000);// 前5秒钟将第二线程合并到主线程,5秒过后,第二线程与主线程并发执行
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 主线程:循环输出一句话
*/
for (int i = 0; i < 10; i++) {
System.out.println("I am Main Thread");
}
}
}
运行结果:
4.3 yield方法
Thread类的静态方法。暂停当前正在执行的线程对象,并执行其他线程。也就是高风亮节,自己先暂停一下,让给别人先执行一下下。
示例代码
线程类:
[java] view plaincopy
/**
* 定义线程类(继承Thread类)
*
* @author wangzhipeng
*
*/
public class Mythread3 extends Thread {
public Mythread3(String s) {
super(s);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": " + i);
// 如果i能被10整除,则让出cpu执行主线程,也就是说输出结果的子线程i为0、5、10、15等时,下一个输出必定为主线程的输出(只要主线程没有执行完毕)
if (i % 5 == 0) {
yield();
}
}
}
}
yield方法测试类:
[java] view plaincopy
/**
* yield方法测试类
*
* @author wangzhipeng
*
*/
public class TestYield {
public static void main(String[] argStrings) {
Mythread3 mythread3 = new Mythread3("------zhipeng");
mythread3.start();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println("-----MainThread " + i);
}
}
}
输出结果:
4.4 线程的优先级Priority
每一个Java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java优先级在MIN_PRIORITY(1)和MAX_PRIORITY(10)之间的范围内。默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器时间。然而,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
示例程序
定义两个线程类:
[java] view plaincopy
public class T1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("----------T1");
}
}
}
[java] view plaincopy
public class T2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("T2");
}
}
}
测试类:
[java] view plaincopy
public class TestPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(new T1());
Thread thread2 = new Thread(new T2());
thread1.setPriority(Thread.NORM_PRIORITY + 5);// 给thread1线程的优先级加5,这样它被调度的机会就会比thread2大很多
thread1.start();
thread2.start();
}
}
运行结果:
五、 总结
进程是系统资源分配的基本单元,它是程序的运行示例,可以独立存在。线程是cpu调度的基本单元,是一个程序内部的控制流程,不能独立存在,必须依附于进程。一个进程包含多个线程,进程是为了得到系统资源,但实际上运作这些系统资源的都是线程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。因为,上下文的切换开销也很重要,如果你创建了太多的线程,CPU花费在上下文的切换的时间将多于执行程序的时间!
【免责声明】本文部分系转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,如涉及作品内容、版权和其它问题,请在30日内与我们联系,我们会予以重改或删除相关文章,以保证您的权益!
Java开发高端课程免费试学
大咖讲师+项目实战全面提升你的职场竞争力
- 海量实战教程
- 1V1答疑解惑
- 行业动态分析
- 大神学习路径图
相关推荐
更多2019-02-21
2019-02-21
2019-02-20
2018-04-09
达内就业喜报
更多>Java开班时间
-
北京 丨 11月27日
火速抢座 -
上海 丨 11月27日
火速抢座 -
广州 丨 11月27日
火速抢座 -
兰州 丨 11月27日
火速抢座 -
杭州 丨 11月27日
火速抢座 -
南京 丨 11月27日
火速抢座 -
沈阳 丨 11月27日
火速抢座 -
大连 丨 11月27日
火速抢座 -
长春 丨 11月27日
火速抢座 -
哈尔滨 丨 11月27日
火速抢座 -
济南 丨 11月27日
火速抢座 -
青岛 丨 11月27日
火速抢座 -
烟台 丨 11月27日
火速抢座 -
西安 丨 11月27日
火速抢座 -
天津 丨 11月27日
火速抢座 -
石家庄 丨 11月27日
火速抢座 -
保定 丨 11月27日
火速抢座 -
郑州 丨 11月27日
火速抢座 -
合肥 丨 11月27日
火速抢座 -
太原 丨 11月27日
火速抢座 -
苏州 丨 11月27日
火速抢座 -
武汉 丨 11月27日
火速抢座 -
成都 丨 11月27日
火速抢座 -
重庆 丨 11月27日
火速抢座 -
厦门 丨 11月27日
火速抢座 -
福州 丨 11月27日
火速抢座 -
珠海 丨 11月27日
火速抢座 -
南宁 丨 11月27日
火速抢座 -
东莞 丨 11月27日
火速抢座 -
贵阳 丨 11月27日
火速抢座 -
昆明 丨 11月27日
火速抢座 -
洛阳 丨 11月27日
火速抢座 -
临沂 丨 11月27日
火速抢座 -
潍坊 丨 11月27日
火速抢座 -
运城 丨 11月27日
火速抢座 -
呼和浩特丨11月27日
火速抢座 -
长沙 丨 11月27日
火速抢座 -
南昌 丨 11月27日
火速抢座 -
宁波 丨 11月27日
火速抢座 -
深圳 丨 11月27日
火速抢座 -
大庆 丨 11月27日
火速抢座