首页 > 其他分享 >多线程(三)

多线程(三)

时间:2023-08-09 19:01:08浏览次数:28  
标签:private 线程 println 多线程 public Bank out

1、线程安全的懒汉式

1.1、线程安全的懒汉式,代码如下:

//一个单一设计模式的类如下:
public class Bank {
    private double account;
    private String name;

    private static Bank instance = null;

    //私有化构造器
    private Bank() {
    }

    private Bank(double account, String name) {
        this.account = account;
        this.name = name;
    }

    public void setAccount(double account) {
        this.account = account;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getAccount() {
        return account;
    }

    public String getName() {
        return name;
    }

    //这里就会出现线程安全的问题,我们可以利用同步方法来解决这个问题
    public synchronized static Bank getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
            instance = new Bank();
        }
        return instance;
    }

}

//测试类
public class Test {

    private static Bank b1 = null;
    private static Bank b2 = null;

    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                b1 = Bank.getInstance();
            }
        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                b2 = Bank.getInstance();
            }
        };

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

        //主线程在执行到下面的输出语句时分线程还没执行到,所以b1和b2都还没有被赋值,我们需要让分线程先执行!
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b1 == b2);

    }
}


输出结果:

Test02.Bank@41629346 Test02.Bank@41629346 true

这里有一种优化方案,就是当一个线程创建好对象之后,后面的线程就可以不用再去执行同步代码块里面的内容,而是直接拿着对象走,相当于去店里排队买东西,当东西已经卖完的时候,后面的人就不需要再接着去排队,而是直接走人。

优化后的代码如下所示:

public class Bank {
    private double account;
    private String name;

    private static Bank instance = null;

    //私有化构造器
    private Bank() {
    }

    private Bank(double account, String name) {
        this.account = account;
        this.name = name;
    }

    public void setAccount(double account) {
        this.account = account;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getAccount() {
        return account;
    }

    public String getName() {
        return name;
    }

    //这里就会出现线程安全的问题,我们可以利用同步方法来解决这个问题
    public static Bank getInstance() {
        if (instance == null) {         //如果这个对象不为空,那么就直接得到相应的对象,而不再去走同步代码块里面的内容
            synchronized (Bank.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(e.getMessage());
                    }
                    instance = new Bank();
                }
            }

        }
        return instance;
    }

}

但是这里依旧会存在一个问题:指令重排,就是当一个线程进入同步代码块时,在创建对象的时候已经把对象创建好了,但是此时还没有执行创建对象的init方法,就是还没有进行初始化,那么后面的线程在进行if判断的时候就会直接跳过,从而获得一个不完整的对象,引发问题。

解决办法,给共享数据加上:volatile,如下所示:

private static volatile Bank instance = null;
2、死锁

2.1、死锁的定义:不同的线程分别占用对方需要的同步资源不放,都在等待对方放弃自己需要的同步资源,从而形成死锁。

死锁的例子:

public class Test {
    public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(e.getMessage());
                    }

                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("a");
                    s2.append("1");

                    synchronized (s1) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

    }
}

2.2、诱发死锁的原因:

互斥条件

占用且等待

不可抢夺(或不可抢占)

循环等待

3、ReentrantLock的使用(在jdk5.0之后)

3.1、使用的步骤:

①、首先声明ReentrantLock的对象(注意要解决线程安全问题这里依旧要保证对象唯一!)

②、调用ReentrantLock对象里面的lock和unlock方法,将操控共享数据的代码放在lock和unlock之间

测试代码如下所示:

import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
    private int ticket = 100;       //票数
    private Object object = new Object();

    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true) {

            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }

            try {

                lock.lock();

                if (this.ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在售票\t" + "票号为:" + this.ticket);
                    this.ticket--;
                } else {
                    System.out.println("票已卖完!");
                    break;
                }

            } finally {
                lock.unlock();		//为了让资源一定能得到释放
            }

        }
    }
}
4、线程通信

4.1、为什么需要线程通信:当我们需要多个线程来共同完成同一件任务,并且我们希望它们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此来实现多线程共同操作一份数据。

