首页 > 编程语言 >Java多线程

Java多线程

时间:2024-01-19 22:45:37浏览次数:28  
标签:Java Thread void System 线程 多线程 public out

Java多线程

名词解释

程序(program)
  • 是为完成特定任务、用某种语言编写的一组指令集合。简单而言:就是自己写的代码
进程(Process)
  • 进程是指运行中的程序,比如启动迅雷时,就启动了一个进程,操作系统就会为该进程分配内存空间。

  • 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

线程(Thread)
  • 线程由进程创建的,是进程的一个实体
  • 一个进程可以拥有多个线程
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程
并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行

一、创建线程的两种方式

方法一、继承 Thread 类,重写 run() 方法,调用 start() 开启线程

  • 子类继承 Thread 类具备多线程能力
  • 启动线程:子类对象.start();
  • 不建议使用:避免OOP单继承局限性

注意:线程开启不一定立即执行,由CPU调度

public static void main(String[] args) {
    //main线程为主线程
    
    //创建一个线程对象
    A a = new A(a);
    //调用 start() 方法开启线程
    a.start();
    //说明:当main线程启动一个子线程 A,主线程不会阻塞,会继续执行
    //这时主线程和子线程是并行交替执行
}
......
class A extends Thread {
/*
    1.当一个类继承了 Thread 类,该类就可以当做线程使用
    2.会重写 run 方法,写上自己的业务代码
    3.run Thread 类 实现了 Runnable 接口的run方法
*/
    //线程入口点
    @Override
    public void run() {
        //线程体
        super.run();
    }
}

方法二、实现 Runnable 接口,重写 run() 方法,执行线程需要丢入 Runnable 接口的实现类。调用 start() 方法

  • 实现接口 Runnable 具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start();
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
public static void main(String[] args) {
    //main线程为主线程
    
    //创建一个 Runnable 接口实现类对象
    A a = new A();
    //创建一个线程对象,通过线程对象来开启线程,也就是代理
    Thread thread = new Thread(a);
    //调用 start() 方法开启线程
    thread.start(a);
    
    //说明:当main线程启动一个子线程 A,主线程不会阻塞,会继续执行
    //这时主线程和子线程是并行交替执行
}
......
class A implements Runnable {
/*
    1.当一个类继承了 Thread 类,该类就可以当做线程使用
    2.会重写 run 方法,写上自己的业务代码
    3.run Thread 类 实现了 Runnable 接口的run方法
*/
    //线程入口点
    @Override
    public void run() {
        //线程体
        super.run();
    }
}

代理实例:

//一份资源
StartThread station = new StartThread();
//多个代理
new Thread(station,"小明").start();
new Thread(station,"小红").start();
new Thread(station,"老师").start();

静态代理(Proxy):为其他对象提供一个代理以控制对这个对象的访问。

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 优点:
    • 代理对象可以做很多真实对象做不了的事情
    • 真实对象专注做自己的事情
//创建一个接口
public interface BuyCar {
    public void buyCar();
}
//创建一个实现类
public class BuyCarImpl implements BuyCar {
 
    @Override
    public void buyCar() {
        System.out.println("我要买车~~~啦啦啦");
    }
}
//创建一个代理类
public class BuyCarProxy implements BuyCar{
    private BuyCar buyCar;
    //注意事final修饰的关键字 不可修改
    //构造函数注入,需要被代理的对象
    public  BuyCarProxy(final BuyCar buyCar) {
        this.buyCar = buyCar;
    }
    //静态代理- 的实现方式
    @Override
    public void buyCar() {
        System.out.println("不贷款,全款!买车前的准备~~~");
        buyCar.buyCar();
        System.out.println("买完车了,出去浪~~~");
    }
}
//客户端调用
public abstract class ProxyTest implements BuyCar {
    public static void main(String[] args) {
        System.out.println("-+-+-+正常调用-+-+-+");
        BuyCar car=new BuyCarImpl();
        car.buyCar();
 
        System.out.println("-+-+-+使用静态代理-+-+-+");
        BuyCar proxy=new BuyCarProxy(car);
        proxy.buyCar();
    }
}

