多线程详解(代码片段)

好好学java的xz 好好学java的xz     2023-02-21     304

关键词:

4.4 题外话

根据多线程详解(一)的同步,
我们可以使用同步机制将单例模式中的懒汉式改写为线程安全的。

举例:

public class BankTest   
class Bank
    private Bank()
    private static Bank instance = null;

    public static Bank getInstance() 
//方式一:在方法上 加 synchronized 效率稍差

//方式二:效率稍差 只要判断一次同步一次 后面就不用同步了
//解决方案就相当于只有一台手机 买完人走了 出公告 手机卖完了 不用再进来了
//        synchronized (Bank.class) 
//            if (instance == null)
//                instance = new Bank();
//            
//            return instance;
//        

        //方式三:效率更高
        if (instance == null)
            synchronized (Bank.class) 
                if (instance == null)
                    instance = new Bank();
                
            
        
        return instance;//这行就不算操作
    

5 线程的死锁问题

5.1 概念

5.1.1 死锁的理解

不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

5.1.2 说明

1.出现死锁后,不会出现异常,不会出现提示,
只是所有的线程都处于阻塞状态,无法继续

2.我们使用同步时,要避免出现死锁

5.1.3举例

一人一个筷子,互不相让,就打起来了。

5.2 解决方法

  1. 专门的算法、原则
  2. 尽量减少同步资源的定义
  3. 尽量避免嵌套同步

5.3 演示线程的死锁问题

public class ThreadTest 
    public static void main(String[] args) 
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread()
            @Override
            public void run() 
                synchronized (s1)//s1是锁
                    s1.append("a");
                    s2.append("1");
                    //让死锁的概率高一点
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    synchronized (s2)
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    
                
            
        .start();
        new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (s2)
                    s1.append("c");
                    s2.append("3");
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    synchronized (s1)
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    
                
            
        ).start();
    

一个等着拿s2 一个拿着s2等着拿s1 互相僵持

6.JDK5.0 新增解决线程安全问题

6.1 概念

解决线程安全问题的方式三: Lock锁 —JDK5.0新增

》通过显式定义同步锁对象来实现同步。
》同步锁使用Lock对象充当。

6.2 步骤

  1. 实例化ReentrantLock
  2. 调用锁定方法lock()
  3. 调用解锁方法:unlock()

6.3 举例

使用Lock锁解决实现Runnable接口的线程安全问题

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();
    //默认值是false  参数是boolean fair(公平)
    // 设置true 先进先出 一个个执行 不会出现有个执行了下一刻又抢到了
    @Override
    public void run() 
        while (true)
            try 
                //2.调用锁定方法lock()
                lock.lock();//类似同步监视器 下面变成了单线程
                if (ticket > 0)
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket --;
                else 
                    break;
                
            finally 
                //3.调用解锁方法:unlock()
                lock.unlock();
            
        
    

public class LockTest 
    public static void main(String[] args) 
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

如果你用的是继承于Thread类 lock要加个静态 要用同一个

6.4 synchronized与Lock 的异同?(面试题)

相同:
二者都可以解决线程安全问题

不同:
synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。
Lock需要手动的启动同步(Lock()) ,同时结束同步也需要手动的实现(unLock())。

优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

7.线程的通信

7.1 线程通信的例子

使用两个线程打印1-100。线程1,线程2交替打印(一个一个交互进入)

7.2 涉及到三个方法及注意点

7.2.1 方法

wait():
一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():
一旦执行此方法,就会唤醒被wait的一个线程。
如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():
一旦执行此方法,就会唤醒所有被wait的线程

7.2.2 注意点:

1.wait(), notify(), notifyAll() 三个方法必须使用在同步代码块或同步方法中。

2.wait(), notify(), notifyAll() 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现IllegaLMonitorStateException异常

3.wait(), notify(), notifyAll() 三个方法是定义在java.lang.Object类中。

7.2.3 面试题: sleep() 和 wait() 的异同?

相同点:
一旦执行方法, 都可以使得当前的线程进入阻塞状态。

不同点:
1)两个方法声明的位置不同: Thread类中声明sleep(),object类中声明wait()

2)调用的要求不同:
sleep()可以在任何需要的场景下调用。
wait()必须使用在同步代码块或同步方法中

3)关于是否释放同步监视器:
如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait() 会释放锁。

7.3 举例

