1.程序、进程、线程概念
程序(Program):为了完成某个特定的任务、功能,而用某种编程语言编写的一组指令的集合。(静止的代码)
进程(Process):操作系统调度程序,是程序的一次运行,是正在运行的一个程序。(运行中代码)
如果一个程序运行两次,就会有两个进程。
在操作系统中,进程是资源分配、调度和管理的最小单位。每个进程在内存中是独立的。
线程(Thread):线程是进程中的一条执行路径。
一个进程中可能有多条路径同时执行,多个并行的(concurrent)线程。
线程是CPU处理器调度的最小单元。
线程可以对进程的内存空间和资源进行访问,并与同一个进程中的其他线程共享,它们从同一个堆中分配对象。由于线程间的通信是在
同一个地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。但是也存在安全问题。
因为其中一个线程对共享的系统资源的操作都会给其他线程带来影响。
由此可知,多线程中的同步(和谐)是非常重要的问题。
举例说明:
(1)单线程:
一旦前面的车,阻塞了,后面的车只能一直等待
(2)多线程:
其中一条路阻塞了,其他路还能继续走
注 意:如果单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,
但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比那个收费人员。如果有某个人不想交钱,
那么那个收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。
单线程: 多线程:
但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
说明:一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,
异常处理线程。当然如果发生异常,会影响主线程。
多线程实现方式
Runnable接口实现多线程
Callable接口实现多线程
Runnable接口实现多线程
即在主线程以外开启另一个线程呢?
Thread类
在Java中负责线程的这个功能的是java.lang.Thread这个类
可以通过创建Thread的实例来创建新的线程
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()又被称为线程体。
必须通过调用Thread类的start()方法来启动一个线程。
JDK1.5之前创建新执行线程有两种方法:
1.一种方法是将类声明为 Thread 的子类。重写run()
2.声明实现 Runnable 接口的类
该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
示例代码
package com.thread;
public class Coding extends Thread{
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println("编写代码....,写了"+i+"行代码");
}
}
}
package com.thread;
public class Chat implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println("聊天....,发了"+i+"条消息");
}
}
}
package com.thread;
public class TestThread {
public static void main(String[] args) {
Coding c = new Coding();
Chat chat = new Chat();
//不能直接调用run(),必须通过start启动线程。由CPU决定JVM什么时候执行谁的run()
c.start();
new Thread(chat).start(); //静态代理模式 target = chat
for(int i=1;i<=100;i++){
System.out.println("main听歌...i="+i);
}
}
}
注意:启动线程
public synchronized void start() {
......
if (threadStatus != 0)
throw new IllegalThreadStateException();
boolean started = false;
try {
start0();
started = true;
} finally {
.......
}
}
private native void start0();
结论:
(1)如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
(2)run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定
(3)想要启动多线程,必须调用start方法
(4)一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”
继承方式和实现方式的区别:
【实现方法的好处】
1)避免了单继承的局限性
2)多个线程可以共享同一个接口子类的对象target,非常适合多个相同线程来处理同一份资源。
案例:
龟兔赛跑
1. 案例题目描述:编写龟兔赛跑多线程程序,设赛跑长度为30米
1)乌龟和兔子每跑完10米输出一次结果。
2)兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒
3)乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒
2. 案例完成思路要求:
1)乌龟一个线程,兔子一个线程
2)两个线程同时开启
3)提示:可以使用Thread.sleep(毫秒数)来模拟耗时
package com.thread;
public class Race implements Runnable {
private String name;
private long speed;
private long restTime;
private long distance;
public Race(String name, long distance, long speed, long restTime) {
super();
this.name = name;
this.distance = distance;
this.speed = speed;
this.restTime = restTime;
}
@Override
public void run() {
int sum = 0;
long start = System.currentTimeMillis();
while (sum < distance) {
try {
Thread.sleep(speed);// 每米距离运动员的时间
} catch (InterruptedException e) {
e.printStackTrace();
return ;
}
sum++;
try {
if (sum % 10 == 0) {
System.out.println(name+"跑了"+sum+"米");
if(sum < distance){// 每10米休息一下
System.out.println(name+"正在休息....");
Thread.sleep(restTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
return ;
}
}
long end = System.currentTimeMillis();
System.out.println(name+"跑"+sum+"米用时"+(end-start)+"毫秒");
}
}
package com.thread;
public class TestRace {
public static void main(String[] args) {
Race rabbit = new Race("兔子", 20, 10, 10000);
Race tortoise = new Race("乌龟", 20, 100, 1000);
new Thread(rabbit).start();
new Thread(tortoise).start();
}
}
Callable接口实现多线程
JDK1.5之后增加的,在java.util.concurrent包,Callable和Future接口
优点:可以获取返回值
缺点:繁琐
Callable接口
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。
Callable和Runnable有几点不同:
Callable定义的方法是call(),而Runnable定义的方法是run()。
call()方法可以有返回值,运行Callable任务可拿到一个Future对象,而run()不能有返回值。
call()方法可以抛出异常,而run()不能抛出异常。
Future接口
Future表示异步计算的结果。
它提供了检查计算是否完成的方法,以等待计算的完成,并检查计算的结果。
通过Future对象可了解任务执行情况,可取消任务的执行,还可以获取任务执行的结果。
Future的cancel(boolean mayInterruptIfRunning)方法可以取消任务的执行,true表示立即中断,false表
示继续等待它的完成
get()方法等待计算完成,获取计算结果。
启动方式一:FutureTask + Thread
FutureTask实现了两个接口:Runnable和Future接口。
因为实现了Runnable接口,所以FutureTask的对象可以作为Thread的target启动。
因为实现了Future接口,所以可以获取返回值。
FutureTask的构造方法:
public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable,V result)
Thread的相关的构造方法:
public Thread(Runnable target)
package com.call;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws Exception {
Race rabbit = new Race("兔子", 20, 10, 1000000);
Race tortoise = new Race("乌龟", 20, 100, 1000);
FutureTask<Long> f1 = new FutureTask<Long>(rabbit);
FutureTask<Long> f2 = new FutureTask<Long>(tortoise);
new Thread(f1).start();
new Thread(f2).start();
// 如果没有这三句,那么两个get()都将等待线程执行完才返回结果
Thread.sleep(10000);// 比如规定比赛时间不能超过10秒
f1.cancel(true);// 为true,比赛时间到,不管兔子是否跑完,都结束兔子的比赛
f2.cancel(true);// 为true,比赛时间到,不管乌龟是否跑完,都结束兔子的比赛
System.out.println("比赛结束");
Long num1 = -1L;
Long num2 = -1L;
try {
num1 = f1.get();
} catch (Exception e) {
System.out.println("兔子没跑完,比赛成绩无效");
}
try {
num2 = f2.get();
} catch (Exception e) {
System.out.println("乌龟没跑完,比赛成绩无效");
}
if(num1>num2){
System.out.println("兔子赢");
}else if(num1<num2){
System.out.println("乌龟赢");
}else{
System.out.println("难分伯仲");
}
}
}
package com.call;
import java.util.concurrent.Callable;
public class Race implements Callable<Long> {
private String name;
private long speed;
private long restTime;
private long distance;
public Race(String name, long distance, long speed, long restTime) {
super();
this.name = name;
this.distance = distance;
this.speed = speed;
this.restTime = restTime;
}
@Override
public Long call() throws Exception {
int sum = 0;
long start = System.currentTimeMillis();
while (sum < distance) {
Thread.sleep(speed);// 每米距离运动员的时间
sum++;
if (sum % 10 == 0) {
System.out.println(name+"跑了"+sum+"米");
if(sum < distance){// 每10米休息一下
System.out.println(name+"正在休息....");
Thread.sleep(restTime);
}
}
}
long end = System.currentTimeMillis();
System.out.println(name+"跑"+sum+"米用时"+(end-start)+"毫秒");
return end - start;
}
}
启动方式二:Executors工厂 + ExecutorService调度服务
package com.call;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestCallable2 {
public static void main(String[] args) throws Exception {
Race rabbit = new Race("兔子", 20, 10, 1000000);
Race tortoise = new Race("乌龟", 20, 100, 1000);
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Future<Long> f1 = threadPool.submit(rabbit);
Future<Long> f2 = threadPool.submit(tortoise);
// 如果没有这三句,那么两个get()都将等待线程执行完才返回结果
Thread.sleep(10000);// 比如规定比赛时间不能超过10秒
f1.cancel(true);// 为true,比赛时间到,不管兔子是否跑完,都结束兔子的比赛
f2.cancel(true);// 为true,比赛时间到,不管乌龟是否跑完,都结束兔子的比赛
System.out.println("比赛结束");
Long num1 = -1L;
Long num2 = -1L;
try {
num1 = f1.get();
} catch (Exception e) {
System.out.println("兔子没跑完,比赛成绩无效");
}
try {
num2 = f2.get();
} catch (Exception e) {
System.out.println("乌龟没跑完,比赛成绩无效");
}
if(num1>num2){
System.out.println("兔子赢");
}else if(num1<num2){
System.out.println("乌龟赢");
}else{
System.out.println("难分伯仲");
}
threadPool.shutdown();//停止服务
}
}
package com.call;
import java.util.concurrent.Callable;
public class Race implements Callable<Long> {
private String name;
private long speed;
private long restTime;
private long distance;
public Race(String name, long distance, long speed, long restTime) {
super();
this.name = name;
this.distance = distance;
this.speed = speed;
this.restTime = restTime;
}
@Override
public Long call() throws Exception {
int sum = 0;
long start = System.currentTimeMillis();
while (sum < distance) {
Thread.sleep(speed);// 每米距离运动员的时间
sum++;
if (sum % 10 == 0) {
System.out.println(name+"跑了"+sum+"米");
if(sum < distance){// 每10米休息一下
System.out.println(name+"正在休息....");
Thread.sleep(restTime);
}
}
}
long end = System.currentTimeMillis();
System.out.println(name+"跑"+sum+"米用时"+(end-start)+"毫秒");
return end - start;
}
}
线程的生命周期
一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止
停止线程
1、自然终止:线程体正常执行完毕
2、外部干涉:推荐使用标识
线程类中定义线程体使用的标识
线程体使用该标识
对外提供改变标识的方法
外部根据条件调用该方法即可
3、Thread.stop()已过时
package com.stop;
public class Race implements Runnable {
private String name;
private long speed;
private long restTime;
private long distance;
private boolean flag = true;//线程类中定义线程体使用的标识
public Race(String name, long distance, long speed, long restTime) {
super();
this.name = name;
this.distance = distance;
this.speed = speed;
this.restTime = restTime;
}
@Override
public void run() {
int sum = 0;
long start = System.currentTimeMillis();
while (flag) {//线程体使用该标识
try {
Thread.sleep(speed);// 每米距离运动员的时间
} catch (InterruptedException e) {
e.printStackTrace();
flag = false;
}
sum++;
try {
if (sum % 10 == 0) {
System.out.println(name+"跑了"+sum+"米");
if(sum < distance){// 每10米休息一下
System.out.println(name+"正在休息....");
Thread.sleep(restTime);
}else{
flag = false;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
flag = false;
}
}
long end = System.currentTimeMillis();
System.out.println(name+"跑"+sum+"米用时"+(end-start)+"毫秒");
}
//对外提供改变标识的方法
public void stop() {
this.flag = false;
}
}
package com.stop;
import java.util.Random;
public class TestRace {
public static void main(String[] args)throws Exception {
Race rabbit = new Race("兔子", 20, 10, 10000);
Race tortoise = new Race("乌龟", 20, 100, 1000);
new Thread(rabbit).start();
new Thread(tortoise).start();
for(int i=0;i<100;i++){
Random r = new Random();
int n = r.nextInt(100);
System.out.println("n="+n);
//外部根据条件调用该方法即可,外部干涉
if(n%10==0){
rabbit.stop();
tortoise.stop();
//注意,rabbit和tortoise不一定立马停止。就好比公安局打电话给收费站说拦截某车,某车得到收费员那里才能停下,没到那里不会停。
}
}
}
}
Thread类的方法
构造方法
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name):创建新的Thread对象
必用方法
1、public void run()线程在被调度时执行的操作
2、public void start()启动线程,并执行对象的run()方法
其他方法
1、public final boolean isAlive()判断线程是否还活着
2、public final String getName()返回线程的名称
3、public final void setName(String name)设置该线程名称
4、public static Thread currentThread()返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
线程的优先级
线程的优先级控制
MAX_PRIORITY(10);
MIN _PRIORITY (1);
NORM_PRIORITY (5);
涉及的方法:
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
线程创建时继承父线程的优先级
说明:优先级只是代表概率,不代表绝对的先后顺序
控制线程的方法
1、public final void join():等待该线程终止。 (有并线的效果)
2、public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
3、public static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作
受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权,即不释放锁。
package com.thread.method;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
* 广告倒计时
*/
public class TestSleep2 {
public static void main(String[] args)throws Exception {
SimpleDateFormat sf = new SimpleDateFormat("mm:ss");
Date start = sf.parse("00:05");
Date end = sf.parse("00:00");
long time =start.getTime();
do{
Date date = new Date(time);
System.out.println(sf.format(date));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
time -= 1000;
if(end.getTime()>=time){
break;
}
}while(true);
}
}
package com.thread.method;
/*
* 抢票
*/
public class TestSleep3 {
public static void main(String[] args)throws Exception {
Ticket t = new Ticket();
new Thread(t,"窗口一").start();
new Thread(t,"窗口二").start();
new Thread(t,"窗口三").start();
}
}
class Ticket implements Runnable{
private int num = 10;
public void run(){
while(true){
if(num<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票,剩余:"+(--num));
}
}
}
出现线程安全问题后续补充