携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第39天,Java 多线程入门 - 掘金 (juejin.cn)
前言
Java 多线程入门,了解多个线程是如何同时进行的。
一、线程简介
多任务 -> 多进程 -> 多线程
多任务)在多道系统中,允许计算机宏观上执行多道程序(区别于单道系统,内存中可有多道程序)。
多进程)当一个程序在等I/O时,就可以把CPU让给另一个程序,程序执行走走停停,因此引入进程的概念来更好的描述和控制程序的并发执行,从而提高系统的吞吐率和资源利用率。
多线程)引入线程是为了多道程序在并发执行时所付出的时空开销,提高系统的并发性能。同一进程中多个线程可以并发执行,而同一进程下的线程共享进程所拥有的所有资源,所以同一进程下的线程切换就会减少很多时空开销。(轻量级+共享性)
二、线程实现
1)继承Thread,重写run方法
2)实现Runnable接口
3)实现Callable接口
1、Thread
实例1:第一步,自定义线程类继承Thread类;第二步,重写run方法,编写线程执行体;第三步,创建线程对象,调用start方法启动线程。
package com.xhu.java.thread;
public class TestThread extends Thread {
//类似于GC线程的其它线程
@Override
public void run() {
//run方法线程
for (int i = 0; i < 5; i++) {
System.out.println("run线程");
}
}
//程序的主线程
public static void main(String[] args) {
TestThread t1 = new TestThread();
//调用start,开启线程
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程");
}
}
}
注:多个线程的先后执行是否CPU的调度算法实现的,不能人为干预。
package com.xhu.java.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//用多线程同步下载图片
public class TestDownPicture extends Thread {
String url;
String fileName;
public TestDownPicture(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
WebDownLoader wdl = new WebDownLoader();
wdl.downLoader(this.url, this.fileName);
System.out.println("下载完毕!"+ fileName);
}
public static void main(String[] args) {
TestDownPicture tdp1 = new TestDownPicture("1.jpg", "d:down1.jpg");
TestDownPicture tdp2 = new TestDownPicture("1.jpg", "d:down2.jpg");
TestDownPicture tdp3 = new TestDownPicture("1.jpg", "d:down3.jpg");
tdp1.start();
tdp2.start();
tdp3.start();
}
}
class WebDownLoader {
//下载方法
public void downLoader(String url, String fileName) {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、Runnable
package com.xhu.java.thread;
//执行线程需要需要将this丢入runnable的实现类,再调start方法。
//推荐使用runnable接口
public class TestRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程1");
}
//主线程
public static void main(String[] args) {
TestRunnable tr = new TestRunnable();
//代理
Thread t = new Thread(tr);
t.start();
}
//1.实现Runnable,重写run,2.代理,3.start
}
3、买票案例
package com.xhu.java.thread;
//同一对象,多个线程来操作。
//模拟买票
public class TestBuyTicket implements Runnable {
//该对象只有5张票
private int ticketNums = 15;
//抢票线程体
@Override
public void run() {
while (true) {
if (ticketNums < 1) {
break;
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNums-- + "票");
//每抢一张票,不允许马上又抢
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestBuyTicket t1 = new TestBuyTicket();
//处理同一对象,同步问题就需注意
new Thread(t1, "小李").start();
new Thread(t1, "小王").start();
new Thread(t1, "黄牛").start();
}
}
4、龟兔赛跑
package com.xhu.java.thread;
//多线程模拟龟兔赛跑
public class Race implements Runnable {
boolean flag = false;
@Override
public void run() {
int i = 0;
while (++i <= 100) {
//第一名是否产生
if (flag) break;
//终结比赛
if (i >= 100) {
flag = true;
System.out.println("winner is " + Thread.currentThread().getName());
break;
}
//模拟兔子睡觉
if (i >> 1 == 0 && "兔子".equals(Thread.currentThread().getName())) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
}
}
public static void main(String[] args) {
Race r = new Race();
new Thread(r, "兔子").start();
new Thread(r, "乌龟").start();
}
}
5、Callable
package com.xhu.java.thread;
import java.util.concurrent.*;
//1.实现Callable接口来实现多线程
public class TestCallable implements Callable {
//2.重写call方法
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "正在执行");
return null;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.实例多线程对象
TestCallable tc = new TestCallable();
//4. 开启服务
ExecutorService ser = Executors.newFixedThreadPool(1);
//5. 提交执行
Future submit = ser.submit(tc);
//6. 获取结果
Integer obj = (Integer) submit.get();
//7. 断开服务
ser.shutdownNow();
}
}
三、静态代理
package com.xhu.java.thread;
//模拟静态代理
//1.需要实现同一个接口
//2.代理对象实现目标对象不能实现或很复杂的(可能是为了更好的封装)
public class TestStaticProxy {
public static void main(String[] args) {
//Runnable 的静态代理
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程代码段");
}
}).start();
//对比结婚的静态代理
new WeddingCompany(new Marry() {
@Override
public void happyMarry() {
System.out.println("男主女主结婚啦");
}
}).happyMarry();
}
}
//同一接口:和结婚相关
interface Marry {
void happyMarry();
}
//情侣结婚
class Lovers implements Marry {
String manName;
String womanName;
public Lovers(String manName, String womanName) {
this.manName = manName;
this.womanName = womanName;
}
@Override
public void happyMarry() {
System.out.println("男主:" + this.manName + ",女主:" + this.womanName + ",结婚啦");
}
}
//婚庆公司,提供其它功能,让结婚变得更有仪式感
class WeddingCompany implements Marry {
Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
before();
this.target.happyMarry();
after();
}
private void after() {
System.out.println("打扫现场,收取尾款");
}
private void before() {
System.out.println("布置现场");
}
}
四、Lambda
函数式接口:一个接口里面只有一个抽象方法。
函数式编程:Lambda表达式,保留函数式接口的匿名内部类的核心部分。(形参)-> {重写的语句},如
(int a)->System.out.println(a)
or(a)->System.out.println(a)
ora->System.out.println(a)
优点:留下核心逻辑,简洁,避免匿名内部类过多。
package com.xhu.java.thread;
public class TestLambda {
public static void main(String[] args) {
Lambda l = ()-> System.out.println("lambda");
l.show();
}
}
interface Lambda {
void show();
}
五、线程状态
1、线程停止
package com.xhu.java.thread;
//1.JDK提示的过时的方法stop、destroy不建议使用
//2.建议使用标志位来停止线程。
public class TestStop implements Runnable {
//设置标志位来停止线程
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
System.out.println("run Thread " + i++);
}
}
public void stop() {
this.flag = false;
}
//主线程
public static void main(String[] args) {
TestStop ts = new TestStop();
new Thread(ts).start();
for (int i = 0; i < 10000; i++) {
System.out.println("main" + i);
if (i == 9000) {
ts.stop();
System.out.println("该线程该停止了");
}
}
}
}
2、线程休眠
package com.xhu.java.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟时间
//模拟计时器
public class TestSleep {
//模拟时间
public static void stimulateTime() throws InterruptedException {
Date date = null;
int num = 10;
while (num-- > 0) {
date = new Date();
System.out.println(new SimpleDateFormat("hh:mm:ss").format(date));
Thread.sleep(1000);
}
}
//模拟计时器
public static void down() throws InterruptedException {
int num = 10;
while (num-- > 0) {
System.out.println(num);
Thread.sleep(1000);
}
}
public static void main(String[] args) throws InterruptedException {
stimulateTime();
down();
}
}
3、线程礼让
1)让正在进行的线程暂停,但不阻塞。
2)将线程从运行态转换为就绪态,跟其它线程在同一就绪队列。
3)CPU根据调度算法来重新调线程,所以礼让不一定会成功。
package com.xhu.java.thread;
public class TestYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "->start");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "->end");
}
public static void main(String[] args) {
TestYield ty = new TestYield();
new Thread(ty, "one").start();
new Thread(ty, "two").start();
}
}
4、线程合并
线程插队
package com.xhu.java.thread;
//线程插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("vip线程" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin tj = new TestJoin();
Thread t = new Thread(tj);
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main排队" + i);
if (i == 50) {
t.join();
}
}
}
}
5、五个状态
package com.xhu.java.thread;
public class TestThreadState {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("..........");
}
});
//新生状态
Thread.State state = t.getState();
System.out.println(state);
//运行态
t.start();
state = t.getState();
System.out.println(state);
//阻塞态
Thread.sleep(2000);
state = t.getState();
System.out.println(state);
//终止状态
Thread.sleep(6000);
state = t.getState();
System.out.println(state);
}
}
6、获取优先级
package com.xhu.java.thread;
public class TestPriority {
public static void main(String[] args) {
//默认优先级 5
new Thread(() -> System.out.println(Thread.currentThread().getName()
+ "-->" + Thread.currentThread().getPriority())).start();
//设置优先级
Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName()
+ "-->" + Thread.currentThread().getPriority()));
t1.setPriority(10);
//注意:先设置优先级再启动
t1.start();
System.out.println(t1.getPriority());
//主线程main的优先级
System.out.println("main's Priority is " + Thread.currentThread().getPriority());
}
}
7、守护线程
package com.xhu.java.thread;
//测试守护线程
//用户线程
//守护线程
public class TestDaemon {
public static void main(String[] args) {
You y = new You();
God g = new God();
//设置守护线程,JVM不用等它,完成用户线程就可以结束了。
Thread tg = new Thread(g);
tg.setDaemon(true);//默认是false,为用户线程。
tg.start();
//用户线程
new Thread(y).start();
}
}
//你
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36; i++) {
System.out.println("好好活着");
}
System.out.println("died");
}
}
//上帝
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("god 永生,保护我们");
}
}
}
六、线程同步
多个线程操作同一资源,需要同步线程,保证正确性。
1、线程不安全
ArrayList
package com.xhu.java.thread;
import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
int j = i;
new Thread(() -> {
list.add(j);
}).start();
}
//主线程阻塞4秒,让上面的线程跑完
Thread.sleep(4000);
//期待size == 1000,但实际<=1000
System.out.println(list.size());
//得到结果870
}
}
2、线程同步
1)synchronized方法,默认锁this 2)synchronized块,自定义锁任意对象
1)调用synchronized方法,必须获得该对象的锁,如果该对象已被锁住,则线程阻塞。
缺点)如果一个方法很长,那么它独占对象的时间就会很长,导致效率降低。所以可以通过synchronized块来锁特定的地方。
A)抢票锁
package com.xhu.java.thread;
public class TestSynchronized {
public static void main(String[] args) throws InterruptedException {
//车站有10张票
Station s = new Station(100000);
//三个人去抢
Thread t1 = new Thread(s, "我");
Thread t2 = new Thread(s, "你");
Thread t3 = new Thread(s, "黄牛党");
//开始抢票
//第二种方法,synchronized块,synchronized默认锁this,但是如果要锁其它对象就可以用synchronized块
synchronized (s){
t1.start();
t2.start();
t3.start();
}
Thread.sleep(10000);
System.out.println(Station.modCount);
}
}
class Station implements Runnable {
private int ticketNum;
private boolean flag = true;
public static int modCount;
public Station(int ticketNum) {
this.ticketNum = ticketNum;
}
@Override
public void run() {//锁上this
while (flag) {
//没票了
if (ticketNum < 1) {
flag = false;
break;
}
//买票
try {
buy(ticketNum--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void buy(int i) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "抢到了票" + i);
modCount++;
//Thread.sleep(1000);
}
//通过设置标志位来停止线程
public void stop() {
this.flag = false;
}
}
B)让ArrayList安全
package com.xhu.java.thread;
import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
int j = i;
new Thread(() -> {
synchronized (list) {
list.add(j);
}
}).start();
}
//主线程阻塞4秒,让上面的线程跑完
Thread.sleep(4000);
//期待size == 1000,但实际<=1000
System.out.println(list.size());
//得到结果870
}
}
C)JUC中的安全集合
package com.xhu.java.thread;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> carr = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
int j = i;
new Thread(() -> {
carr.add(j);
});
}
Thread.sleep(2000);
System.out.println(carr.size());
}
}
3、死锁
死锁详解 四个必要条件:
1)互斥访问,资源独占。
2)请求并保持,保持了申请的独占资源的同时去申请其它的。
3)不可剥夺,保持的独占资源不可剥夺。
4)循环等待,相互请求互相保持的资源。
package com.xhu.java.thread;
public class DeadLock implements Runnable {
//同时拿到两个筷子才能吃饭
private LeftChopSticks lc;
private RightChopSticks rc;
private int choice;
public DeadLock(LeftChopSticks lc, RightChopSticks rc, int choice) {
this.lc = lc;
this.rc = rc;
this.choice = choice;
}
@Override
public void run() {
if (choice == 0) {
synchronized (lc) {
System.out.println(Thread.currentThread().getName() + "拿到了左筷子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拿到左筷子,准备拿右筷子
synchronized (rc) {
System.out.println(Thread.currentThread().getName() + "拿到了左筷子");
}
}
} else {
synchronized (rc) {
System.out.println(Thread.currentThread().getName() + "拿到了右筷子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拿到右筷子,准备拿左筷子
synchronized (lc) {
System.out.println(Thread.currentThread().getName() + "拿到了右筷子");
}
}
}
}
public static void main(String[] args) {
//只new 一根左筷子
LeftChopSticks lc = new LeftChopSticks();
//只new 一根右筷子
RightChopSticks rc = new RightChopSticks();
DeadLock dl1 = new DeadLock(lc,rc,1);
DeadLock dl2 = new DeadLock(lc,rc,0);
new Thread(dl1,"小李").start();
new Thread(dl2,"小王").start();
}
}
//一根左筷子
class LeftChopSticks {
}
//一根右筷子
class RightChopSticks {
}
4、Lock
JUC的同步接口Lock,规范了同步锁的定义,其实现类ReentrantLock,有着跟synchronized一样的内存语义和并发性。
package com.xhu.java.thread;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock implements Runnable {
//10张票
private int ticketNum;
//显式可重入锁
private final ReentrantLock rl = new ReentrantLock();
public TestLock(int ticketNum) {
this.ticketNum = ticketNum;
}
@Override
public void run() {
while (true) {
//锁定,保证ticketNum的正确性
rl.lock();
try {
if (ticketNum < 1) break;
System.out.println(Thread.currentThread().getName() + "抢到了票" + ticketNum--);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
rl.unlock();
}
}
}
public static void main(String[] args) {
TestLock tl = new TestLock(10);
//多个人抢票
new Thread(tl, "小李").start();
new Thread(tl, "小王").start();
new Thread(tl, "黄牛党").start();
}
}
七、线程通信问题
线程同步就需要线程通信,那么线程如何通信?
典型问题:生产者-消费者问题
软件方法、硬件方法、信号量、管程做到线程通信
1、管程法
package com.xhu.java.thread;
//管程法解决生产者-消费者问题
public class TestTube {
public static void main(String[] args) {
Container c = new Container(10);
Producer p = new Producer(c);
Customer cus = new Customer(c);
new Thread(p).start();
new Thread(cus).start();
}
}
//Producer
class Producer implements Runnable {
private Container c;
public Producer(Container c) {
this.c = c;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//生成鸡
Chicken chicken = new Chicken(i + "");
try {
c.push(chicken, i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//Customer
class Customer implements Runnable {
private Container c;
public Customer(Container c) {
this.c = c;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//消费鸡
try {
c.pop(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品为鸡
class Chicken {
private String id;
public Chicken(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//装鸡的容器
class Container {
private int capacity;
private Chicken[] chickens;
private int count;
public Container(int capacity) {
this.capacity = capacity;
this.chickens = new Chicken[this.capacity];
}
//把鸡装入容器
public synchronized void push(Chicken chicken, int i) throws InterruptedException {
//容器已满,则等待
if (chickens.length == count) {
this.wait();
}
//容器没满,把鸡装入
chickens[count++] = chicken;
System.out.println("生产了第" + i + "鸡");
//通知其它操作容器且等待的线程,有鸡了
this.notifyAll();
}
//把容器里的鸡取出来
public synchronized Chicken pop(int i) throws InterruptedException {
//容器为空,则等待
if (count == 0) {
this.wait();
}
//容器里有鸡,则取鸡
Chicken chicken = chickens[--count];
System.out.println("消费了第" + i + "只鸡");
//通知其它操作容器且等待的线程,可以加鸡
this.notifyAll();
return chicken;
}
}
八、线程池
背景)每次都创建线程,用完就销毁线程,这样耗费资源太大,对性能影响很大。
解决)提前创建多个线程放入线程池,使用时就获取,不使用的时候就放回线程池中,避免频繁的创建和销毁线程。
好处)减少资源消耗的同时还可以提高响应速度。同时也便于线程管理
corePoolSize:核心池的大小。
maxmumPoolSize:最大线程数。
keepAliveTime:线程没有任务时最多保持多长时间后会终止。
package com.xhu.java.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//1.创建线程池服务
ExecutorService ser = Executors.newFixedThreadPool(10);//参数为池子大小
//2.执行线程
ser.execute(new MyThread());
ser.execute(new MyThread());
ser.execute(new MyThread());
ser.execute(new MyThread());
//3.关闭连接
ser.shutdownNow();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结
1)多线程是什么?
2)Java 如何实现多线程?
3)静态代理思想
4)Lambda函数式编程,简化匿名内部类。
5)线程状态
6)线程同步
7)线程通信问题
8)线程池
参考文献
[1] Java 多线程 狂神 [2] [JDK 1.12] [3] [The Art of Java Concurrency Programming 方腾飞,魏鹏,程晓明]