Java多线程编程详解——快速上手附代码

Java多线程编程详解——快速上手附代码

Java多线程编程方面涉及到的知识,结合B站狂神说视频总结笔记,附代码。


一、创建多线程的方法

三种:

  1. 继承Thread类
  2. 实现Runnable接口,将重写run方法,将Runnable实现类作为Thread类的Target创建线程对象,再调用该Thread类的start()方法启动该线程。(这里是用到了静态代理模式,两个类都实现同一个接口,将实际类当作代理类的Target传入构造函数参数中,接着由代理类创建对象代理实际类完成操作)
  3. 实现Callable接口。可以定义返回类型,获取线程执行结果。
    在这里插入图片描述
    多线程创建方法代码示例:
import java.util.concurrent.*;

// 1. 继承Thread类
public class T1 extends Thread{

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "这是继承Thread类方法");
    }

    public static void main(String[] args) {
        T1 thread1 = new T1();
        // 继承Thread类启动线程
        thread1.run();
        TestRunnable tr = new TestRunnable();
        TestCallable tc = new TestCallable();
        // 启动Runnable线程
        new Thread(tr,"Runnable实现线程").start();
        
//        // Callable启动线程方式一        
//        ExecutorService service = Executors.newFixedThreadPool(3);
//        Future<String> result =  service.submit(tc);
//
//        try {
//            String out = result.get();
//            System.out.println(out);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }
//
//        service.shutdown();

        // Callable启动线程方式二
        FutureTask<String> futureTask = new FutureTask<String>(tc);
        new Thread(futureTask).start();
        try {
            String out = futureTask.get();
            System.out.println(out);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

// 2. 实现Runnable接口方式
class TestRunnable implements Runnable{
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

// 3. 实现Callable接口方式
class TestCallable implements Callable{

    @Override
    public String call() {
        System.out.println("输出一个人");
        return "小纶纶";
    }
}

二、静态代理

即如上面Runnable实现创建线程即是使用了静态代理模式。
在这里插入图片描述

三、Lambda表达式

作用:简化代码(jdk8新特性)
在这里插入图片描述

注意:函数式接口是Lambda表达式的关键所在。
在这里插入图片描述
在这里插入图片描述

public class LamdaThread {

    //内部静态类实现方式
    static class Like implements ILike{

        @Override
        public void like() {
            System.out.println("I Like You!");
        }
    }

    public static void main(String[] args) {
        Like like1 = new Like();
        like1.like();

        //函数式接口lambda实现方式
        ILike like = () -> {
            System.out.println("This is a lambda!");
        };
        like.like();
    }
}

interface ILike{
    void like();
}

总结:

  • lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就该用代码块包裹;
  • 前提是接口为函数式接口;
  • 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。

四、线程状态

在这里插入图片描述
在这里插入图片描述

五、 常用线程方法

在这里插入图片描述

1. 线程停止

不推荐使用自带的线程内部的停止方法。如:
在这里插入图片描述

在Thread类destroy()方法上面加有@Deprecated注解,左边方法也被划了横线,表示这个方法已经被废弃,使用可能会有bug或弊端。
优质解决方法:可以用一个标志位,在线程体内部加入标志位,更改标志位已终止线程执行。
在这里插入图片描述
在这里插入图片描述

2. 线程休眠——sleep()

在这里插入图片描述

// 模拟演示
	try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

3. 线程礼让——yield()

在这里插入图片描述
方法:Thread.yield()

4. 线程强制执行——join()

在这里插入图片描述

5. 线程状态观测——Thread.State

在这里插入图片描述
在这里插入图片描述
有了线程的状态,我们就能在线程执行的过程中根据线程的状态进行标志,当线程处于某种状态时我们就执行某种操作。比如我们在监测线程状态时发现一个线程在很长一段时间内都是TIME_WAITING状态的话就执行break。线程只能启动一次,一旦进入死亡模式的话就不能再次启动了。

六、线程优先级

在这里插入图片描述

线程优先级默认是5,最高为10,最低为1。使用Thread.getPriority()获取线程的优先级信息,使用Thread.setPriority()设置线程优先级。
注:优先级高并不代表一定是其先执行,只是说在权重分配上其权重更高,实际运行情况还是看CPU执行结果。一般来说权重高的线程优先执行的概率高。

思考:性能倒置问题(指优先级低的线程被执行了,优先级高的反而在等待)
Why?正常来说,无论怎么样,高优先级的任务一来,低优先级任务就会让位,所以似乎必定高优先级任务能拿到CPU,那为啥还会有优先级倒置?
让我们考虑这么个例子,优先级 T1>T2>T3

T3先执行,他执行的时候正好拿到了资源A,意味着别的任务不能访问资源A,而会被阻塞;
这时 T2执行,由于优先级更高,他很轻松的抢占了CPU 然后他成功的访问到了资源B,成为唯一能够访问资源B,拿到锁的幸运儿。有趣的是,资源B也是T3所需要的,但是这不影响,因为按理来说,T2会执行,直到释放B的锁,以及让出CPU,这样T3可以继续执行;
这时出现了T1,他毫不意外的从T2抢占了CPU,但是却没办法执行,因为缺乏资源A!所以理论上他需要T3执行完才能继续执行。。。
你说接下来发生什么?自然是T1被阻塞,然后让出CPU,实际上最先执行的是T2,等T2执行完,T3才能获得CPU(此时因为T1被阻塞了,没办法抢占CPU),继续执行,等到T3执行完,释放T1所需的资源A,最后才轮到T1.

解决方案:
目前据我所知有两种解决方案
1. 优先级继承
我们发现问题出在,一个矛盾,高优先级的任务因为低优先级任务持有的资源(当然不是CPU资源),而阻塞,既然我们没法轻易释放锁,解锁,因为锁需要CPU运行才能释放,那我们干脆直接让,那个获得资源的任务,得到相同的优先级,或者说继承那个高优先级,这样原先的低优先级任务,可以先运行,赶紧用完赶紧走,别打扰我T1干事。

换个角度来看,这就是临时提升CPU的抢占优先级,临时“升官”,为的就是赶紧执行让出资源。

资源让出来以后,再恢复优先级,该干啥干啥:)

这样 一开始你会觉得 这不还是让T3先于T1了嘛?但是你别忘了,我们这层优化可以使得T1不用在T2之后执行

So?不就节省了一个任务嘛?那如果我改变一下条件,假设有100个任务 T1 ~ T100,T100最低,持有T1的资源,你觉得会发生什么事?

很明显T1会被迫等待T2~T99,所有的任务都执行完才能执行!!!!

这就非常显著的性能差异了

2. 优先级天花板
除了优先级继承模式,还有个模式被称为优先级天花板,
这个模式很好玩的在于,他直接避免了阻塞的发生:)优先级继承的机制触发条件是,高优先级任务被手握资源的低优先级任务阻塞,而天花板则是,只要他手握资源,那么在所有可能需要此资源的线程中,他拥有最高优先级,

这意味着,其他资源的占有比CPU的占有更重要,占有其他临界资源,我可以给你配好CPU的使用优先级,但你如果只是占有CPU 实际上你还被阻塞 等同于没有优先。
————————————————
版权声明:本文为CSDN博主「阮菜鸡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43178828/article/details/113914627

七、守护线程

在这里插入图片描述
Thread.setDaemon(true); 表示将该线程设置为守护线程,默认是false表示是用户线程。
在这里插入图片描述

八、线程同步

在这里插入图片描述

1. synchronized关键字

线程不安全示范:
代码如下:

// 线程不安全示例代码
public class ThreadNotSafe {
   public static void main(String[] args) {
       Ticket t = new Ticket(10);
       Thread thread1 = new Thread(t,"小明");
       Thread thread2 = new Thread(t,"小红");
       Thread thread3 = new Thread(t,"小强");
       
       thread1.start();
       thread2.start();
       thread3.start();
   }
}

class Ticket implements Runnable{
   private int ticket;
   Boolean flag = true;

   public Ticket(int ticket){
       this.ticket = ticket;
   }

   @Override
   public void run() {
       while (flag){
           try {
               Thread.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           buy();
       }
   }

   public void buy(){
       if(ticket <= 0){
           flag = false;
           return;
       }
       System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
   }
}

上示例模拟火车票购票,三个线程同时执行买票操作,总票数设置为10张,未保证线程安全,运行结果如下:
在这里插入图片描述
可以看到这里有多人买到了同样的票,在这个例子中这样线程是不安全的。

解决方法:
在这里插入图片描述

	// buy方法中加入synchronized关键字进行线程同步控制
    public synchronized void buy(){
        if(ticket <= 0){
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
    }

Synchronized锁对象内方法时,默认锁的是对象本身 this, 因此对于我们不需要锁本身而是实际操作对象时,可以使用同步块策略。

在这里插入图片描述

2. CopyOnWriteArrayList

测试JUC安全类型的集合,它是线程安全的。java.util.concurrent.CopyOnWriteArrayList

3. 死锁

在这里插入图片描述
在这里插入图片描述
以下为一段死锁代码示例:

public class DeadLock {
    public static void main(String[] args) {
        MakeUp makeUp1 = new MakeUp(0,"灰姑凉");
        MakeUp makeUp2 = new MakeUp(1,"白雪公主");

        new Thread(makeUp1).start();
        new Thread(makeUp2).start();
    }
}

// 口号
class Lipstick{

}

// 镜子
class Mirror{

}

class MakeUp implements Runnable{

    // 需要的资源只有一份,用static来保证
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();

    int choose;  // 选择
    String girlName;  // 使用化妆品的人

    public MakeUp(int choose, String girlName){
        this.choose = choose;
        this.girlName = girlName;
    }


    @Override
    public void run() {
        // 进行化妆
        if(choose == 0){
            synchronized (mirror){
                System.out.println(girlName + "拿到了镜子");
                synchronized (lipstick){
                    System.out.println(girlName + "拿到了口红");
                }
            }
        }else {
            synchronized (lipstick){
                System.out.println(girlName + "拿到了口红");
                synchronized (mirror){
                    System.out.println(girlName + "拿到了镜子");
                }
            }
        }
    }
}

灰姑凉和白雪公主这两个女孩要化妆,灰姑凉先选择拿口号给自己上个口红先,白雪公主先拿到镜子先照照,但是当前镜子和口红都只有一个,这时候灰姑凉要拿镜子了,但是还拿着口红不放,白雪公主想要画口红了但是同样不放开已经拿到手的镜子,结果两个人进入了僵持,谁也不先让步,结果谁也画不成妆了。
程序死锁,进程永远无法继续运行
在这里插入图片描述

同一资源被两个进程使用,互相持有又不放开,谁都请求不到造成死锁,这里解决的方法就是不要让synchronized代码块持有两个或以上的对象锁,在写代码的时候就要采用合理的策略,避免这种情况的发生,如上例中对一个对象操作完释放掉对象锁。

4. Lock(锁)

在这里插入图片描述

在这里插入图片描述
Lock测试代码:

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        Station s = new Station();
        new Thread(s,"黄牛").start();
        new Thread(s,"小红").start();
        new Thread(s,"小明").start();
    }
}

class Station implements Runnable{
    private int ticket = 1000;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        buy();
        // 模拟延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void buy(){
        lock.lock();
        try {
            while (true){
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() +"买到了第" + ticket-- + "张票");
                }else {
                    break;
                }
            }
        }finally {
            lock.unlock();
        }
    }
}