class Number implements Runnable 
    private int number = 1;//共享数据

    @Override
    public void run() 
        while (true)
            synchronized (this)  //this代表number对象
                notify();//线程一把线程二唤醒 线程二把线程一唤醒 notifyAll()就是唤醒所有  省略了this
                if (number <= 100) 
                    try 
                        Thread.sleep(10);//sleep 不会释放锁
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    //使得调用如下wait() 方法的线程进入阻塞状态 wait会释放锁
                    try 
                        wait();//  省略了this
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                else 
                    break;
                
            
        
    

public class CommunicationTest 
    public static void main(String[] args) 
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    

8.JDK5.0新增线程创建方式

8.1 多线程创建,方式三:实现Callable接口

8.1.1 步骤

Future接口最重要

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,
    创建FutureTask 的对象
  5. 将Future Task的对象作为参数传递到Thread类的构造器中,
    创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值

8.1.2 Callable比Runnable更强大

如何理解实现Callable接口的方式创建多线程
比实现Runnable接口创建多线程方式要强大

  1. call()可以有返回值的
  2. call() 可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的

8.1.3 举例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer> 
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception //回调方法
        int sum = 0;
        for (int i = 1 ; i <= 100 ; i++) 
            if (i % 2 == 0)
                System.out.println(i);//分线程
                sum += i;
            
        
        return sum;
    

public class ThreadNew 
    public static void main(String[] args) 
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask 的对象
        FutureTask<Integer> futureTask = new FutureTask<>(numThread);
        //5.将Future Task的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try 
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的calL()的返回值。
            Integer sum = futureTask.get();//调get方法获取Callable接口实现类的回调方法
            System.out.println("总和为:" + sum);//主线程
         catch (InterruptedException e) 
            e.printStackTrace();
         catch (ExecutionException e) 
            e.printStackTrace();
        
    

8.2 多线程创建,方式四:使用线程池(开发常用的)

8.2.1 介绍

背景:
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

解决方案:
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

8.2.2 步骤

  1. 提供指定线程数量的线程池
  2. 执行指定的线程的操作。
    需要提供实现Runnable接口 或 Callable接口实现类的对象
  3. 关闭线程池

8.2.3 好处

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

8.2.4 举例

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

class NumberThread implements Runnable 
    @Override
    public void run() 
        for (int i = 1 ; i <= 100 ; i++) 
            if (i % 2 == 0)
                System.out.println(Thread.currentThread().getName() + ":" + i);//分线程
            
        
    

class NumberThread1 implements Runnable 
    @Override
    public void run() 
        for (int i = 1 ; i <= 100 ; i++) 
            if (i % 2 != 0)
                System.out.println(Thread.currentThread().getName() + ":" + i);//分线程
            
        
    

public class ThreadPool 
    public static void main(String[] args) 
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//造了个线程池
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
       //设置线程池的属性
//        System.out.println(service.getClass());//获取是哪个类造的
        service1.setCorePoolSize(15);

        //2.执行指定的线程的操作。需要提供实现Runnable接口 或 Callable接口实现类的对象
        //线程要干什么不知道 所以还是要提供实现 Runnable接口的 实现类
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable
//       service.submit(Callable callable);//适合适用于Callable

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

9. 面试题

一共有几种多线程创建方式? 》 4种

解决线程安全问题? 》3种

多线程详解---(多案例实战)(代码片段)

多线程1、区分单线程和多线程单线程:就像是做饭,洗衣服,煮水,一个一个进行多线程:在单线程的基础上,可以考虑煮水的时候,洗衣服节约时间packagecom.kong.thread;//创建线程,重写run方法࿰... 查看详情

java多线程详解(代码片段)

线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线... 查看详情

java多线程详解(代码片段)

多线程创建方式Thread类定义一个子类MyThread继承线程类Java.lang.Thread,重写run()方法创建MyThread对象调用线程对象的start()方法启动线程(启动后还是执行run方法)优缺点优点:编码简单缺点:线程类已经继承Thread,无法继承其他类... 查看详情

详解c++11多线程(代码片段)

c++的多线程可以充分利用计算机资源,提高代码运行效率。在这里总结了一些多线程应用过程中的基本概念和用法。一、进程与线程进程是资源分配和调度的一个独立单位。而线程是进程的一个实体,是CPU调度和分派的基本单位... 查看详情

