我是靠谱客的博主 悲凉手机,最近开发中收集的这篇文章主要介绍java多线程(分分钟基础秒杀)前言线程安全问题(并发问题)线程池,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述


theme: channing-cyan
highlight: androidstudio

文章目录

    • theme: channing-cyan highlight: androidstudio
  • 前言
    • 继承Thread
    • 实现Runale接口
    • Callable接口
    • 线程周期
    • CPU调度
  • 线程安全问题(并发问题)
    • 方案一,直接加synchronized
    • 提取Tickets
    • 上锁
    • 生成者与消费者
      • 信号灯
      • 管程法
  • 线程池

「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」

前言

关于java线程的创建这里就不进行过多复述了

继承Thread

这个是直接使用那个Thread继承一下,重写run方法的

public class ThreadDome1 extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }

    public static void main(String[] args) {

        new ThreadDome1().start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }
}

实现Runale接口

还有一个是实现这个接口的。

public class RunableDome implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {

            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }

    public static void main(String[] args) {
        RunableDome run = new RunableDome();
        new Thread(run, "小明").start();
        new Thread(run,"小红").start();
    }

}

这个有什么好处就不用多说了(多个线程可以控制一个对象),有什么问题(并发)才谁我们后面的重点之一。

Callable接口

这玩意和runable类似,但是的话有返回值,然后我们用线程池跑了一下。

public class CallabelDome implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
        return true;
    }

    public static void main(String[] args) {

        CallabelDome t1 = new CallabelDome();
        CallabelDome t2 = new CallabelDome();

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<Boolean> submit = executorService.submit(t1);
        Future<Boolean> submit1 = executorService.submit(t2);

        executorService.shutdownNow();
    }


}

线程周期

这个看一张图即可明白

image.png

中文图

image.png

这里提一下先前那个线程停止的方式,我们一般是使用自己定义的方式(在外部停止的话)

public class RunableDome implements Runnable{

    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void stop(){
        this.setFlag(false);
        System.out.println("主线程调用stop让该线程停止运行");
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            if(!flag)
                return;
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }

    public static void main(String[] args) {
        RunableDome run = new RunableDome();
        new Thread(run,"子线程").start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
            if(i==950){
                run.stop();
            }
        }
    }

}

CPU调度

这个主要是关于线程一些,优先级,优先执行,调度转让的内容。

一个是线程转让

Thread.yield() 

就可以让 A 线程转让(假设在A线程执行这个代码)让CPU去调度别的线程,当然不一定成功。

Thread.setPriority(2)

这个就是那个优先级的那个,谁先执行,0到10 最大是10,当然这个也是得看cpu心情

还有一个设置守护线程,也就是主线程结束子线程就结束

我就使用了一个setDaemo()

public class RunableDome implements Runnable{

    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void stop(){
        this.setFlag(false);
        System.out.println("主线程调用stop让该线程停止运行");
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            if(!flag)
                return;
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }

    public static void main(String[] args) {
        RunableDome run = new RunableDome();
        Thread thread = new Thread(run, "子线程");
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
        }
    }

}

可以看到我们的子线程要跑10000次的结果只跑了1000多次,因为主线程结束了。

image.png

还有最后一个join强制获取CPU调度权,还是刚刚那个例子,我就要让子线程执行玩。
那么我就可以这样

image.png

image.png

线程安全问题(并发问题)

现在开始进入到我们应该开始关注的问题了。

首先我们举个买票的例子,这个也是很多人都说过的例子,以前我学python写的博客的时候使用的什么工厂的例子,那是是我自己相的,但是有点抽象,java的说那个的时候,我直接上代码,当时就是给自己看到呗,现在还是要考虑一下不仅自己要看得明白,别人也得更好看一点。

先上代码,这个例子很简单。

public class BuyTicket implements Runnable{

    private int tickets = 1000;//一开始1000张票
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            tickets --;
            System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票");

        }
    }

    public static void main(String[] args) {
        Runnable tickets = new BuyTicket();
        new Thread(tickets,"小明").start();
        new Thread(tickets,"小红").start();
        new Thread(tickets,"小容").start();


    }
}

image.png

看输出结果我们发现输出了三次936,也就是产生了错乱,也就是不安全。

原理的话其实很简单。我们可以举个例子模拟代码运行原因。

image.png

所以我们现在要解决这个问题。

我们在放大一下问题。用那个sleep放大一下问题。



public class BuyTicket implements Runnable{