Lambda表达式,也可称为闭包。类似于JavaScript中的闭包。

Lambda表达式在Java语言中引入了一个操作符“->”,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

  • 左侧:指定了Lambda表达式需要的所有参数
  • 右侧:制定了Lambda体,即Lambda表达式要执行的功能。
(parameters) -> expression
或
(parameters) ->{ statements; }

函数式接口

只包含一个抽象方法的接口,就称为函数式接口。我们可以通过Lambda表达式来创建该接口的实现对象。
我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

public class TestLambda{
    //3、静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda()}{
            System.out.println("I like lambda2");
        }
    }
    
    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
        
        like = new Like2();
        like.lambda();
        
        //4、局部内部类
        class Like2 implements ILike{
            @Override
            public void lambda()}{
                System.out.println("I like lambda3");
            }
         }
         like = new Like3();
         like.lambda();

         //5、匿名内部类,没有类的名称,必须借助接口或者父类
         like = new Like(){
             @Override
             public void lambda(){
                 System.out.println("I like lambda4");
             }
         };
         like.lambda();

         //6、用Lambda简化
         like = ()->{
             System.out.println("I like lambda5");
         };
         like.lambda();
    }
}

//1、定义一个函数式接口
public interface ILike {
    void lambda();
}

//2、实现外部类
class Like implements ILike{
    @Override
    public void lambda()}{
      	System.out.println("I like lambda");
    }
}

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

下面对每个语法格式的特征进行举例说明:

(1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下:

public void test01(){
    Runnable runnable=()-> System.out.println("Runnable 运行");
    runnable.run();//结果:Runnable 运行
}

(2)语法格式二:Lambda需要一个参数,无返回值。如下:

public void test02(){
    Consumer<String> consumer=(x)-> System.out.println(x);
    consumer.accept("Hello Consumer");//结果:Hello Consumer
}

(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:

public void test02(){
    Consumer<String> consumer=x-> System.out.println(x);
    consumer.accept("Hello Consumer");//结果:Hello Consumer
}

(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。

public void test04(){
    Comparator<Integer> com=(x, y)->{
        System.out.println("函数式接口");
        return Integer.compare(x,y);
    };
 	System.out.println(com.compare(2,4));//结果:-1
}

(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写

public void test05(){
    Comparator<Integer> com=(x, y)-> Integer.compare(x,y);
    System.out.println(com.compare(4,2));//结果:1
}

(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”

public void test06(){
    Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y);
    System.out.println(com.compare(4,2));//结果:1
}

总结:

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

二、线程状态

1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

  • (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
  • (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  • (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

控制线程的方法

  1. setPriority(int):设置线程的优先级别,可选范围1-10,默认为5,优先级高代表抢到时间片的概率高
  2. static sleep(long):让当前的线程休眠指定毫秒数
  3. static yield():让当前线程直接放弃时间片返回就绪[很可能自投自抢的情况,比较浪费CPU资源,建议少用]
  4. join():让当前线程邀请调用方法的那个线程优先执行,在被邀请的线程执行结束之前当前线程一直处于阻塞状态,不再继续执行
  5. static activeCount():得到程序中所有活跃的线程:就绪+运行+阻塞

停止线程

  • 不推荐使用JDK提供的 stop() 、destory() 方法[已废弃]
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量,当 flag == false ,则终止线程运行
public class TestStop implements Runnable{
    //1、线程中定义线程体使用的标志
    private boolean flag = true;
    
    @Override
    public void run(){
        //2、线程体使用该标志
        while(flag){
            System.out.printf("run...Thread");
        }
    }
    
    //3、对外提供方法改变标志
    public void stop(){
        this.flag = false;
    }
}

线程休眠

sleep可以模拟网络延时,倒计时,放大问题的发生性等。

阻塞状态的线程 如何解除阻塞?

  • sleep() : 睡眠的时间超时了,就自动解除
  • join() : 被邀请的线程执行结束了,就自动解除
  • await() : 门闩都被拔掉的时候,就解除阻塞
  • wait() : 被其它线程notify()/notifyAll(),就解除阻塞
  • interrupt() 能够在这些条件都没满足的情况下,直接唤醒

优先级取值范围

Java 线程优先级使用 1 ~ 10 的整数表示:

  • 最低优先级 1:Thread.MIN_PRIORITY
  • 最高优先级 10:Thread.MAX_PRIORITY
  • 普通优先级 5:Thread.NORM_PRIORITY
  • 注意:
    • 优先级的设定建议在 start() 调度之前
    • 优先级低只是意味着获得调度的概率低,并不是优先级低就i不会被调度了,这都是看CPU的调度

守护(damon)线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 守护线程一般用于记录操作日志,监控内存,垃圾回收等待

实现线程同步的方式

通过synchronized关键字和lock锁两种方法来实现线程同步

同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是对象本身,或者是class

public void test(){
    //同步块,其中Obj为同步监视器,Obj可以时任何对象,但是推荐使用共享资源作为同步监视器
    //synchronized 默认锁的是 this。锁的对象应该是变化的量,需要增删改
    synchronized(obj){
        System.out.println("===");
    }
}

死锁:多个线程互相抱着对方需要的资源,然后形成僵持

java 死锁产生的四个必要条件:

  • 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

解决死锁问题的方法是:一种是用synchronized,一种是用Lock显式锁实现。

synchronized和Lock对比:

  • Lock是显式的(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,处理作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock > 同步代码块(已经进入方法体,分配相应的资源)> 同步方法(在方法体之外)

使用ReentrantLock(可重入锁)实现同步

  • lock()方法:上锁

  • unlock()方法:释放锁

  • trylock():synchronized 是不占用到手不罢休的,会一直试图占用下去。与 synchronized 的钻牛角尖不一样,Lock接口还提供了一个trylock方法。

  • public class test{
        //定义一个
        private final ReentrantLock lock = new ReentrantLock();
        public void m(){
            //上锁
            lock.lock();
            try{
                //保证线程安全的代码
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //如果同步代码有异常,要将 unlock() 写入finially语句块中
                lock.unlock();
            }
        }
    }
    

使用Condition实现等待/通知

  • 使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法

  • Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法

三、线程间的通信方式

Object类中相关的方法有notify方法和wait方法。因为wait和notify方法定义在Object类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。

1.wait()

  • wait()方法:让当前线程进入等待,并释放锁。
  • wait(long)方法:让当前线程进入等待,并释放锁,不过等待时间为long,超过这个时间没有对当前线程进行唤醒,将自动唤醒

2.notify()

  • notify()方法:让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,并从其他线程中唤醒其中一个继续执行。
  • notifyAll()方法:让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,将唤醒所有等待状态的线程。

3.wait()与sleep()比较

  • 当线程调用了wait()方法时,它会释放掉对象的锁。
  • Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

线程池

java通过Executors提供线程池

创建线程池,在构造一个新的线程池时,必须满足下面的条件:

  • corePoolSize(线程池基本大小)必须大于或等于0
  • maximumPoolSize(线程池最大大小)必须大于或等于1
  • maximumPoolSize必须大于或等于corePoolSize
  • keepAliveTime(线程存活保持时间)必须大于或等于0
  • workQueue(任务队列)不能为空
  • threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类
  • handler(线程饱和策略)不能为空,默认策略为ThreadPoolExecutor.AbortPolicy

标签:Java,Thread,void,System,线程,多线程,public,out
From: https://www.cnblogs.com/daimadaxia/p/17975766

相关文章

  • 《Java并发实现原理:JDK源码剖析》PDF
    《Java并发实现原理:JDK源码剖析》全面而系统地剖析了JavaConcurrent包中的每一个部分,对并发的实现原理进行了深刻的探讨。全书分为8章,第1章从最基础的多线程知识讲起,理清多线程中容易误解的知识点,探究背后的原理,包括内存重排序、happen-before、内存屏障等;第2~8章,从简单到复杂,逐......
  • Java学习日记 Day5 今天开始十点准时下班,身体是革命的本钱。。
    JavaSE:今天终于把集合结束了,这周尽量看完IO、多线程和网络编程吧。①Map的常用方法:基本还是增删改查的那些东西,挑重要的讲了。一个是keySet(),能过获取map中所有的key值,values()方法能够获取map中所有的数据值。但其实获取了key之后通过get(key)遍历也能获得属性值。②HashMap、Ta......
  • 细说JavaScript对象(JavaScript对象详解)
    在JavaScript中对象作为数据类型之一,它的数据结构区别于其余5中数据类型,从数据结构角度看对象就是数据值的几个,其书就结构就是若干组名值对,类似于其他语言中的哈希、散列关联数组等,但对象在JavaScript中不仅仅扮演着数据类型的角色,同时也是JavaScript语言的实现基础,可通过内置对......
  • 细说JavaScript函数(JavaScript函数详解)
    函数的作用就是封装一段JavaScript代码,让开发者可以通古简单的方式使用这段代码![细说JavaScript函数(JavaScript函数详解)](https://img-blog.csdnimg.cn/direct/9f5c340fdb0d4540a3bcb8e5e251e96b.png)一、函数的分类在几乎所有的编程语言中,都有函数这一概念,并且没中语言本身......
  • 细说JavaScript内置对象(JavaScript内置对象详解)
    ![细说JavaScript内置对象(JavaScript内置对象详解)](https://img-blog.csdnimg.cn/direct/69e530474ccf4835b58ecf810db1f348.png#pic_center)一、String对象任何一门语言都会有关于js字符串的介绍,一连串的字符组成一串,就构成了字符串。字符串的处理不存在生活中还是在计算机应......
  • 细说JavaScript数组(JavaScript数组详解)
    ![细说JavaScript数组(JavaScript详解)](https://img-blog.csdnimg.cn/direct/1f9f0b9905754c918bfc33f9d7565825.png#pic_center)一、理解数组数组不是一种独立的数据类型,它由对象发展而来,它可以使用对象的诸多方法,但又区别于对象。数组通常用来存储列表等信息,它就像一张电子表......
  • 细说JavaScript语句详解(JavaScript语句详解)
    ![细说JavaScript语句详解](https://img-blog.csdnimg.cn/direct/19cd6f6cc963422f983afbd941772185.png)在js中有多种不同的语句,如表达式语句、声明语句、条件语句、循环语句、跳出语句等一、顺序结构在生活中约定俗成的顺序规则就是自前往后、自上而下、自左至右,在js中与......
  • JavaScript保留字和预定义的全局变量及函数汇总
    保留字也称关键字,每种语言中都有该语言本身规定的一些关键字,这些关键字都是该语言的语法实现基础,JavaScript中规定了一些标识符作为现行版本的关键字或者将来版本中可能会用到的关键字,所以当我们定义标识符时就不能使用这些关键字了,下面介绍下JavaScript保留字和预定义的全局变量......
  • JavaScript常用事件详解
    一、用于form(表单)的事件在网页中经常会遇到一些表单的验证,是通过事件进行处理的,比如用户输入用户名之后,及时显示用户是否被注册用于form(表单)的事件事件名功能onblur|当元素失去焦点时运行onchange|当元素值被改变时运行onfocus|当元素获取焦点时运行onselect......
  • 细说JavaScript表达式和运算符号(JavaScript表达式和运算符号详解)
    除了简单的表达式还有复杂的表达式,它是由简单表达式构成的,将简单表达式组合成复杂表达式最常见的方法就是使用运算符![细说JavaScript表达式和运算符号详解](https://img-blog.csdnimg.cn/direct/2781400b25be4b38bbf99d0c1b93d169.png)一、表达式表达式分为简单表达式和复杂......