ios多线程详解(代码片段)

iOS多线程详解Slogan:可能是最通俗易懂的iOS多线程详细解析文章1.基础概念1.1进程进程是计算机中已运行程序的实体,是线程的容器维基百科-进程。每个进程之间是相互独立的,每个进程均运行在器专用且收保护的内存空... 查看详情

java多线程:线程池详解(代码片段)

文章目录1.线程池1.1线程池概述1.1.1线程池的概念1.1.2线程池的工作机制1.1.3使用线程池的原因1.1.3线程池的设计思路:1.2线程池的创建1.2.1Executors默认线程池newCachedThreadPool:创建默认线程池,最多容纳int类型的最大值newFixedT... 查看详情

c#多线程thread实例详解(代码片段)

1.Thread线程启动由于ThreadStart是一个委托,所以可以简化写法staticvoidMain(string[]args)Console.WriteLine("----------主程序开始,线程ID是0-----------------",Thread.CurrentThread.ManagedThreadId);for(inti=0;i<5;i++)ThreadSta 查看详情

狂神说java笔记--多线程详解部分笔记(代码片段)

传送门==>B站遇见狂神说Java多线程详解做笔记时有的知识点并没有整理;ml1.线程创建之继承Thread类图片下载练习2.线程创建之实现Runnable接口买票案例模拟龟兔赛跑3.线程创建之实现Callable接口4.静态代理模式5.Lambda表达式6.... 查看详情

sqlalchemy多线程下事务隔离机制详解(代码片段)

1.起因通过开启多线程,并发查询订单详情信息,通过将不同订单对象发送给不同线程,执行完所需要的业务逻辑之后,对订单对象的数据进行修改,然后执行commit,查询数据库发现数据没有更新,且后台日志没有任何的报错**... 查看详情

[linux]linux多线程详解(代码片段)

目录1.线程概念1.1什么是线程1.2从操作系统看线程1.3线程的分类1.4线程的优缺点2.线程控制2.1线程创建2.2线程终止2.3线程等待2.4线程分离3.线程安全3.1线程不安全的现象3.1如何解决--互斥锁3.1.1互斥锁原理3.1.2互斥锁接口3.2死锁3.2.1... 查看详情

详解c++多线程(代码片段)

一、线程的同步和互斥  同步是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运... 查看详情

java多线程详解(代码片段)

多线程基本概念每个运行的程序就是一个进程,当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个进程。进程的特性:独立性:每一个进程都拥有自己的私有地址空间。在没有经过进程本身允许的情... 查看详情

分析详解python多线程与多进程区别(代码片段)

python的多线程比较鸡肋,优先使用多进程1基础知识现在的PC都是多核的,使用多线程能充分利用CPU来提供程序的执行效率。1.1线程线程是一个基本的CPU执行单元。它必须依托于进程存活。一个线程是一个executioncontext(... 查看详情

java多线程:线程池详解(代码片段)

文章目录线程池注意点:线程池都有哪些状态?谈谈线程池的拒绝策略?线程池的队列大小通常怎么设置?1.CPU密集型任务2.IO密集型任务3.混合型任务线程池有哪些参数,各个参数的作用是什么?线程池注意点:... 查看详情

java多线程详解总结(代码片段)

...的单位,系统在运行时会为每个进程分配不同的内存区域线程(thread):进 查看详情

python多进程详解(代码片段)

1、由于python多线程适合于多IO操作,但不适合于cpu计算型工作,这时候可以通过多进程实现。python多进程简单实用#多进程,可以cpu保持一致,python多线程适合多io.对于高cpu的可以通过多进程实现。importmultiprocessingimporttimedefrun(nam... 查看详情

c++多线程std::thread详解(代码片段)

...d::thread构造函数三、其他成员函数四、传递临时参数作为线程对象的注意事项4.1解决办法:4.2原因分析4.3总结五、传递类对象、智能指针作为线程参数5.1修改子线程中的对象,不会影响主线程中的对象5.2传递智能指针参考链接:... 查看详情

java多线程基础--线程生命周期与线程协作详解(代码片段)

前言各位亲爱的读者朋友,我正在创作Java多线程系列文章,本篇我们将梳理基础内容:线程生命周期与线程协作这是非常基础的内容,本篇仅从知识完整性角度出发,做一次梳理。作者按:本篇按照自己... 查看详情