目录
理解线程的概念
- 一个程序至少有一个进程,一个进程至少有一个线程;
- 线程是进程的一个实体,是CPU调度和分派的基本单位;
- 一个线程可以创建和撤销另一个线程;
- 同一个进程中的多个线程之间可以并发执行
- 在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收gc集线程。每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程;
- 多线程可以等价于多任务,当有多个任务要处理,多个任务一起做所消耗的时间比任务串行起来所消耗的时间短;
- 可以通过Thead和Runnable两种方式创建线程;
- 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止;
- 多个线程同时运行的时候,每段时间片中只执行一个线程;
- 线程创建成功启动之后,并不是马上执行,而是等待CPU创建分配时间片之后才会真正执行,多个线程谁先抢到时间片,谁就先执行;
- main主线程也许会在自线程之后执行(下面有例子),也就是说线程的执行顺序,默认情况下人为不能改变执行顺序;
- 线程的生命周期只有一次;
线程的创建方式
Thread
public class ThreadTest {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
MyThread t2=new MyThread();
t2.start();
MyThread t3=new MyThread();
t3.start();
System.out.println("main");
}
static class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<=10;i++) {
System.out.println("该时间片段线程名称:"+getName()+" 打印出来的数据是:"+i);
}
}
}
}
执行结果:
实现Runnable接口
- Runnable并不是线程;只是通过实现接口配合Thread来创建子线程
- 查看Thread类源码的run方法,假如没有给传runnable的话,target是null,run方法是不执行的
@Override
public void run() {
if (target != null) {
target.run();
}
}
public class RunnableTest {
public static void main(String[] args) {
MyRunnable r1=new MyRunnable();
MyRunnable r2=new MyRunnable();
MyRunnable r3=new MyRunnable();
Thread thread=new Thread(r1);
Thread thread2=new Thread(r2);
Thread thread3=new Thread(r3);
thread.start();
thread2.start();
thread3.start();
Thread thread4=new Thread();
thread4.start();
System.out.println("main");
}
static class MyRunnable implements Runnable{
@Override
public void run() {
Thread thread=Thread.currentThread();
for(int i=0;i<=10;i++) {
System.out.println("该时间片段线程名称:"+thread.getName()+" 打印出来的数据是:"+i);
}
}
}
}
打印结果:
线程的5种状态
(创建、就绪、执行、阻塞、终止)
线程常用方法
sleep(毫秒) | 使线程进入阻塞,例如我们实现一个显示时间,隔1秒执行功能,可以sleep(1000) |
getName()/setName | 在run方法中获取线程和设置线程名称 |
currentThread() | 获取当前线程,结合Runnable使用的时候会调用 |
interrupt()和 interrupted() |
|
join() |
|
yiled() | 当前线程让出CPU资源,主动让给其他线程,不会让线程进入阻塞状态,也就是说让本线程从执行中的状态 又回到就绪的状态,以后甚至马上也可能会继续抢占CPU时间片,继续执行 |
setPriority()和getPriority() | 优先级,默认优先级是5,不需要手动改,从1到10,优先级越高,获得的CPU越多 |
setDaemon(true) | 设置为后台线程,或者叫守护线程 |
isAlive() | 判断线程的状态 线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true |
- 多线程设置线程名称和获取名称setName和getName
public class YieldTest {
public static void main(String[] args) {
YieldThread t1=new YieldThread("线程1");
YieldThread t2=new YieldThread("线程2");
YieldThread t3=new YieldThread("线程3");
t1.start();
t2.start();
t3.start();
}
static class YieldThread extends Thread{
public YieldThread(String name) {
setName(name);
}
@Override
public void run() {
System.out.println(getName());
}
}
}
执行结果:
- interrupt()
public class InterruptTest {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
System.out.println("按回车结束线程");
new Scanner(System.in).nextLine();
t1.interrupt();
}
static class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<=10;i++) {
try {
//每隔1秒钟打印一次
System.out.println("该时间片段线程名称:"+getName()+" 打印出来的数据是:"+i);
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("main线程打断了子线程");
}
}
}
}
}
执行结果:当前秒数内抛异常之后,会继续执行后续线程中的内容
显然,打断之后还一直执行是不对的,进行代码优化,判断线程状态进行停止
public class InterruptTest {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
System.out.println("按回车结束线程");
new Scanner(System.in).nextLine();
t1.interrupt();
}
static class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<=10;i++) {
try {
if(isInterrupted()) {
break;
}
//每隔1秒钟打印一次
System.out.println(getName()+" "+i+" "+isInterrupted());
sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("main线程打断了子线程"+isInterrupted());
interrupt();
System.out.println("main线程打断了子线程"+isInterrupted());
}
}
}
}
}
打印结果:
join()
- 比如有两个线程A和B,两个线程都在执行,执行过程中发现,A线程要用到B线程的结果,然后再继续执行,这时候,就需要把并行改成串行,相当于把B线程插入到了A线程中,然后再执行A线程,这时候就需要线程B调用join()方法,进行插入操作;
首先做个测试,假如我要求1到10000000之间所有的质数,我们知道,java启动程序会开启main主线程,需要开启子线程的话,需要我们手动去添加,首先上代码
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
f1();
}
private static void f1() throws InterruptedException {
System.out.println("单线程测试质数个数:");
//单线程测试
long s=System.currentTimeMillis();
JoinThread thread=new JoinThread(1, 10000000);
thread.start();
int sum=thread.count;
s=System.currentTimeMillis()-s;
System.out.println("总共"+sum+"个质数,耗时:"+s);
}
//定义子线程
static class JoinThread extends Thread{
int start;
int end;
int count;
public JoinThread(int start,int end) {
this.start=start;
this.end=end;
if(start<=2) {
count=1;
start=3;
}
}
@Override
public void run() {
for(int i=start;i<end;i++) {
if(isZhishu(i)) {
count++;
}
}
}
private boolean isZhishu(int i) {
double max=1+Math.sqrt(i);
for(int j=2;j<max;j++) {
if(i%j==0) {
return false;
}
}
return true;
}
}
}
打印结果:
明显是错误的,为什么会这样呢?答案很简单,在子线程还没执行完的时候,main主线程就开始调用了输出语句,获取结果了,所以肯定是错误的,这时候就要用到join方法,main主线程等待子线程执行完再执行,把子线程调用join,插入到main线程
代码块改f1()方法添加,改成如下:
private static void f1() throws InterruptedException {
System.out.println("单线程测试质数个数:");
//单线程测试
long s=System.currentTimeMillis();
JoinThread thread=new JoinThread(1, 10000000);
thread.start();
thread.join();
int sum=thread.count;
s=System.currentTimeMillis()-s;
System.out.println("总共"+sum+"个质数,耗时:"+s);
}
这时候打印结果:
结果正确了,但是发现耗时用了6秒多,效率很低,这时候,可以优化,把单线程改成多个子线程:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
f5();
}
private static void f5() throws InterruptedException {
System.out.println("5个线程测试质数个数:");
//单线程测试
long s=System.currentTimeMillis();
JoinThread[]threads=new JoinThread[5];
for(int i=0;i<threads.length;i++) {
threads[i]=new JoinThread(2000000*i,2000000*(i+1));
threads[i].start();
// threads[i].join();//千万不要在这里进行join,否则会变成多个子线程串行,和单线程无区别了
for(JoinThread joinThread:threads) {
joinThread.join();
}
int sum=0;
for(int i=0;i<threads.length;i++) {
sum+=threads[i].count;
}
s=System.currentTimeMillis()-s;
System.out.println("总共"+sum+"个质数,耗时:"+s);// TODO Auto-generated method stub
}
//定义子线程
static class JoinThread extends Thread{
int start;
int end;
int count;
public JoinThread(int start,int end) {
this.start=start;
this.end=end;
if(start<=2) {
count=1;
start=3;
}
}
@Override
public void run() {
for(int i=start;i<end;i++) {
if(isZhishu(i)) {
count++;
}
}
}
private boolean isZhishu(int i) {
double max=1+Math.sqrt(i);
for(int j=2;j<max;j++) {
if(i%j==0) {
return false;
}
}
return true;
}
}
运行结果:
-
后台进程setDaemon(true)
public class daemonTest {
public static void main(String[] args) {
System.out.println("输入换行停止t1线程:");
DaemonThread t1=new DaemonThread();
t1.start();
Thread t2=new Thread(){
public void run() {
new Scanner(System.in).next();
t1.interrupt();
};
};
//一定要写在执行之前
// t2.setDaemon(true);
t2.start();
}
static class DaemonThread extends Thread{
@Override
public void run() {
for(int i=0;i<=5;i++) {
try {
System.out.println(i);
sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程异常");
}
}
}
}
}
执行结果
控制台依然在等待用户输入
假如把t2.setDaemon(true);这句话放开,设置成后台进程,那么线程执行完就会结束,不会等待用户操作了,执行结果如图: