java线程讲解
线程基本概念
学习视频来自B站,感谢狂神的分享:B站视频地址
进程:操作系统中运行的程序,比如QQ、游戏、IDE;
线程:同一个进程可以包含多个线程
普通方法调用与多线程调用:
线程的创建
1. 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里执行具体方法");
}
public static void main(String[] args) {
//创建对象
MyThread myThread = new MyThread();
myThread.run(); //和后续代码顺序执行,只有一条主线
myThread.start(); //启动另一个线程,和后续代码并行执行
}
}
2. 实现Runnable接口
//1.实现Runnable接口并重写run方法
//2.将实现Runnable接口的类,放入执行线程,调用start方法
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里执行具体方法");
}
public static void main(String[] args) {
//创建Runnable接口实现类的对象
MyRunnable mr = new MyRunnable();
//创建线程对象,并通过它开启线程
new Thread(mr).start();
}
}
两种方法的对比:
并发问题:
//多个线程同时买火车票
public class TickRunnable implements Runnable {
private int tickNum = 10; //票数
@Override
public void run() {
while (true){
if(tickNum <= 0){
break;
}
System.out.println(Thread.currentThread().getName()+" --> 获得第"+tickNum-- +"张票");
}
}
public static void main(String[] args) {
TickRunnable tickRunnable = new TickRunnable();
//同一个对象操,被多个线程使用
//未做并发处理,会出现多个人抢到同一张票的问题
new Thread(tickRunnable,"张三").start();
new Thread(tickRunnable,"李四").start();
new Thread(tickRunnable,"黄牛").start();
}
}
//模拟龟兔赛跑
public class RaceRunnable implements Runnable {
private static String winner; //胜利者
@Override
public void run() {
//一共跑一百步
for (int i = 0; i <= 100; i++) {
if(i == 100 && Thread.currentThread().getName().equals("兔子")){
Thread.sleep(200); //如果是兔子跑到100步,沉睡200毫秒
}
boolean isOver = isOver(i);
if(isOver){
break;
}
}
}
//跑到100,给winner赋值,结束比赛
private boolean isOver(int step){
if(winner != null){
return true;
}else if(step >= 100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是:"+winner);
return true;
}else{
System.out.println(Thread.currentThread().getName()+"--->跑了第"+step+"步");
return false;
}
}
public static void main(String[] args) {
RaceRunnable rr = new RaceRunnable();
new Thread(rr,"兔子").start();
new Thread(rr,"乌龟").start();
}
}
3. 实现Callable接口
public class MyCallable implements Callable<Integer> {
private static Integer index = 0; //记录第几个创建
//实现call方法,并返回值
@Override
public Integer call() throws Exception {
return index++;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc1 = new MyCallable();
MyCallable mc2 = new MyCallable();
MyCallable mc3 = new MyCallable();
//创建执行服务
ExecutorService executor = Executors.newFixedThreadPool(3);
//提交执行
Future<Integer> submit1 = executor.submit(mc1);
Future<Integer> submit2 = executor.submit(mc2);
Future<Integer> submit3 = executor.submit(mc3);
//获取结果
Integer index1 = submit1.get();
Integer index2 = submit2.get();
Integer index3 = submit3.get();
//关闭服务
executor.shutdownNow();
}
}
知识扩充
静态代理
//--------------- 代理类与被代理类实现同一个接口 ---------------
//代理类 传入 被代理类的对象
public class StaticProxy {
public static void main(String[] args) {
You you = new You(); //真实对象
WiddingConpany widdingConpany = new WiddingConpany(you); //代理类对象
widdingConpany.HappyMarry(); //通过代理类执行方法
}
}
//定义结婚接口,让实现类和代理类都实现这个接口
interface Marry{
void HappyMarry();
}
//实现类:真实结婚对象
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("新郎新娘:结婚了,真开心!");
}
}
//代理类:婚庆公司
class WiddingConpany implements Marry{
private Marry target; //指向被代理的真实对象
public WiddingConpany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before(); //调用真实对象方法之前
target.HappyMarry();
after(); //调用真实对象方法之后
}
private void before(){
System.out.println("代理类:结婚之前,布置现场");
}
private void after(){
System.out.println("代理类:结婚之后,收尾款");
}
}
//--------------- Thread类也实现了Runnable接口 ---------------
//线程也是代理模式:Thread类是代理类,代理了Runnable接口的实现类(匿名内部类)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable接口");
}
}).start();
//等同于下面的方式:
new Thread(()->{
System.out.println("函数式编程,实现Runnable接口");
}).start();
lambda表达式
函数式接口: 只包含唯一一个抽象方法的接口(只有一个方法,且抽象);函数式接口,可通过lambda表达式创建该接口的对象。
//Runnable就是函数式接口:
public interface Runnable{
public abstract void run();
}
//推导过程如下:
public class LambdaInfer {
//2. 通过静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("2.通过静态内部类实现:lambda --》2");
}
}
public static void main(String[] args) {
//3.通过局部内部类实现
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("3.通过局部内部类实现:lambda --》3");
}
}
//---------------------- 推导过程: ----------------------
//1.实现类
ILike like = new Like1();
like.lambda();
//2.静态内部类
like = new Like2();
like.lambda();
//3.局部内部类
like = new Like3();
like.lambda();
//4.通过匿名内部类实现
like = new ILike() {
@Override
public void lambda() {
System.out.println("4.通过匿名内部类:lambda --》4");
}
};
like.lambda();
//5.用lambda简化
like = ()->{
System.out.println("5.通过lambda简化:lambda --》5");
};
like.lambda();
}
}
输出结果如下:
1.通过实现类实现:lambda --》1
2.通过静态内部类实现:lambda --》2
3.通过局部内部类实现:lambda --》3
4.通过匿名内部类实现:lambda --》4
5.通过lambda简化:lambda --》5
线程的状态
线程的状态:创建、就绪、运行、阻塞、死亡
1.停止
1.不建议使用JDK提供的 stop()、destory()方法(已废弃)
2.建议使用 标志位 ,终止线程的运行。
public class RunnableStop implements Runnable {
private boolean flag = true;
@Override
public void run() {
int index = 0;
//通过标志位,执行线程方法体
while (flag){
System.out.println("run -->"+index++);
}
}
//公共方法,转换标志位
public void stop(){
flag = false;
}
public static void main(String[] args) {
RunnableStop runnableStop = new RunnableStop();
new Thread(runnableStop).start();
for (int i = 0; i < 10000; i++) {
System.out.println("main: "+i);
if(i == 900){
runnableStop.stop(); //转换线程标志位,停止方法
System.out.println("线程停止!!");
}
}
}
}
2.休眠 sleep
作用:
- 模拟网络延时,放大问题发生的可能性(抢票问题)
- 模拟系统倒计时
3. 礼让 yield
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "--线程 start");
Thread.yield(); //调用yield方法,当前线程重新参与cpu竞争
System.out.println(Thread.currentThread().getName()+ "--线程 end");
}
}
4. 强制执行 join
Join 合并线程,等待此线程执行完成后,再执行其他线程。
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("run线程 --》"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TestJoin());
thread.start();
for (int j = 0; j < 300; j++) {
System.out.println("主线程 -- "+j);
//当j为200的时候,调用join方法使主线程阻塞,将thread执行完
if(j == 200){ thread.join(); }
}
}
}
5.状态监测 Thread.State
java.lang.Thread.State 继承自 java.lang.Enum
- NEW :尚未启动的线程,处于此状态
- RUNNABLE:在java虚拟机中执行的线程,处于此状态
- BLOCKED:被阻塞等待监视器锁定的线程,处于此状态
- WAITING:在等待另一个线程执行特定动作的线程,处于此状态
- TIMED_WAITING:在等待另一个线程执行动作达到指定等待时间的线程,处于此状态
- TERMINATED:已退出的线程,处于此状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread( ()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("--------- 线程已结束 ---------");
});
//--------- 获取线程状态,并输出:---------
Thread.State state = thread.getState();
System.out.println(state);//1.未执行,输出NEW
//2.启动线程
thread.start();
state = thread.getState();
System.out.println(state); //输出 RUNNABLE
//只要线程不终止,一直输出状态
while(state != Thread.State.TERMINATED){
//3.线程阻塞
Thread.sleep(100);
state = thread.getState();
//输出 TIMED_WAITING;线程执行完之后,输出 TERMINATED
System.out.println(state);
}
}
}
6.线程优先级
public class MyPriority implements Runnable {
public static void main(String[] args) {
MyPriority priority = new MyPriority();
//线程优先级:先设置后启动[默认为5。范围1 至10,超出报异常]
Thread t1 = new Thread(priority, "a");
Thread t2 = new Thread(priority, "b");
Thread t3 = new Thread(priority, "c");
//默认权重为5
t1.start();
//设置最高,先设置再启动
t2.setPriority(10);
t2.start();
//设置最低
t3.setPriority(1);
t3.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 优先级为:"+Thread.currentThread().getPriority());
}
}
7/守护线程
线程分为用户线程与守护线程
虚拟机确保 用户线程 执行完,但不用等 守护线程 执行完
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
//创建上帝,设置为守护线程。虽然未设置结束条件,
// 但在用户线程结束后,仍会结束
Thread godThread = new Thread(god);
godThread.setDaemon(true); //默认为false
godThread.start();
//创建用户线程,虚拟机保证用户线程执行结束
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("--上帝守护--");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("在人世间第 "+i+" 天");
}
System.out.println("goodbye world!");
}
}
线程同步
并发
并发:同一个对象 被 多个线程 同时操作
并发 | 同一个对象 被 多个线程 同时操作 |
---|---|
线程同步 | 线程同步是一种等待机制,多个同时访问此对象的线程, 进入这个对象的等待池形成队列。 前面的线程使用完,下一个线程再使用 |
同步的条件 | 队列+锁 |
不安全的线程:
//不安全的买票线程:
// 三个人同时得到最后一张,输出 -1
public class UnsafeByTicket {
public static void main(String[] args) {
ByTicket byTicket = new ByTicket();
new Thread(byTicket,"aa").start();
new Thread(byTicket,"bb").start();
new Thread(byTicket,"cc").start();
}
}
class ByTicket implements Runnable{
private static int ticketNum = 10;
private boolean flag = true;
@Override
public void run() {
while (flag){
buy();
}
}
//定义买票方法
public void buy() throws InterruptedException {
if(ticketNum <= 0){ //票数为0的时候,停止线程执行
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买了--->"+ticketNum --);
}
}
输出结果:
bb买了--->1
cc买了--->0
aa买了--->-1
// ArrayList 线程不安全,被多个线程同时操作,可能出现混乱
//扩充:CopyOnWriteArrayList 线程安全,不用添加同步块
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> strList = new ArrayList<String>();
for (int i = 0; i < 10000; i++) { //添加10000条数据
new Thread(()->{
strList.add(Thread.currentThread().getName());
}).start();
}
//沉睡10秒,模拟延迟
Thread.sleep(10000);
//输出结果,很可能不到10000(多个线程同时添加一个位置)
System.out.println(strList.size());
}
}
同步方法与同步块
- 同步方法,synchronized锁定的是当前对象。如果线程中的增删改操作的不是该对象,则起不到方法同步的效果
- synchronized方法快,可以锁定任何对象,但是建议将变化的对象作为同步监视器
死锁
多个线程各自占有一些公共资源,并且互相等待其他线程占有的资源才能运行,导致线程都停止执行。某一个同步块同事拥有两个以上对象的锁,就可能造成 “死锁”
- 死锁产生的四个必要条件
1.互斥条件:一个资源每次只被一个线程使用
2.请求与保持:一个线程因请求自愿而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不可强行剥夺
4.循环等待:多个线程之间,形成一种头尾相接的循环等待资源关系
//灰姑娘先拿到口红,白雪公主先拿到镜子;两人相互等待对方的资源
public class DeadLock {
public static void main(String[] args) {
MakeUp girl1 = new MakeUp(1, "灰姑娘");
MakeUp girl2 = new MakeUp(2, "白雪公主");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick{}
//镜子
class Mirror{}
class MakeUp extends Thread{
//用static保证资源唯一
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
//实体类属性
int choice; //选择:1先用口红,2先用镜子
String girlName; //名字
public MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
girlMakeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//定义化妆方法
public void girlMakeUp() throws InterruptedException {
if(choice == 1){
//synchronized嵌套使用,导致相互等待,造成死锁
synchronized (lipstick){
System.out.println(girlName+"-->拿到了口红");
Thread.sleep(1000); //拿到口红,使用1秒
synchronized (mirror){
System.out.println(girlName+"-->拿到了镜子");
}
}
}else{
synchronized (mirror){
System.out.println(girlName+"-->拿到了镜子");
Thread.sleep(2000); //拿到镜子,使用2秒
synchronized (lipstick){
System.out.println(girlName+"-->拿到了口红");
}
}
}
}
}
//程序卡死,输出结果:
//白雪公主-->拿到了镜子
//灰姑娘-->拿到了口红
解决方法:synchronized不要嵌套使用
lock(锁)
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2,"aa").start();
new Thread(testLock2,"bb").start();
new Thread(testLock2,"cc").start();
}
}
//使用ReentrantLock实现线程同步
class TestLock2 implements Runnable{
private static int ticketNum = 10; //一共10张票
private ReentrantLock lock = new ReentrantLock(); //Lock的实现类
@Override
public void run() {
try {
lock.lock(); // ------ 对下面代码块添加 锁---
while (ticketNum > 0){
Thread.sleep(1000);
System.out.println("买了票:-->"+ticketNum-- );
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // ------ 解 锁------
}
}
}
线程协作
java提供了几种方法,解决线程之间的通讯:
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知。 与synchronized不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程, 优先级高的线程优先调度 |
注:以上方法都是Object类的方法,都只能在同步方法或者同步代码块中调用,否则将抛出 IllegalMonitorStateException
管程法
//使用管程法,利用缓冲区测试生产者、消费者
public class TestPC {
public static void main(String[] args) {
//定义缓冲区
SynContainer synContainer = new SynContainer();
//启动生产者、消费者
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
// ------------- 生产者 -------------
class Productor extends Thread{
//加入缓冲区并创建构造方法
private SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
//开始生产产品
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synContainer.push(new Chicken(i));
// System.out.println("生产了-->"+i);
}
}
}
// ------------- 消费者 -------------
class Consumer extends Thread{
//加入缓冲区并创建构造方法
private SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
//开始消费产品
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken chicken = synContainer.pop();
// System.out.println("消费了--"+chicken.id);
}
}
}
// ------------- 产品 -------------
class Chicken{
int id; //产品编号
public Chicken(int id) { this.id = id;}
}
// ------------- 缓冲区 -------------
class SynContainer{
Chicken[] chickens = new Chicken[10]; //缓冲区可放10只鸡
int index = 0; //缓冲区的下标
// --- 生产者放入产品
public synchronized void push(Chicken chicken){
//判断缓冲区是否满了:如果是则等待,并通知其他线程消费
if(index == chickens.length){
//等待消费者
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满,则继续放入产品
System.out.println("生产了-->"+chicken.id);
chickens[index] = chicken;
index ++;
//唤醒其他线程
this.notifyAll();
}
// --- 消费者消费产品 ---
public synchronized Chicken pop(){
//判断缓冲区是否满了:如果是则等待,并通知其他线程消费
if(index == 0){
//等待并唤醒其他线程
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有产品,可以消费
index --; //不可将这一行放到下面,否则下标越界
Chicken chicken = chickens[index];
System.out.println("消费了--"+chicken.id);
//唤醒其他线程,并返回结果
this.notifyAll();
return chicken;
}
}
信号灯法
public class TestPC2 {
public static void main(String[] args) {
Tv tv = new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
// ------------- 演员 -------------
class Player extends Thread{
private Tv tv;
public Player(Tv tv) { this.tv = tv; }
//开始表演节目
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2 == 0){
tv.play("亮剑--李云龙");
}else{
tv.play("抖音--记录美好生活");
}
}
}
}
// ------------- 观众 -------------
class Watcher extends Thread{
private Tv tv;
public Watcher(Tv tv) { this.tv = tv; }
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.watch();
}
}
}
// ------------- 节目 -------------
class Tv{
String tvName; //节目名称
boolean flag = true; //信号灯:演员表演为true; 观众观看为false
//表演节目
public synchronized void play(String tvName){
this.tvName = tvName;
//如果标识位为true,演员开始表演
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演-------->"+this.tvName);
this.flag = !this.flag;
this.notifyAll();
}
//观看节目
public synchronized String watch(){
//标识位为false,观众开始观看
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+this.tvName);
this.flag = !this.flag;
this.notifyAll();
return this.tvName;
}
}
注:要在等待(wait)与唤醒(notifyAll)之间处理业务,否则会出现不可预测结果
线程池
背景:经常创建和销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入 线程池 中,使用时直接获取,使用完放回池子中。避免频繁的创建销毁,可以重复利用
好处:
- 提高响应速度(减少了创建线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要重复创建)
- 便于线程管理:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时,最多保持多长时间后会终止
public class TestPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(9);
//-----2.execute方法,没有返回值-----
service.execute(new MyRunnable2());
service.execute(new MyRunnable2());
//-----3.submit方法,可以获取返回值-----
Future<String> future = service.submit(new Callable2());
System.out.println(future.get());
//4.关闭连接
service.shutdownNow();
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
class Callable2 implements Callable<String>{
@Override
public String call() throws Exception {
return "你好:"+Thread.currentThread().getName();
}
}
Callable接口的实现类,也可通过下面的方式,获取返回值:
//另一种写法:
public class NewThread {
public static void main(String[] args) {
new MyThread1().start(); //第一种启动方式
new Thread(new MyThread2()).start(); //第二种启动方式
//第三种启动方式
FutureTask<String> futureTask = new FutureTask<String>(new MyThread3());
new Thread(futureTask).start();
try {
String str = futureTask.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// --- 继承Thread类 ---
class MyThread1 extends Thread{
@Override
public void run() { System.out.println("继承Thread类"); }
}
// --- 实现Runnable接口,无返回值 ---
class MyThread2 implements Runnable{
@Override
public void run() { System.out.println("实现Runnable接口"); }
}
// --- 实现Callable,有返回值 ---
class MyThread3 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("实现Callable接口");
return "Callable接口实现类";
}
}