一、基本概念
1.程序:指令集 静态概念
2.进程:操作系统调度程序,是一个动态的概念。是程序的一次动态执行的过程,占用特定的地址空间。每个进程都是独立的,由3部分组成
cpu、data、code
。缺点:内存的浪费,cpu
负担重。3.线程:在进程内多条执行路径。线程是进程中一个单一的连续控制流程(执行路径)。线程又被称为轻量级进程,一个进程可拥有多个并行的线程,一个进程中的线程共享相同的内存单元/内存地址空间-->可以访问相同的变量和对象,而且它们从同一堆中分配对象-->通信、数据交换、同步操作。由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
4.比较
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器PC线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了cpu之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源)线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线程的,而是多条线(线程)共同完成的 | 线程是进程的一部分,所以线程有的时候被称为是轻量级进程或轻权进程 |
二、相关类的使用
2.1 使用Thread类实现多线程
这里我们给出一个简单的例子:
Rabbit_01.java
package cn.itcast.day169.thread;
/*
* 模拟龟兔赛跑
* 1、创建多线程,继承Thread类+重写run方法(其中的代码都是线程体)
* 2、使用线程:创建子类对象+对象.start(),调用start方法表示线程的启动
* */
//兔子
public class Rabbit_01 extends Thread{
public void run() {
//线程体
for(int i = 0; i < 100; i++){
System.out.println("兔子跑了: " + i + "步");
}
}
}
//乌龟
class Tortoise extends Thread{
public void run() {
//线程体
for(int i = 0; i < 100; i++){
System.out.println("乌龟跑了: " + i + "步");
}
}
}
RabbitApp_01.java
package cn.itcast.day169.thread;
//我们会将此类加到一个线程组中
public class RabbitApp_01 {
public static void main(String[] args) {
//创建子类对象
Rabbit_01 rab = new Rabbit_01();
Tortoise tor = new Tortoise();
//调用start方法
rab.start();//不要调用run方法,调用run方法只是调用一个普通方法,没有开启一个线程
tor.start();//此时这里就有两条线程了
for(int i = 0; i < 1000; i++){
System.out.println("main " + i );
}
}
}
说明:这里可以看到我们通过继承Thread
类实现了多线程,但是很明显有个缺点,就是如果一个类本身就已经继承了一个父类,那么此类就不能再继承Thread
类来实现多线程了。
2.2 使用Runnable实现多线程
为了解决上面的问题,我们可以实现Runnable
接口。优点:可以同时实现继承,实现Runnable
接口方式要通用一些。
- 1)避免单继承
- 2)方便共享资源 , 同一份资源, 多个代理访问。
Runnable
使用了静态代理的设计模(继承相同的接口),首先我们看什么是静态设计模式:
StaticProxy.java
package cn.itcast.day169.thread;
/*静态代理
* 1、真实角色
* 2、代理角色:要持有真实角色的引用
* 3、二者实现相同的接口
* */
public class StaticProxy {
public static void main(String[] args) {
You you = new You();//创建真实角色
//创建代理角色+ 持有真实角色的引用
WeddingCompany company = new WeddingCompany(you);
//执行任务
company.marry();
}
}
//接口
interface Marry{
public abstract void marry();
}
//真实角色
class You implements Marry{
public void marry() {
System.out.println("you and girlfriend marry");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you ;
public WeddingCompany() {}
//这里我们通过构造方法传递真实引用,当然我们也可以使用setter方法来传递
public WeddingCompany(Marry you) {
this.you = you;
}
private void befor(){
System.out.println("布置新房");
}
public void marry() {
//在这里我们可以添加一些真实对象中没有的方法,完成一些额外的工作
befor();
you.marry();
after();
}
private void after(){
System.out.println("闹婚房");
}
}
说明:静态代理本质上就是一个类替代执行另一个类的相关方法,在执行此方法的时候还会附加执行一些操作,而不是直接去执行实际类的方法,相当于一种扩展。在实现静态代理的时候需要注意的是,代理类和被代理类需要实现相同的接口,而执行的方法都是代理类中的方法,同时代理类中的方法需要完成被代理类中需要完成的功能。例子中代理类WeddingCompany
和被代理类You
都继承的接口是Marry
,本来是You
结婚,但是我们将这项工作交给代理类,不仅完成结婚的工作,还处理了一些其他事情。
下面我们看如何使用Runnable
实现多线程:
Programer.java
package cn.itcast.day169.thread;
//使用Runnable创建线程
/*1、类实现Runable接口,重写run方法-->真实角色类
* */
public class Programer implements Runnable{
public void run() {
for(int i = 0; i < 1000; i++){
System.out.println("一边做a, 一边做b");
}
}
}
ProgramerApp.java
package cn.itcast.day169.thread;
//使用Runnable创建线程
/*1、类实现Runable接口,重写run方法-->真实角色类
* 2.启动多线程,使用静态代理
* 1)创建真实角色
* 2)创建代理角色
* 3)调用start方法启动线程
* */
public class ProgramerApp {
public static void main(String[] args) {
//1)创建真实角色
Programer programer = new Programer();
//2)创建代理角色
Thread proxy = new Thread(programer);
//3)调用start方法启动线程
proxy.start();
for(int i = 0; i < 1000; i++){
System.out.println("一边聊天");
}
}
}
说明:这里我们看到实现实例化被代理类Programer
,然后使用此类创建Thread
代理类。这样便实现了多线程。这里我们推荐使用这种方法。此种方式避免单继承的局限性,同时方便共享资源。
2.3 通过Callable接口实现多线程
在上面的方式中我们可以看到使用
Runnable
接口时,run
方法是不能有返回值的,但是有时候我们不可避免的希望得到返回值,此时我们就可以使用Callable
这个接口。Callable
是类似于Runnable
的接口,实现Callable
接口的类和实现Runnable
的类都是可被其它线程执行的任务。Callable
和Runnable
有几点不同:
1)Callable
规定的方法是call()
,而Runnable
规定的方法是run
2)call
方法可抛出异常,而run
方法是不能抛出异常的
3)Callable
的任务执行后返回值,运行Callable
任务可拿到一个Future
对象,而Runnable
的任务是不能返回值的。Futrue
表示异步计算的结果。它提供了检查计算是否完成的方法啊,以等待计算的完成,并检索计算的结果。通过Future
对象可以了解任务执行的情况,可取任务的执行,还可以获取任务的执行的结果。
缺点:繁琐使用方式
1)创建Callable
实现类+重写call
方法
2)借助执行调度服务ExecutorService
获取Futrue
对象
ExecutorService ser = Executors.newFixedThreadPool(2);
Futrure result = ser.submit(实现类对象);
3)、获取值result.get()
4)停止服务ser.shutdownNow()
Callable_01.java
package cn.itcast.day169.thread;
import java.util.concurrent.*;
/*使用Callable创建线程 */
public class Callable_01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建两个线程
ExecutorService service = Executors.newFixedThreadPool(2);
Race tortoise = new Race("千年王八", 1000);
Race rabbit = new Race("兔子", 500);
//获取值
Future<Integer> result1 = service.submit(tortoise);
Future<Integer> result2 = service.submit(rabbit);
Thread.sleep(2000);//2秒
tortoise.setFlag(false);//停止线程体循环
rabbit.setFlag(false);
int num1 = result1.get();
int num2 = result2.get();
System.out.println("乌龟跑了 " + num1 + "步");
System.out.println("兔子跑了 " + num2 + "步");
//关闭线程
service.shutdownNow();
}
}
class Race implements Callable<Integer>{
private String name ;//名称
private long time ;//延时时间,用来表示速度
private boolean flag = true;
private int step = 0;//步
public Race(){}
public Race(String name ){
this.name = name;
}
public Race(String name, long time) {
this.name = name;
this.time = time;
}
//重写call方法
public Integer call() throws Exception {
while(flag){
Thread.sleep(time);
step++;
}
return step;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}
说明:这里的使用方式和之前的稍微有点不同。
三、线程状态
3.1 基本概念
线程被创建之后我们调用
start
方法,此时线程进入就绪状态(可运行状态),当cpu
调度的时候进入运行状态,当线程执行完后此线程就停止了,即终止状态。但是如果线程在还没有执行完的时候被停止,那么就进入阻塞状态,如果阻塞状态解除之后线程又进入就绪状态,在之后cpu
调度则还会接着进入运行状态进行执行。这里注意:死亡状态是线程声明周期中的最后一个阶段,线程死亡的原因有两个。一个是正常执行完,一个是线程被强制性的终止工作,如果通过执行
stop
或destroy
方法来终止一个线程(不推荐使用这两个方法),前者会产生异常,后者是强制终止,不会释放锁。-
死亡状态如何停止线程
在之前的例子中,其实我们已经看到,我们可以通过一个标志来停止线程。
1、自然终止
2、外部干涉- 1)线程类中定义线程体使用的标志
- 2)线程体使用该标志
- 3)提供对外的方法可以改变该标志
- 4)外部根据条件调用该方法即可
使用标志位来停止线程:
package cn.itcast.day174.thread01;
public class Demo01 {
public static void main(String[] args) {
Study s = new Study();
new Thread(s).start();
//外部干涉
for(int i = 0; i < 100; i++){
if(i == 50){//注意:这里停下来的时间点不是很准确,由cpu决定
s.stop();
}
System.out.println("main-->" + i);
}
}
}
class Study implements Runnable{
private boolean flag = true;
public void run() {
while(flag){
System.out.println("study thread...");
}
}
//对外提供该表标志的方法
public void stop(){
this.flag = false;
}
}
- 阻塞
- 1、
join
:合并线程,将多条线程合并为一个 - 2、
yield
:暂停当前线程(暂停自己),执行其他线程 - 3、
sleep
:暂停线程,休眠的时候不会释放锁。相关应用:
1)与时间相关:数数和倒计时
2)模拟网络延时
- 1、
合并线程
package cn.itcast.day174.thread01;
/*合并线程 */
public class JoinDemo01 extends Thread{
public static void main(String[] args) throws InterruptedException {
JoinDemo01 demo = new JoinDemo01();
Thread t = new Thread(demo);//新生状态
t.start();//就绪状态
//cpu调度就进入运行状态
for(int i = 0; i < 100; i++){
if(i == 50){
//此时main方法阻塞了,必须让线程t运行完,就是刚开始谁先执行都是随机的,
//但是当i的值为50后,便是t线程执行完后main线程才能再次执行
t.join();
}
System.out.println("main..." + i);
}
}
public void run(){
for(int i = 0; i < 100; i++){
System.out.println("join..." + i);
}
}
}
说明:这里其实是有两个线程,一个是线程t
,一个是main
。开始时是并发执行,但是到i
的值为50的时候便需要t
线程运行完成之后main
线程才能开始执行。
暂停当前线程
package cn.itcast.day174.thread01;
/*暂停执行当前的线程(暂停自己),并执行其他线程*/
public class YieldDemo01 extends Thread{
public static void main(String[] args) throws InterruptedException {
YieldDemo01 demo = new YieldDemo01();
Thread t = new Thread(demo);//新生状态
t.start();//就绪状态
//cpu调度就进入运行状态
for(int i = 0; i < 100; i++){
if(i % 20 == 0){
//注意:这里的暂停不是很严格
Thread.yield();//暂停自己,注意:写在谁的线程体内就暂停谁(这里暂停main),效果不是很明显
}
System.out.println("main..." + i);
}
}
public void run(){
for(int i = 0; i < 100; i++){
System.out.println("yield..." + i);
}
}
}
说明:这里一定要注意到底是暂停哪个线程。
数数和倒计时
package cn.itcast.day174.thread01;
import java.util.Date;
import java.text.SimpleDateFormat;
//倒计时,倒数十个数,一秒内打印一个
public class SleepDemo01 {
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test1() throws InterruptedException {
int num = 10;
while (true) {
System.out.println(num--);
Thread.sleep(1000);// 暂停
if (num <= 0) {
break;
}
}
}
// 倒计时
public static void test2() throws InterruptedException {
Date endTime = new Date(System.currentTimeMillis() + 10 * 1000);
long end = endTime.getTime();
while (true) {
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
endTime = new Date(endTime.getTime() - 1000);
Thread.sleep(1000);
if (end - 10000 > endTime.getTime()) {
break;
}
}
}
}
模拟网络延时
package cn.itcast.day174.thread01;
//模拟网络延时,可能出现并发问题
public class SleepDemo02 {
public static void main(String[] args) {
Web12306 web = new Web12306();//真实角色
//代理对象
Thread t1 = new Thread(web, "黄牛1");//第二个参数是当前线程的名字
Thread t2 = new Thread(web, "黄牛2");
Thread t3 = new Thread(web, "黄牛3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable{
private int num = 50;
public void run() {
while(true){
if(num <= 0){
break;
}
try {
Thread.sleep(500);//500ms的延时
//加入延时之后可能会造成资源冲突的问题,这就是并发问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");
}
}
}
- 线程基本信息
方法 | 功能 |
---|---|
isAlive() |
判断线程是否还“活”着,即线程是否还未终止 |
getPriority() |
获得线程的优先级数值 |
setPriority() |
设置线程的优先级数值 |
setName() |
给线程一个名字 |
getName() |
取得线程的名字 |
currentThread() |
取得当前正在运行的线程对象,也就是取得自己本身 |
下面给出两个例子:
MyThread.java
package cn.itcast.day174.thread01;
public class MyThread implements Runnable {
private boolean flag = true;
private int num = 0;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName() + num++);
}
}
public void stop() {
this.flag = false;
}
}
InfoDemo01.java
package cn.itcast.day174.thread01;
//currentThread()静态方法
public class InfoDemo01 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
Thread proxy = new Thread(t1, "t1线程");//第二个参数是线程的名称
//我们也可以这样设置线程名称
proxy.setName("狗蛋");
System.out.println(proxy.getName());
System.out.println(Thread.currentThread().getName());//获取名称
proxy.start();
System.out.println("线程启动后的状态: " + proxy.isAlive());//查看线程是否“活着”
Thread.sleep(200);
t1.stop();
Thread.sleep(200);
System.out.println("线程停止后的状态: " + proxy.isAlive());
}
}
InfoDemo02.java
package cn.itcast.day174.thread01;
//优先级
//MAX_PRIORITY 10最大
//MIN_PRIORITY 1最小
//NORM_PRIORITY 5默认
//优先级不代表绝对的优先,没有先后顺序,代表的是概率
public class InfoDemo02 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
Thread p1 = new Thread(t1, "t1线程");
MyThread t2 = new MyThread();
Thread p2 = new Thread(t2, "t2线程");
p1.setPriority(Thread.MIN_PRIORITY);//设置优先级
p2.setPriority(Thread.MAX_PRIORITY);//设置优先级
p1.start();
p2.start();
Thread.sleep(100);
t1.stop();
t2.stop();
}
}
说明:这两个例子都是用来取得线程的一些信息。