首先需要明确三个概念:程序,进程,线程
程序:分为可执行程序,不可执行程序。例如我们写的代码中main函数里的程序为可执行程序。
程序的官方解释:是指令、数据及其组织形式的描述。
进程:运行起来的程序,叫做进程。
进程的官方解释:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是程序的实体。
如下图所示,腾讯QQ在运行,是一个进程。
线程:让一个进程能够同时处理多件事情。是操作系统能够进行运算调度的最小单位,它被包含在进程当中,是进程中的实际运作单元。
进程与线程之间的联系:
进程与线程之间的区别:
下图解释了程序,进程,线程之间的关系,它们之间呈现树形结构。
串行:按照指令顺序依次执行。
并行:真正的同时执行。
并发:CPU的快速切换。
并发和并行都是充分利用cpu的手段。
大部分操作系统都支持多线程并发操作,人们一遍聊天,还能一遍打开网易云听歌,还能同时上网查资料等,这些看上去是在同时进行,事实是cpu在某一时刻只能执行一个程序,即一个进程,它是通过快速切换来完成并发执行。
创建线程的方式:new Thread对象。
定义线程执行单元的方式有三种:
三种方式的区别:
继承类与实现接口的区别:
Callable和Runnable接口的区别:
以下代码是Callable的使用:
FutureTask中的get()方法:运行在主线程中,get方法调用之后如果子线程运行结束,正常返回返回值,如果子线程未结束那么将会阻塞在这里直到子线程执行完返回返回值。
package com.tulun.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
class CallableThread implements Callable{
@Override
public Object call() throws Exception {
System.out.println("线程的名字是:" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(7);
return "abc";
}
}
public class CallableThreadTest {
public static void main(String[] args) { //主线程
CallableThread callable = new CallableThread();
FutureTask<String> task = new FutureTask<>(callable); //获取未来数据的方式
Thread thread = new Thread(task);
thread.setName("子线程");
thread.start();
try {
System.out.println(task.get());//运行在主线程中
//get方法调用之后如果子线程运行结束,正常返回返回值
//如果子线程未结束那么将会阻塞在这里直到子线程执行完返回返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
执行结果:
线程的名字是:子线程
abc
start()方法和run()方法的区别:
每个线程对应有一个线程优先级的数值。
优先级的取值范围是1-10,一般默认为5。
优先级高的被cpu先执行的概率大,并不是优先级高的一定会被先执行。
在项目开发中不能使用优先级来完成一些特定任务。
优先级的特性:
优先级没有具体的使用场景。
以下代码说明,优先级高的线程并不一定先执行完。
package com.tulun.thread;
import java.util.Random;
class MyThread1 extends Thread {
@Override
public void run() {
long start = System.currentTimeMillis();
long sum = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 50000; j++) {
Random random = new Random();
random.nextInt();
sum = sum + i;
}
}
long end = System.currentTimeMillis();
System.out.println("-------------thread 1 use time = " + (end - start));
}
}
class MyThread2 extends Thread {
@Override
public void run() {
long start = System.currentTimeMillis();
int sum = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 50000; j++) {
Random random = new Random();
random.nextInt();
sum = sum + i;
}
}
long end = System.currentTimeMillis();
System.out.println("*****************thread 2 use time = " + (end - start));
}
}
public class PriorityTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
MyThread1 myThread1 = new MyThread1();
myThread1.setPriority(5);
myThread1.start();
MyThread2 myThread2 = new MyThread2();
myThread2.setPriority(6);
myThread2.start();
}
}
}
执行结果:
-------------thread 1 use time = 510
*****************thread 2 use time = 637
-------------thread 1 use time = 658
*****************thread 2 use time = 635
*****************thread 2 use time = 638
-------------thread 1 use time = 656
-------------thread 1 use time = 773
-------------thread 1 use time = 848
*****************thread 2 use time = 747
*****************thread 2 use time = 747
守护线程是一个特殊的线程。
在后台执行的线程,当所有非守护线程结束之后,守护线程会自动结束。
守护线程必须在守护前设置,若在守护后设置就会报错。
应用场景:
以下代码是守护线程测试代码:
将线程t设置为守护线程,while死循环。当主线程和r线程结束了的时候,t线程会自动结束。这就是守护线程。
package com.tulun.thread;
import java.util.concurrent.TimeUnit;
/**
* 守护线程test
*/
class DaemonThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(i+" Daemon");
}
}
}
public class DaemonTest {
public static void main(String[] args) {
DaemonThread runable = new DaemonThread();
Thread r = new Thread(runable);
//匿名内部类
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName());
}
}
});
t.setDaemon(true);
t.start();
r.start();
try {
TimeUnit.SECONDS.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
注:线程默认命名方式:Thread-编号
线程的生命周期:指一个线程从生到死的过程。
一个线程从生到死经历的过程:New,Runnable,blocked,waiting,time_waiting,terminated。
线程都会经历的状态:New,Runnable,terminated。
下面这幅图描述了线程的生命周期:
以下对线程生命周期的每个状态进行解释:
New状态:
还没有执行,只是new出来了。
当我们用关键字new创建一个thread对象时,此时它并不是出于执行状态,因为没有调用start方法启动该线程,那么线程的状态为new状态。
准确地说,它只Thread对象的状态,因为没有start之前,该线程根本就不存在,与你用关键字new创建一个普通的java对象没有什么区别new 状态通过start方法进入runnable状态。
Runnable状态:
Runnable状态:
分成了两种状态可执行(就绪态)和运行态。
线程对象进入就可执行(就绪态)态必须调用start方法,那么此时才是真正的在jvm进程中创建一个线程。
线程一经启动就可以立即得到执行?
线程的运行和进程一样都要听命于cpu的调度,那么我们把中间状态成为可执行状态(就绪态),它是具备执行的资格,但是并没有真正的执行起来而是在等待cpu的调度。
由于存在运行状态,所以不会直接进入blocked、waiting、timed waiting和terminated状态,即使是在现层的执行逻辑中调用wait、sleep或者其他block的io操作等,也必须获得cpu的调度执行权才可以,严格来讲,可执行(就绪态)的线程只能意外终止或者进入运行状态;
Blocked状态:
线程阻塞状态
进行某个阻塞的io操作,比如因网络数据的读写而进入blocked状态。
获取某个锁资源,从而加入到该锁的阻塞队列中进入blocked状态。
Waiting状态:
一个线程在等待执行一个(唤醒)动作时,该线程进入waiting状态。
进入这个状态不能自动唤醒,必须等待另一个线程发出特定的指令才能够被唤醒。
Timed_waiting状态:
和上面的类似,只是这个状态的线程不会一直等下去,会有一个超时时间。
Terminated状态:
terminated是一个线程的最终状态,在该状态中线程将不会切换到其他任何状态,线程进入terminated状态,意味着该线程的整个生命周期结束了。
三种结束情况:
简言之,能让线程插队。
一个线程处于waiting态时,是不会运行的。
以下用代码来说明:
子线程ReceiveThread1 调用join方法,可以让子线程插到主线程之前运行,主线程此时处于waiting态,等待子线程结束唤醒。
主线程waiting态,那么后面的代码在这段时间将不会被执行,直到调用join方法的这个线程执行完之后才会执行后续代码。
package com.tulun.thread;
/**
* 测试join方法
*/
class ReceiveThread1 extends Thread {//子线程的定义
Thread thread;
public ReceiveThread1(String str, Thread thread) {
super(str);
this.thread = thread;
}
@Override
public void run() { //线程的执行单元
for (int i = 0; i < 20; i++) {
System.out.println(this.getName());
System.out.println("state: " + thread.getState());
}
}
}
public class ThreadTestJoin {
public static void main(String[] args) { //主线程
//处理发送数据
ReceiveThread1 thread = new ReceiveThread1("接收数据",Thread.currentThread());
// thread.setName("接收线程");
thread.start(); //启动子线程
try {
/*
子线程插到了主线程之前,主线程处于waiting态,等待子线程结束唤醒
*/
thread.join();
//thread.join(1);//超时时间为1ms,如果超时了,即1ms内子线程未结束,主线程会被唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
//当子线程结束,主线程被唤醒
//主线程waiting 那么后面的代码在这段时间将不会被执行 直到调用join方法的这个线程执行完之后才会执行后续代码
System.out.println("state: " + Thread.currentThread().getState()); //主线程被唤醒执行后的状态
for (int i = 0; i < 5000; i++) {
System.out.println("发送数据");
}
}
}
CountDownLatch
解释:
源码:
构造方法
//参数count为计数值
public CountDownLatch(int count) { };
常用方法
//什么时候count减为0,后面的代码才开始执行。
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1,一个线程执行完进行减一操作
public void countDown() { };
示例
package com.tulun.thread;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
class AirPlaneS implements Runnable{
private List<String> list;
private CountDownLatch latch;
public AirPlaneS(List<String> list,CountDownLatch latch) {
this.list = list;
this.latch = latch;
}
@Override
public void run() {
list.add(Thread.currentThread().getName()+"8:00");
latch.countDown();
}
}
public class AirLineCountDown {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(3);
AirPlaneS airPlaneS = new AirPlaneS(list,latch);
Thread t1 = new Thread(airPlaneS,"南方航空");
Thread t2 = new Thread(airPlaneS,"东方航空");
Thread t3 = new Thread(airPlaneS,"海南航空");
t1.start();
t2.start();
t3.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
注:如果count数大于实际线程数,调用await方法时就永远不会减为0,主线程会一直等待。
CyclicBarrier
解释:
让所有线程都等待完成后才会继续下一步行动。
源码:
构造方法
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
常用方法
//所有线程相互等待直到把barrier中的值减为0
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
示例
package com.tulun.thread;
/**
* CyclicBarrier
*/
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
class AirPlaneSearch implements Runnable {
List<String> list;
CyclicBarrier barrier;
public AirPlaneSearch(List<String> list, CyclicBarrier barrier) {
this.list = list;
this.barrier = barrier;
}
@Override
public void run() { //查询各个航空公司的信息
System.out.println("开始从" + Thread.currentThread().getName() + "查询航班信息");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (list) {
list.add(Thread.currentThread().getName() + "8:00");
}
try {
//所有的线程相互等待直到把barrier中的值减为0 A: 2 B 1 C
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class AirLineCircle {
public static void main(String[] args) {
final List<String> list = new LinkedList<>();
//有几个子线程就设置位几
// 什么时候Runnable 减为0 Runnable中的方法被执行
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
Iterator<String> iterator = list.iterator();
System.out.println("--------------------------------------------");
System.out.println("所查询的航班信息如下: ");
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
});
AirPlaneSearch runnable = new AirPlaneSearch(list,barrier);
Thread t1 = new Thread(runnable, "南方航空");
Thread t2 = new Thread(runnable, "东方航空");
Thread t3 = new Thread(runnable, "海南航空");
t1.start();
t2.start();
t3.start();
}
}
两者区别:
在Java中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。
interrupt方法不一定会真正"中断"线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然的调用线程的interrupt方法,以为这样就能取消线程。
join(),会判断线程中断位,如果中断位为true,就会抛出异常,从而结束线程的等待状态。
public boolean isInterrupted();//测试此线程是否已被中断。此方法不影响线程的中断状态
public void interrupt();//中断线程,将线程中断位从false改为true。
public static boolean interrupted();//测试此线程是否已被中断,线程中断位会被擦除。
实际应用:心跳机制,用来维护客户端和服务器之间的连接。
能否抛出异常是判断能否中断的一个标志。
线程中断的方法:
Object的wait/wait(long)/wait(long,int)方法
Thread的sleep(int)/sleep(long,int)
Thread的join/join(long)/join(long,int)方法
示例:
使用join方法,使子线程先运行,设置计数器i,当i计数到20时,调用interrupt方法,中断主线程的等待,并且将线程中断位由false变为true。
主线程处于waiting状态,会判断线程中断位,当线程中断位为true时,就会抛出异常从而结束线程的等待状态。
package com.tulun.thread;
import java.util.concurrent.TimeUnit;
class ReceiveThread11 extends Thread {//子线程的定义
Thread thread;
private int i = 0;
public ReceiveThread11(String str, Thread thread) {
super(str);
this.thread = thread;
}
@Override
public void run() { //线程的执行单元
while (true){
i++;
System.out.println(this.getName());
if(i == 20){
//中断了主线程的等待状态 将线程中断位 由flase --》 true
thread.interrupt();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadInterrupt {
public static void main(String[] args) { //主线程
//处理发送数据
ReceiveThread11 thread = new ReceiveThread11("接收数据",Thread.currentThread());
thread.start(); //启动子线程
try {
thread.join();
//主线程处于waiting状态 会判断线程中断位
// 如果中断位位true --》 就会抛出异常从而结束线程的等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true){
System.out.println("发送数据");
try {
//主线程处于waiting状态 会判断线程中断位
// 如果中断位位true --》 就会抛出异常从而结束线程的等待状态
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如何让一个线程结束:
正常结束:一个线程的生命周期正常结束掉。
非正常结束:有以下几种方式。
法一:让子线程运行5s后调用interrupt方法改变其中断标识位,通过判断中断标志位为true来使子线程break,结束。
缺点:若有sleep(),join等方法,中断状态位会被擦除,使得判断无效
法二:利用sleep方法可抛出异常的特点,5s后调用interrupt方法,sleep抛出异常,此时break退出循环,结束线程。
缺点:每次打印的时候都会隔1nm,改变了本来的执行逻辑,对效率有很大的影响
法三:使用volatile关键字,设置flag,让子线程运行5s后,改变flag的值,子线程通过判断flag的值来break出循环,结束线程。
示例:
以下代码目的是让子线程运行5s后关闭。
package com.tulun.thread;
import java.util.concurrent.TimeUnit;
//经常用来结束线程的方式
class BThread implements Runnable{
//存在线程安全问题
private static volatile boolean flag = false;
@Override
public void run() {
/**
* 法一
* 缺点:若有sleep(),join等方法,中断状态位会被擦除,使得判断无效
*/
// while(true){
// if(Thread.currentThread().isInterrupted()){
// break;
// }
// System.out.println("接受数据");
// }
/**
* 法二
* 缺点:每次打印的时候都会隔1nm,改变了本来的执行逻辑,对效率有很大的影响
*/
// while(true){
// try {
// TimeUnit.NANOSECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// break;
// }
// System.out.println("接受数据");
// }
/**
* 法三
*/
while(true){
if(flag){
break;
}
System.out.println("接受数据");
}
}
public static void closeThread(){
flag = true;
}
}
public class CloseThread {
public static void main(String[] args) {
BThread runable = new BThread();
Thread t = new Thread(runable);
t.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
BThread.closeThread();//只是将主线程副本中flag变为true
System.out.println(t.isInterrupted());
}
}
volatile能够解决缓存一致性问题。
以上代码中的flag,主线程和子线程都要操作flag。
加上volatile之后,主线程改变flag的值之后立马会将这个值同步到主内存中。
子线程会一直判断主内存中的值是否被修改,如果被修改的话立刻进行同步。