九、线程协作

生产者消费者模式

1. 线程通信

在这里插入图片描述

2. 管程法

生产者消费者模型  利用缓冲区解决:管程法。
生产者、消费者、产品、缓冲区
在这里插入图片描述

代码示例:

public class TestGuanCheng {
   public static void main(String[] args) {
       Container maidaolao = new Container();
       Productor productor = new Productor(maidaolao);
       Consumer consumer = new Consumer(maidaolao);

       new Thread(productor).start();
       new Thread(consumer).start();

   }
}

class Chicken{
   int id;
   public Chicken(int id){
       this.id = id;
   }

   public int getId() {
       return id;
   }

   public void setId(int id) {
       this.id = id;
   }
}

class Consumer implements Runnable{
   Container store;
   public Consumer(Container store){
       this.store = store;
   }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println("消费了第" + store.pop().getId() + "只鸡");
       }
   }
}

class Productor implements Runnable{
   Container store;
   public Productor(Container store){
       this.store = store;
   }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           Chicken c = new Chicken(i);
           store.push(c);
           System.out.println("已经生产了第" + i + "只鸡了");
       }
   }
}


class Container{

   Chicken[] chickens = new Chicken[10];
   int count = 0;

   // 生产
   public synchronized void push(Chicken chicken){
       // 如果容器满了,就需要等待消费者来消费
       if(count == chickens.length){
           // 生产等待
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

       // 如果没满就丢入产品
       chickens[count] = chicken;
       count++;

       // 通知等待中的线程来进行消费
       this.notifyAll();
   }

   // 消费
   public synchronized Chicken pop() {
       // 判断能否消费
       if(count == 0){
           // 消费等待
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

       // 如果可以消费
       count--;
       Chicken chicken = chickens[count];

       // 通知等待中的线程来进行生产
       this.notifyAll();
       return chicken;
   }
}

3. 信号灯法

测试生产者消费者问题2  信号灯法:标志位解决

简单来讲就是利用一个或多个标志位来控制各个线程之间的执行
代码示例:

public class TestXinHaoDeng {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

class Player extends Thread{
    TV tv;
    public Player(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i % 2 == 0){
                this.tv.play("快乐大本营");
            }else {
                this.tv.play("中国好声音");
            }
        }
    }
}

class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}

class TV{
    String show;
    // 正在表演,观众等待 T
    // 正在观看,演员等待 F
    Boolean flag = true;

    // 表演
    public synchronized void play(String show){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("表演了" + show);
        // 通知观众观看
        this.notifyAll();
        this.show = show;
        this.flag = !this.flag;
    }

    // 观看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("观众观看了" + show);
        // 提醒演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

十、线程池

在这里插入图片描述
在这里插入图片描述
创建一个线程池的方法:
在这里插入图片描述
创建线程池代码示例(newFixedThreadPool(int nThreads)方法):

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {
   public static void main(String[] args) {
       // 1. 创建服务,创建线程池
       // newFixedThreadPool 参数为线程池大小,即最大线程数
       ExecutorService se = Executors.newFixedThreadPool(10);

       // 2. 执行
       se.execute(new MyThread());
       se.execute(new MyThread());
       se.execute(new MyThread());
       se.execute(new MyThread());

       // 3. 关闭线程池
       se.shutdown();
   }
}

class MyThread implements Runnable{

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }
}


总结

以上能在实际运用多线程有个初步的了解,了解其使用原理的话理解起来也更加轻松了,文章内截图来自B站狂神说多线程详解视频。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值