    private int tickets = 10;//一开始1000张票
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            tickets --;
            System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票还剩下"+tickets+"张票");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        Runnable tickets = new BuyTicket();
        new Thread(tickets,"小明").start();
        new Thread(tickets,"小红").start();
        new Thread(tickets,"小容").start();


    }
}

image.png

方案一,直接加synchronized

我们现在直接看这个代码

public class BuyTicket implements Runnable{


    private int tickets = 10;//一开始1000张票
    @Override
    public synchronized void run() {

        for (int i = 0; i < 10; i++){

            if(isEmpty(tickets))
                return;
            System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票还剩下"+tickets+"张票");
            tickets --;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public int getTickets() {
        return tickets;
    }


    public boolean  isEmpty(int tickets){
        if(tickets<=0){
            return true;
        }
        return false;

    }


    public static void main(String[] args) throws InterruptedException {
        BuyTicket tickets = new BuyTicket();
        Thread t1 = new Thread(tickets, "小明");
        Thread t2 = new Thread(tickets, "小红");
        Thread t3 = new Thread(tickets, "小容");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();


        System.out.println(tickets.getTickets());


    }
}

加上这个synchronized其实就是相当于加了一把锁,但是问题解决了不。
我们可以看看结果

image.png

可以看到我们得到了一个想要的值,显然我们的这个方式是OK的,那好我们再看一下执行的时间。

image.png

image.png

花费了1秒。(这里其实还有个小细节其实我们可以发现这个sleep其实是不会释放掉当前线程的锁的,除非你强行释放了)

但是这里明显有一个问题那就是我们发现,这个是直接锁代码块(默认锁的是当前对象也就是this)那么就有问题了,你锁住了,10张票都被一个人买走了,显然是不行的,所以我们干嘛,得去想办法解决这个问题,那就是我们发现这我们需要上锁的对象其实一直都是只有一个家伙,那就是ticket,所以我们其实是可以直接对ticket进行同步的,也就是把这个家伙提取出来,因为这个synchornized需要接受对象,或者Class。

提取Tickets

class Ticket{
    private int tickets = 10;

    public int getTickets() {
        return tickets;
    }

    public void setTickets(int tickets) {
        this.tickets = tickets;
    }

    public void sell(int ticket){

        System.out.println(Thread.currentThread().getName()+"买了第:"+(ticket)+"张,票还剩下"+getTickets()+"张");
        setTickets(getTickets()-1);
    }
}


public class BuyTicket implements Runnable{


    private Ticket tickets = new Ticket();//一开始1000张票
    @Override
    public  void run() {


            for (int i = 0; i < 10; i++) {
                synchronized (tickets) {
                    if (tickets.getTickets() <= 0) {
                        return;
                    }
                    tickets.sell((i + 1));
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }



    public static void main(String[] args) throws InterruptedException {
        BuyTicket tickets = new BuyTicket();
        Thread t1 = new Thread(tickets, "小明");
        Thread t2 = new Thread(tickets, "小红");
        Thread t3 = new Thread(tickets, "小容");

        long l = System.currentTimeMillis();

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        long l1 = System.currentTimeMillis();

        System.out.println("此时的票数为:"+ tickets.tickets.getTickets());
        System.out.println("执行时长为"+(l1-l));


    }
}

这个执行时长也差不多也是一样的。

image.png

image.png

上锁

我们使用代码synchronized代码块的话多多少少还是有点不方便的。所以我们和python一样可以上个锁。

我们回到最开始的代码,直接上锁。

public class BuyTicket implements Runnable{

    private int tickets = 10;//一开始1000张票
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            try {
                lock.lock();
                if (tickets <= 0) return;
                tickets--;
                System.out.println(Thread.currentThread().getName() + "买了第:" + (i + 1) + "张票还剩下" + tickets + "张票");

            }finally {
                lock.unlock();
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public int getTickets() {
        return tickets;
    }

    public static void main(String[] args) throws InterruptedException {
        BuyTicket tickets = new BuyTicket();
        Thread t1 = new Thread(tickets, "小明");
        Thread t2 = new Thread(tickets, "小红");
        Thread t3 = new Thread(tickets, "小容");

        long l = System.currentTimeMillis();

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        long l1 = System.currentTimeMillis();

        System.out.println("此时的票数为:"+ tickets.getTickets());
        System.out.println("执行时长为"+(l1-l));


    }
}

接下来是执行时间这里我强行放锁了。

image.png

404 ms

如果我不放的话(睡眠后的话)

image.png

image.png

生成者与消费者

前面两个主要是说了synchronized,这个东西有点“重”所以我们用了锁,但是这个锁是比较耗费资源的,所以我们还有这个模式,这个也是我写那个原来java多线程的内容不仔细的地方,因为一开始我的案例就是用这个的,原因也是在python里面说了很多次,但是其实python里面有个东西我没说到,那就是协程,在python里面是有协程的,不过本质上还是一个单线程,这个和Go语言的协程有别开来,那个go语言的协程是指一个模式,那个其实是多线程,而且用那个管道思想,让Go的线程实现不仅简单而且非常快速的解决了同步通信的问题,很不错!有时间可以聊聊,Go也是个很不错的语言,只是目前我想做的事情Go给的支持还不够。

OK 那么现在回到我们这里,我们说生产者消费者模式,那么其实这里具体的实现大致有两种思路,一种就是和python一样用队列,这个叫做管程法,还有就是用标志,叫做信号灯法,这里我都会有说明,不要着急,毕竟我也不想去翻翻老博客。

当然其实从代码上面说的话,这个和我们的锁是类似的。只是这里使用了另一个玩意,叫做wait()

这里你可能会好奇这个wait和unlock()的区别,我这里直接说一些结论:

  1. 首先二者都是释放资源。
  2. 前者用于线程通信,wait()之后配置notify使用。
  3. 而lock的作用是保护多线程访问的共享资源
  4. 线程之间的通信方法Condition.await 和 Condition.signal (Object.wait 和 Object.notify) 的调用前提是必须获取到锁,如果没有获取到锁就直接调用,则会抛出java.lang.IllegalMonitorStateException 异常
  5. A线程调用 Lock.lock() 导致线程阻塞,如果B线程调用 Lock.unlock() 则自动会解除A线程的阻塞状态,该机制是JVM调度器来唤醒,不需要B线程显示的唤醒A
  6. A线程调用了 Condition.await(Object.wait),如果要唤醒A线程,那么B线程必须做到:
  1. A 获取到锁(因为A线程wait释放了锁)
  2. B 调用 Condition.signal(Object.notify),这个时候A还不能执行,只是被唤醒,唤醒后需要重新获取锁
  3. C 调用unlock释放锁,只有B线程释放了锁,A线程才能真正的被唤醒,然后获取到锁执行

信号灯

先来我们的信号灯法,这个实现比较快。同时也是使用我们这个wait()的。
首先我们要有消费者,生产者,以及产品
在我们这里消费者就是顾客,生产者就是售票处,产品自然就是票
这个直接上代码,比较简单。


class Ticket{
    public int tickets=0; //票的数量
    private boolean flag = false;//还有票不

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public synchronized void buy() throws InterruptedException {
        if(!isFlag()){
            wait();
        }
        tickets --;
        notifyAll();
        this.flag = !this.flag;
    }
    public synchronized void make() throws InterruptedException {
        if(isFlag()){
            wait();
        }
        tickets ++;
        notifyAll();
        this.flag = !this.flag;
    }
}

class Salesman extends Thread{
    Ticket ticket;

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                ticket.make();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Customer extends Thread{
    Ticket ticket;
    public Customer(Ticket ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                if(ticket.tickets<=0){
                    return;
                }
                ticket.buy();
                System.out.println(Thread.currentThread().getName() + "买了第:" + (i + 1) + "张票还剩下" + ticket.tickets + "张票");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Test{
    public static void main(String[] args) throws InterruptedException {
        Ticket ticket = new Ticket();
        Salesman salesman = new Salesman(ticket);

        Customer customer1 = new Customer(ticket);
        Customer customer2 = new Customer(ticket);

        salesman.setName("售票处");
        customer1.setName("小容");
        customer2.setName("小侯");

        long start = System.currentTimeMillis();

        salesman.start();
        customer1.start();
        customer2.start();

        salesman.join();
        customer1.join();
        customer2.join();
        long end = System.currentTimeMillis();

        System.out.println("执行了"+(end-start)+"毫秒还有票数:"+ticket.tickets);

    }
}

那么这就是我们的这个信号灯法。

管程法

image.png

这个也是,不过要稍微麻烦一点,不过可以直接使用synchronized实现,也不难。



class OneTicket{

    public OneTicket(int id) {
        this.id = id;
    }

    private int id;

    public int getId() {
        return id;
    }

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

class TicketContainer{

    OneTicket[] tickets = new OneTicket[10];//10个票
    int count = 0;
    public synchronized void push(OneTicket ticket) throws InterruptedException {
        if(count==tickets.length){
            wait();
        }
        tickets[count] = ticket;
        count++;
        notifyAll();
    }
    public synchronized OneTicket pop() throws InterruptedException {
        if(count==0){
            wait();
        }
        count--;
        OneTicket ticket = tickets[count];
        notifyAll();
        return ticket;
    }


}

class Peopel extends Thread{
    private TicketContainer ticketContainer;

    public Peopel(TicketContainer ticketContainer) {
        this.ticketContainer = ticketContainer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            try {
                Thread.sleep(100);
                OneTicket pop = ticketContainer.pop();
                System.out.println(Thread.currentThread().getName() + "买了第:" + pop.getId() + "张票还剩下" + ticketContainer.count + "张票");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SalesPeopel extends Thread{
    private TicketContainer ticketContainer;

    public SalesPeopel(TicketContainer ticketContainer) {
        this.ticketContainer = ticketContainer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生成了第"+(i+1)+"张票");

            try {
                ticketContainer.push(new OneTicket((i+1)));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



class TestDome{
    public static void main(String[] args) throws InterruptedException {

        TicketContainer ticketContainer = new TicketContainer();

        SalesPeopel salesman = new SalesPeopel(ticketContainer);

        Peopel customer1 = new Peopel(ticketContainer);
        Peopel customer2 = new Peopel(ticketContainer);

        salesman.setName("售票处");
        customer1.setName("小容");
        customer2.setName("小侯");

        salesman.start();
        customer1.start();
        customer2.start();

    }
}

image.png

这里注意的是那个终止条件的问题(我没加),所以的话会一值堵塞。

此外:
这里的话我其实是有一个案例的,不过是使用python的多线程爬虫写的,爬取小姐姐的图片来者。代码是这个,理论都是一样的,只是我们这边使用的queue是人家写好的堵塞的玩意。

import requests
from lxml import etree
import threading
import os
from urllib import request
from queue import Queue
url_frist_save=Queue(1000)
#url_save=Queue(200)
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}

url_root='http://pic.netbian.com'
def get_url():
    date_frist=requests.get(url_root,headers=headers)
    date_frist=date_frist.content.decode('gbk')
    date_frist_=etree.HTML(date_frist)
    uel_1=date_frist_.xpath('//li//a[@href and @title and @target]/@href')[4:]
    for i in uel_1:
        url=url_root+i
        url_frist_save.put(url)
    for i in range(2,101):
        urlx='http://pic.netbian.com/index_{name}.html'.format(name=str(i))
        date_frist = requests.get(urlx, headers=headers)
        date_frist = date_frist.content.decode('gbk')
        date_frist_ = etree.HTML(date_frist)
        uel_1 = date_frist_.xpath('//li//a[@href and @target]/@href')[1:]
        for i in uel_1:
            url = url_root + i
            url_frist_save.put(url)


def down_load():
    if not  os.path.exists(r'C:Usersa3139Desktopprojects爬虫domepictures'):
        os.makedirs(r'C:Usersa3139Desktopprojects爬虫domepictures')
    while True:
        date_frist=requests.get(url_frist_save.get(),headers=headers)
        date_frist=date_frist.content.decode('gbk')
        date_frist_=etree.HTML(date_frist)
        url_down=url_root+str(date_frist_.xpath('//div[@class="photo-pic"]/a/img/@src')[0])
        name=str(date_frist_.xpath('//div[@class="photo-pic"]/a/img/@title')[0])
        path=r'C:Usersa3139Desktopprojects爬虫domepictures'+'\'+name+'.jpg'
        print(path)
        print(url_down)
        try:
            request.urlretrieve(url_down,path)
            request.urlcleanup()
        except:
            print('此图片下载失败')

        if url_frist_save.empty():
            print(url_frist_save.empty())
            break

if __name__=="__main__":

    for i in range(5):
        t1=threading.Thread(target=get_url)
        t2=threading.Thread(target=down_load)
        t1.start()
        t2.start()



应该还能用。

此外的话还有这个线程池,这个就是方便管理,我先前还记得有关一个线程池的面试题,关于线程池的七个参数,好像是,这个就今天早上说一下吧。

线程池

这个其实一开始有我们的演示。

public class Pools {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());
        executorService.execute(new MyThread());

        executorService.shutdownNow();//用完要关
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println("FUcking");
    }
}

这个是最简单的固定大小的,实际上我们有动态的,这个是一个考点,也是个小细节,毕竟要压榨资源呗~
早上再补充喽,那么计划的第一部分就OK了。

最后

以上就是悲凉手机为你收集整理的java多线程(分分钟基础秒杀)前言线程安全问题(并发问题)线程池的全部内容,希望文章能够帮你解决java多线程(分分钟基础秒杀)前言线程安全问题(并发问题)线程池所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部