线程简介
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
多任务
你可以一边开车一边打电话 咳咳 科目四学了这是不可以的奥
你可以一边吃鸡一边上厕所
本质
看似多个任务在同时做,实质上,你的大脑(cpu)只是在同一时间,依旧只做了一件事情。
进程与线程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
一个操作系统中可以有多个进程,而一个进程又可以开辟多个线程。
线程是 CPU调度和执行的单位
本质上是CPU轮询时间片
线程创建
线程的三种创建方式
- Thread
- 继承Thread类实现
- Runnable
- 实现Runnable接口
- Callable
- 实现Callable接口
继承Thread类实现
创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。
public class TestThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000 * 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是子线程");
}
}
public static void main(String[] args) throws InterruptedException {
Thread testThread = new TestThread();
testThread.start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000 * 1);
System.out.println("我是主线程");
}
}
}
小试牛刀:多线程下载图片
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
/*
@author luckyFang
@date 2020 10 22
@desc thread downloader demo .
*/
// 下载器类
class Downloader extends Thread{
private String url;
private String name;
public Downloader(String url,String name) {
this.url = url;
this.name=name;
}
@Override
public void run() {
downloadFile();
}
private void downloadFile(){
try {
URL url = new URL(this.url);
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream(this.name);
int read =0;
byte[] buffer =new byte[1024];
while ((read = inputStream.read(buffer))!=-1){
fileOutputStream.write(buffer,0,read);
}
fileOutputStream.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class TestDownload {
public static void main(String[] args) {
new Downloader("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2047170863,1302224659&fm=26&gp=0.jpg","a.png").start();
new Downloader("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3380021095,725635716&fm=26&gp=0.jpg","b.png").start();
new Downloader("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1912740209,3509854380&fm=26&gp=0.jpg","c.png").start();
}
}
实现Runnable接口
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。
/**
* @author luckyFang
* @date 2020 10 22
* @desc 实现Runnable 来开辟多线程
*/
public class TestRunnable {
// 静态内部类
public static class Inner implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程");
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Inner inner = new Inner();
Thread thread = new Thread(inner);
thread.start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000 * 1);
System.out.println("我是主线程");
}
}
}
当然如果你的java基础够好的话我们可以将上面代码继续简化
public static void main(String[] args) throws InterruptedException {
new Thread(new Inner()).start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000 * 1);
System.out.println("我是主线程");
}
}
继续深入
public Thread(Runnable target) {
this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}
package java.lang;
@FunctionalInterface
public interface Runnable {
void run();
}
通过阅读Thread 类的 代码我们不难发现,他有一个构造函数是Runnable 类型。
那么 通过简单的揣摩,我们也发现Runnable是一个函数式接口,意味着我们可以使用匿名内部类
。
匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程");
try {
Thread.sleep(1000 * 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
既然可以使用匿名内部类,那么同理我们也可以使用lambda表达式来简化我们的代码
lambda表达式 jdk8 新特性之一,是一个匿名函数,Lambda表达式基于数学中的λ演算得名。
格式: ()->{}
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程");
}
}).start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000 * 1);
System.out.println("我是主线程");
}
}
实现 callable
实现callable 接口和上面不同的是,可以拿到返回值。并且可以丢进线程池执行。
import java.util.concurrent.*;
/**
* @author luckyFang
* @date 2020 10 29
* @desc 实现 callable接口
* 线程池的使用
*/
public class TestCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("我在写BUG!");
}
return true;
}
public static void main(String[] args) {
TestCallable testCallable1 = new TestCallable();
TestCallable testCallable2 = new TestCallable();
TestCallable testCallable3 = new TestCallable();
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 投递任务
Future<Boolean> rs1 = executorService.submit(testCallable1);
Future<Boolean> rs2 = executorService.submit(testCallable1);
Future<Boolean> rs3 = executorService.submit(testCallable1);
// 拿到返回值
try {
System.out.println(rs1.get());
System.out.println(rs2.get());
System.out.println(rs3.get());
// 关闭线程池
executorService.shutdownNow();
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程问题
虽然线程可以大幅度提升我们的程序处理能力,同时也会带来一些问题。
下面是一个模拟售票代码,我们创建了4个窗口,来实现售票。分别是A
B
C
D
。
/**
* @author luckyFang
* @date 2020 10 29
* @desc 多线程资源争抢问题
*/
public class TestTicket implements Runnable {
private int ticketNums=100;
@Override
public void run() {
try {
while (true){
if (ticketNums<=0){
break;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestTicket testTicket = new TestTicket();
new Thread(testTicket,"A").start();
new Thread(testTicket,"B").start();
new Thread(testTicket,"C").start();
new Thread(testTicket,"D").start();
}
}
运行结果
B-->98
C-->97
A-->100
D-->99
D-->96 ------
C-->96 ------
B-->96 ------
A-->96 ------
A-->94
C-->92
D-->93
B-->95
A-->91
B-->90
C-->88
D-->89
C-->85
B-->87
A-->86
D-->85
从上面可以看出,程序并没有按照我们想象的那样来执行。
多个线程同时操作一个数据出现了资源争抢问题。
线程状态
线程有五大状态
- 新生
new Thread()
- 就绪
new Thread().start()
- 阻塞
sleep wait 同步锁
- 运行
正在运行的线程
- 死亡
线程中断或者结束
优雅de停止一个线程
线程的停止一直是一个备受争议的问题,假如当前线程正在读写非常重要的数据,结果被异常终止了,后果非常严重。因此不推荐被动结束线程。
而是在线程内部创建一个标志位让线程自己停下来。
示例代码
public class TestStopThread implements Runnable {
private Boolean flag = true;
public static void main(String[] args) {
TestStopThread testStopThread = new TestStopThread();
new Thread(testStopThread).start();
try {
Thread.sleep(2*1000);
testStopThread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (this.flag){
System.out.println("我在写BUG!");
}
}
private void stop(){
this.flag=false;
}
}
线程休眠
Sleep 不会释放锁
客户不给钱就多加Sleep
守护线程
高级线程 和 低级线程
高级线程:JVM会在终止前等待其完成任务
低级线程:和上面恰恰相反(不会阻止JVM的退出)
守护线程就是一个低级线程
创建守护线程
new Thread().setDaemon(true);
示例代码
/**
* @author luckyFang
* @date 2020/11/19 22:35
* @file TestThreadDaemon.java
* @desc 守护线程
*/
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("I'm blessing with you!");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 30000; i++) {
System.out.println("Happy!");
}
}
}
public class TestThreadDaemon {
public static void main(String[] args) {
// god
Thread god = new Thread(new God());
god.setDaemon(true);
god.start();
// you
new Thread(new You()).start();
}
}
按照我们正常的编程思维,上面我们写了个 死循环,程序会无限运行下去,但是实际情况去却不是这样,因为
守护线程是一个低级线程,不会影响JVM的退出
线程同步
本质上是线程的等待机制,排队访问+限制(锁)
要知道每个Java对都有一个锁,一旦一个线程拿到这个对象的锁后,将独占这个对象,其他线程访问必须要等待。
比如说一个厕所在正常情况下只能被一个人使用,其他人如果要使用必须要等待。
这好吗,这不好。
加入 A独占厕所很长时间,外面又有很多人要用,这样的话,效率堪忧!
多线程竞争下,加锁,释放锁,会引起较多的上下文切换,cpu调度频繁,从而导致性能问题。
同步代码块和同步关键字
synchronized
改造不安全代码为安全代码
/**
* @author luckyFang
* @date 2020 10 29
* @desc 多线程资源争抢问题--解决 加锁
*/
public class TestTicket implements Runnable {
private int ticketNums=100;
@Override
public synchronized void run() {
try {
while (true){
if (ticketNums<=0){
break;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestTicket testTicket = new TestTicket();
new Thread(testTicket,"A").start();
new Thread(testTicket,"B").start();
new Thread(testTicket,"C").start();
new Thread(testTicket,"D").start();
}
}
A-->100
A-->99
A-->98
A-->97
A-->96
A-->95
A-->94
A-->93
A-->92
A-->91
A-->90
A-->89
A-->88
同样我们也可以使用 synchronized
代码块来锁对象
/**
* @author luckyFang
* @date 2020 10 29
* @desc 多线程资源争抢问题
*/
public class TestTicket implements Runnable {
private int ticketNums=100;
@Override
public void run() {
synchronized(this){
try {
while (true){
if (ticketNums<=0){
break;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestTicket testTicket = new TestTicket();
new Thread(testTicket,"A").start();
new Thread(testTicket,"B").start();
new Thread(testTicket,"C").start();
new Thread(testTicket,"D").start();
}
}
死锁
多线程下,多个线程各自占有一些共享资源,并且互相等待。
面试官:告诉我什么是死锁我就给你offer
程序猿:你给我offer我就告诉你什么叫死锁
产生死锁的四个必要条件
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
Lock锁
通过显式定义同步锁
import java.util.concurrent.locks.ReentrantLock;
/**
* @author luckyFang
* @date 2020/11/19 23:44
* @file TestLockTicket.java
* @desc
*/
public class TestLockTicket implements Runnable {
private static final ReentrantLock lock;
static {
lock = new ReentrantLock();
}
private int ticketNums = 100;
@Override
public void run() {
try{
while (true) {
if (ticketNums <= 0) {
break;
}
Thread.sleep(100);
lock.lock();
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestTicket testTicket = new TestTicket();
new Thread(testTicket, "A").start();
new Thread(testTicket, "B").start();
new Thread(testTicket, "C").start();
new Thread(testTicket, "D").start();
}
}
线程通信
生产者消费者(管程法)
生产者:
- 生产产品前:通知消费者等待
- 生产产品后:通知消费者消费
消费者:
- 消费产品后:通知生产者供给
/**
* @author luckyFang
* @date 2020/11/20 0:15
* @file TestConsumer.java
* @desc 生产者消费者模型
*/
import java.util.*;
/**
* 产品
*/
class Product{
}
/**
* 生产者
*/
class Productor implements Runnable{
private Container<Product> container;
public Productor(Container<Product> container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Product());
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable{
private Container<Product> container;
public Consumer(Container<Product> container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.pop();
}
}
}
/**
* 缓冲区
* @param <T>
*/
class Container<T> {
private static final int BUFFER_SIZE = 10;
private List<T> list =new ArrayList<>();
private int count;
public synchronized void push(T e){
// over
if(count == BUFFER_SIZE) {
try {
this.wait();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
list.add(e);
++count;
this.notifyAll();
System.out.println("生产产品"+count);
}
public synchronized T pop(){
if (count==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T product = list.get(--count);
this.notifyAll();
System.out.println("消费产品"+count);
return product;
}
}
public class TestConsumer {
public static void main(String[] args) {
Container<Product> productContainer = new Container<>();
new Thread(new Productor(productContainer)).start();
new Thread(new Consumer(productContainer)).start();
}
}