比如:线程A用来生产包子,线程B用来吃包子,包子可以理解为同一资源,线程A和线程B处理的动作,一个是生产,一个是消费,此时线程B必须等待A线程完成后才能执行,那么线程A和线程B之间就需要线程通信,即:==等待唤醒机制!==

案例引入:现在有一个需求,有两个线程,我们现在要将两个线程交替打印出1 - 100。

注意:wait()和sleep的一个区别:执行wait方法时会释放对同步监视器的占用,而sleep不会释放!

public class PrintNum  implements  Runnable{
    private static final ReentrantLock lock = new ReentrantLock();
    private int num = 1;
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }

            synchronized (this){
                this.notify();			//一旦执行此方法,就会唤醒被wait的优先级最高的线程,被唤醒的线程接着当初被wait的地方继续执行。如果有多个优先级相同的线程,那么就随机唤醒一个,如果要唤醒全部,可以用notifyAll(),其中这三个方法必须写在同步代码块或者同步方法中,不能使用在lock中。
                if(num <=100){
                    System.out.println(Thread.currentThread().getName() + ":\t" + num);
                    num++;
                    try {
                        this.wait();		//一旦执行此方法,线程就进入等待状态,并且释放对同步监视器的占用
                    } catch (InterruptedException e) {
                        System.out.println(e.getMessage());
                    }
                }else {
                    break;
                }
            }
        }
    }
}

注意:

①、三个方法的调用者必须是同步监视器,否则会报异常!

②、其中这三个方法必须写在同步代码块或者同步方法中,不能使用在lock中。

5、生产消费问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

//销售作为公共资源
public class Clerk {
    private int number = 0;                     //起始的数量
    private static final int MAX = 20;          //最大的数量

    //生产产品(增加产品)
    public synchronized void produceProduct() {

        if(number >= Clerk.MAX){
            System.out.println("数量达标,已等待生产!");
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }else {
            number++;
            System.out.println(Thread.currentThread().getName() + "开始生成产品了!当前产品数量为:" + this.number);
            notifyAll();       //只要生产了一个产品就唤醒消费者
        }

    }

    //销售产品(减少产品)
    public synchronized void buyProduct() {
        if(number <= 0){
            System.out.println("店中没有产品了,请消费者等一下");
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }else {
            number--;
            System.out.println(Thread.currentThread().getName() + "消费了产品!当前产品数量为:" + this.number);
            notify();      //只要消费了产品就唤醒生产者
        }
    }

}

//顾客
public class Customer implements Runnable {
    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
            clerk.buyProduct();
        }
    }
}

//生产者
public class Producer implements Runnable {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
            clerk.produceProduct();
        }
    }
}

6、创建线程的其它两种方式

6.1、实现Callable接口,使用如下所示:

import java.util.concurrent.Callable;

public class MySum implements Callable {

    @Override
    public Object call() throws Exception {     //前面的返回值类型是一个泛型
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

//测试类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) {
        MySum mySum = new MySum();
        FutureTask futureTask = new FutureTask(mySum);
        Thread t1 = new Thread(futureTask);
        t1.start();

        //获取返回值
        try {
            Object sum = futureTask.get();
            System.out.println("100以内的总数为:" + sum);

        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        } catch (ExecutionException e) {
            System.out.println(e.getMessage());
        }

    }
}

该方式的好处:有了返回值,可以让线程的执行变得更加的灵活。==并且它在和主线程同时执行的时候,必须要等它有了返回值之后主线程才能拿到返回值,而不会出现它还没有返回主线程就获得的情况。==

6.2、线程池

如下所示:

定义两个实现Runnable接口的线程操作类如下所示:

public class PrintNum1 implements Runnable{
    @Override
    public void run() {
        System.out.println("20以内的偶数如下:");
        for (int i = 1; i <= 20; i++) {
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class PrintNum2 implements Runnable{
    @Override
    public void run() {
        System.out.println("20以内的奇数如下:");
        for (int i = 1; i <= 20; i++) {
            if(i % 2 != 0){
                System.out.println(i);
            }
        }
    }
}

测试类:

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

public class Test {
    public static void main(String[] args) {
        //提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //类型转换
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
        //设置线程池的线程数量的上限
        service1.setMaximumPoolSize(20);
        //执行指定的线程操作,线程操作需要实现Runnable接口
        service.execute(new PrintNum1());
        service.execute(new PrintNum2());

        // 适用于实现Callbale接口的:service.submit(Callable callable);

        //关闭连接池
        service.shutdown();

    }
}

标签:private,线程,println,多线程,public,Bank,out
From: https://blog.51cto.com/u_15433911/7024135

相关文章

  • python多线程学习记录
    Python多线程参考文章:python多线程详解(超详细)、Python线程池(threadpool)创建及使用+实例代码、第二十章多线程1、多线程的概念2、python多线程的基本使用方法3、多线程的优点及与多进程的关系1、多线程的概念线程也叫轻量级进程,是操作系统能够进行运算调度......
  • 异步编程和多线程的关系
    引用自“https://zhuanlan.zhihu.com/p/570792890中bluecyan的留言”异步编程,它允许我们多个任务(Task)可以同时执行。多线程技术就是CPU利用多个线程来并发地运行多段逻辑。任务是逻辑层面的,线程是操作系统层面的,由线程ID标识,任务比线程抽象层级更高。异步任务可由线程实现,也可......
  • java定时任务中创建多线程却只有一个线程运行的问题
    在定时任务中开启了多线程。。但是却只有第一个线程运行。。原因是?参考:https://www.cnpython.com/java/515558在您的例子中,它是MyRunnable的单个实例,因此当一个线程在synchronized块内执行工作时,所有其他线程将等待工作完成。因此,有效地说,一次只有一个线程在做真正的工作......
  • .NET Core多线程 (2) 异步 - 上
    去年换工作时系统复习了一下.NETCore多线程相关专题,学习了一线码农老哥的《.NET5多线程编程实战》课程,我将复习的知识进行了总结形成本专题。本篇,我们来复习一下异步的相关知识点,预计阅读时间10分钟。理解异步的本质(1)异步是什么?举个例子,在高峰期去餐厅吃饭,会先排队拿个小票,......
  • new Thread().start(); - 多线程练习
     用Java创建一个线程是这样的:Threadthread=newThread();要启动Java线程,您将调用其start()方法,如下所示:   thread.start();此示例未指定要执行的线程的任何代码。线程启动后会立即再次停止。所以要往线程里写入代码。Threadthread=newThread(){@Override......
  • 掌握.NET多线程编程技术
    当涉及到处理并发任务、提高程序性能以及充分利用多核处理器时,.NET多线程编程技术就变得至关重要。在本篇博客中,我将为您介绍一些.NET多线程编程的基本概念和技术,并附上一些示例代码来帮助您更好地理解。为什么使用多线程编程?多线程编程允许在同一进程中同时执行多个线程,从而充分利......
  • java多线程 sleep()和wait()的区别
    java多线程sleep()和wait()的区别作者:octobershiner(2篇文章)日期:十二月16,2011在11:58上午接触了一些多线程的东西,还是从java入手吧。相信看这篇文章的朋友都已经知道进程和线程的区别,也都知道了为什么要使用多线程了。这两个方法主要来源是,sleep用于线程控制,而w......
  • java多线程编程基础4--正确使用 Volatile 变量
    记住一点就是:对数据的操作是分三步:1.从内存从拿过来2.更改数据3.写入内存 还需要记住一点就是:我们在存储数据的时候,不一定是将数据存放在内存中的,有时放在寄存器中的。所以多线程操作的时候,共有变量A如果被一个线程操作,并且存放在寄存器中并没写入到内存中,而另一个线程操作这个......
  • c#多线程并发执行一个操作函数
    有时候我们进行很多的数据对比运算时,单线程的程序显得很慢,这个时候可以用多线程并发运行:intmaxThread=10;//10个并发线程intcurrTNum=0;WaitHandle[]whs=newWaitHandle[maxThread];//WaitHanle类MSDN上的定义:封装了......
  • Java HTTP多线程下载实现方法
    JavaHTTP多线程下载实现方法在如今互联网高速发展的时代,对于下载来说,速度是一个非常重要的因素。而多线程下载是一种提高下载速度的常见方法。本文将介绍如何使用Java编程实现HTTP多线程下载功能。JavaHTTP多线程下载实现方法一、背景知识在开始实现之前,我们需要了解一些基本......