zoukankan      html  css  js  c++  java
  • 多线程

    进程跟线程的区别:

    进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个进程.

    多进程:操作系统中同时运行多个程序.一个程序至少有一个进程

    线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比较小,互相之间可以影响的,又称之为轻型进程或进程元.

    多线程:在同一个进程中运行的多个任务.一个进程至少有一个线程

    Java操作进行

    在java代码中如何去运行一个进程(简单讲解,获取进程中的数(IO))

    方式一:Runtime类exec方法.

    方式二:ProcessBuilder start方法 推荐使用

    创建并启动线程

     

    子线程一旦创建跟主线程有相同的级别.

    线程类thread类和Runnable类的子类才能称之为线程类.

    主线程(main方法运行,表示主线程)

    方式一:继承Thread;

    1):定义一个类,让类继承于java.lang.Thread类

    2):在类中覆盖Thread中的run方法

    3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)

    4):在main方法中,创建线程对象,并启动线程.

    1. 创建线程的格式: 线程类 a =  new 线程类()
    2. 调用线程对象的start方法 a.start;启动一个线程

    注意:千万不要调用run方法.如果调用run方法,依然还是一个线程,并没有开启新的线程.

    方式二:实现Runnable接口;

    1):定义一个类,让类继承于java.lang.runnable接口

    2):在类中覆盖runnable接口中的run方法

    3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)

    4):在main方法中,创建线程对象,并启动线程.

    public Thread(Runnable target)分配新的 Thread 对象.

    1. 创建线程类对象   

    Thread  t  =  new  Thread(new 继承于runnable接口的对象)

    1. 调用线程对象的start方法  t.start();

    使用匿名内部类来创建此线程

    第一种:使用接口创建匿名内部类对象来创建线程(常用)

    第二种:使用类来创建匿名内部类对象来创建线程

    案例分析

    public class AppleExtendsDome extends Thread {
    public AppleExtendsDome(String name){
    super(name);
    }
    private int apple = 50;
    public void run() {
    for(int i = 0; i <50;i++){
    if(apple>0)
    System.out.println(getName()+"吃了第" + apple-- + "个苹果");
    }

    }
    public static void main(String[] args) {
    new AppleExtendsDome("小杨").start();
    new AppleExtendsDome("小张").start();
    new AppleExtendsDome("小王").start();

    }
    }

    -------------------------------------------------------------------------------

    //五个人同时吃50个苹果
    public class AppleImplementsDome implements Runnable{
    private int apple = 50;
    public void run() {
    for(int i = 0; i <50;i++){
    if(apple>0){
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"吃了第" + apple-- + "个苹果");
    }
    }

    }
    public static void main(String[] args) {
    AppleImplementsDome a = new AppleImplementsDome();
    new Thread(a,"杨贵武").start();
    new Thread(a,"范晓").start();
    new Thread(a,"周雨晨").start();

    }

    }

    分析继承方式和实现方式的区别

    继承方式:

     

    1):java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了

    2):从操作上分析,继承方式更简单,获取线程名字也简单.getName()

    3):从多线程贡共享同一个资源上分析,继承方式不能做到

     

    实现方式:

     

    1):java中可以实现多接口,此时该类还可以继承其他类,并且还可以实现其他接口.

    2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread().getName()来获取当前线程的引用.

    3):从多线程共享同一个资源上分析,实现方式可以做到

     

    两种实现方式的区别和联系:

    在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:

    1):避免点继承的局限,一个类可以继承多个接口。

    2):适合于资源的共享

    通过三个同学吃抢苹果的例子,说明使用实现方式才合理

     

    线程不安全的问题分析

    当多线程并发访问同一个对象的时候,可能出现线程不安全的问题.

     

    解决方案:保证编号跟总数必须同步完成

       A线程进入操作的时候B,C只能在外面等着,A操作结束,B和C才有机会金融入代码区执行

    --------------------------------------------------

    线程同步(重要)

    方式一:同步代码块:

    /**
    * 同步代码块
    * @author Mr.bai
    *
    */
    class AppleImplementsDome implements Runnable {

    private int apple = 50;

    public void run() {
    for (int i = 0; i < 50; i++) {
    synchronized (this) {
    if (apple > 0) {

    System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
    //模拟网络延迟
    try {
    Thread.sleep(100);//sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    apple--;
    }
    }
    }

    }
    }
    //
    public class SynchronizedDome {

    public static void main(String[] args) {
    AppleImplementsDome a = new AppleImplementsDome();
    new Thread(a, "杨贵武").start();
    new Thread(a, "范萧").start();
    new Thread(a, "周雨晨").start();

    }

    }

    语法:synchronized (同步锁){

    //需要同步操作的代码块

       }

    同步锁:此处存放引用数据类型/对象/class文件均可

    为了保证每一线程都能正常执行原子操作,java引入了线程同步机制

    同步监听对象/同步锁/同步监听器/互斥锁

    对象的同步锁只是一个概念,可以想象成对象上标记一个锁。

    Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。

    注意:在任何时候,最多允许一个线程拥有同步锁。

    方式二:同步方法:使用 synchronized 修饰的方法,叫做同步方法,保证线程执行该方法的时候,其他线程只能在方法外等候

    Synchronized public void play(){
             //TODO
             }      

    同步锁是谁呢?

    对于非static方法同步锁就是this;

    对于static方法,我们使用当前方法所在类的字节码对象(xxx.class);

    不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行

    解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,在用run方法调用该新的方法即可.

    问题:假设现在有一筐苹果,数量为50个,现在分给三个人吃,直到吃完为止.

    分析:

    1):苹果的数量是固定的,逐步递减.所以只能有一个线程对象.

    2):分给三个人吃,所以必须创建三个线程.

    3):一个人吃完了才能下一个人吃,所以需要保证线程同步.

    此时考虑该构造方法:public Thread(Runnable group, String name)分配新的 Thread 对象。
    public static Thread currentThread()返回对当前正在执行的线程对象的引用。
    public final String getName()返回该线程的名称。

    /**
    * 同步代码块
    * @author Mr.bai
    *
    */
    class AppleImplementsDome implements Runnable {

    private int apple = 50;

    public void run() {
    for (int i = 0; i < 50; i++) {
    synchronized (this) {
    if (apple > 0) {

    System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
    //模拟网络延迟
    try {
    Thread.sleep(100);//sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    apple--;
    }
    }
    }

    }
    }
    //
    public class SynchronizedDome {

    public static void main(String[] args) {
    AppleImplementsDome a = new AppleImplementsDome();
    new Thread(a, "杨贵武").start();
    new Thread(a, "范萧").start();
    new Thread(a, "周雨晨").start();

    }

    }

































    方式三:锁机制(Lock):

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。同步方法和语句具有的功能Lock都有,除此之外更能体现面向对象. 此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

    详情见:

    /**
    * 同步锁(Lock)
    * @author Mr.bai
    *
    */
    class AppleImplementsDome1 implements Runnable {

    private int apple = 500;

    public void run() {
    for (int i = 0; i < 500; i++) {

    eat();
    }
    }
    //创建一个锁对象
    Lock lock = new ReentrantLock();

    public void eat() {
    //进入方法,开启锁机制
    lock.lock();
    try {

    if (apple > 0) {

    System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
    Thread.sleep(10);//睡眠10毫秒
    apple--;
    }

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

    } finally {
    //释放锁
    lock.unlock();

    }
    }
    }

    //同步锁 (Lock)
    public class LockDome {
    public static void main(String[] args) {
    AppleImplementsDome1 a = new AppleImplementsDome1();
    new Thread(a, "杨贵武").start();
    new Thread(a, "范萧").start();
    new Thread(a, "周雨晨").start();
    }
    }




































    同步锁池:

    /**
    * 消费者
    * @author Mr.bai
    *
    */
    public class Consume implements Runnable {
    private Intermediary up;

    public Consume(Intermediary a) {
    this.up = a;
    }

    public void run() {
    for (int i = 0; i < 50; i++) {
    if (i % 2 == 1) {
    up.fetch();
    } else {
    up.fetch();
    }

    }
    }

    }

    -------------------------------------------------------

    /**
    * 中间商
    * @author Mr.bai
    *
    */
    public class Intermediary {
    private String name;
    private String sex;
    private boolean isEmpty = true;//表示共享资源是否为空的状态

    /**
    * 接收生产者输出的产品
    * @param name 录入的名字
    * @param sex 录入的性别
    */
    synchronized public void entering(String name, String sex) {
    try {
    while(!isEmpty){//当前isEmpty为false的时候,不空等消费者来获取
    //使用同步锁对象调用,表示当线程释放同步锁,进入等待池,只能被其他线程所唤醒
    this.wait();
    }
    //生产开始........
    this.name = name;
    Thread.sleep(10);
    this.sex = sex;
    //生产结束........
    isEmpty = false;//设置共享数据资源不能为空
    this.notify();//唤醒一个消费者
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    /**
    * 消费者提取数据
    */
    synchronized public void fetch() {

    try {
    while(isEmpty){ //当前isEmpty为false的时候,等生产者来生产
    //使用同步锁对象调用,表示当线程释放同步锁,进入等待池,只能被其他线程所唤醒
    this.wait();
    }
    System.out.println(name + "--" + sex);
    this.notify();//唤醒一个生产者
    isEmpty = true;
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

    -----------------------------------------------------------------------------------------------

    /**
    * 生产者
    * @author Mr.bai
    *
    */
    public class Producer implements Runnable {
    private Intermediary up;

    public Producer(Intermediary a) {
    this.up = a;
    }

    public void run() {

    for (int i = 0; i < 50; i++) {
    if (i % 2 == 1) {
    up.entering("小赵", "男" );
    } else {
    up.entering("小杨", "女" );
    }
    }
    }

    }

    -----------------------------------------------------------------------

    public class Test {
    public static void main(String[] args) {
    Intermediary up = new Intermediary();
    //生产者生产

    new Thread(new Producer(up)).start();
    //消费者消费
    new Thread(new Consume(up)).start();
    }

    }





























































































    问题一:出现性别紊乱的情况

    使用同步锁方式解决(参照线程同步三种方式)

    同步锁必须选择多个线程共同的资源对象

    当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁匙

    当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢夺使用权.

    问题二:应该生产一个数据消费一个数据

    解决方案:使用等待和唤醒机制

     

    线程通信wait和notify方法介绍

    Object.long里面的方法

    void wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法.

    void notify() 唤醒在此对象监视器上等待的单个线程

    Void notifyAll() 唤醒在此对象监视器上等待的所有线程

    注意:上述方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException

    多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果

    我们把这个用来做互斥的对象称之为,同步监听对象/同步锁

    ------------------------------------

    同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可.

    ------------------------------------

    因为,只有同步监听锁对象才能调用wait和notify方法,所以wait和notify方法应该存在于Object类中,而不是Thread类中

     

    案例分析:银行存取钱系统

    银行管理系统类

    存钱类(跟取钱类综合参照说明)

    取钱类

    测试类代码:

    线程通信:使用LockCondition接口

    Waitnotify方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException

    Lock机制根本没有同步锁,也就没有自动获取锁和自动释放锁的概念(更加体现面向对象)

    使用格式:

    1):创建lock对象:           Lock lock = new ReentrantLock();

    2):创建condition对象:  Condition condition = Lock.new condition();

    3):需要同步执行的方法体

    案例

    线程的生命周期

    有人又把阻塞状态, 等待状态, 计时等待状态综合称之为阻塞状态

     

    ---------------------------------------------------------

    线程对象的状态存放在Thread类的内部类state中:

    注意:Thread.state类其实是一个枚举类.

     线程对象的状态是固定的,只有6种,此时使用枚举来表示最恰当的.

    在给定的时间点上,一个线程只能处于一种状态.这些状态是虚拟机状态,他们并没有反映所有操作系统线程状态

    1):新建状态使用new创建一个线程对象,仅仅在对重分配内存空间. 在调用start方法 之前

    新建状态下,线程根本没有启动,仅仅是存在一个线程对象而已.

    New Thread();就属于新建状态

     

    2):可运行状态(runnale):分成两种状态,ready和running.分别表示就绪状态和运行状态

    1. i.         就绪状态:线程对象,调用start方法之后,等待JVM的调用
    2. ii.        运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并运行.

    线程对象的start方法,只能调用一次,否则报错.

    3):堵塞(blocked由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

    此时JVM不会给线程分配CPU,知道线程重新进入就绪状态,才有机会转到运行状态.

    阻塞状态只能先进入就绪状态,不能慧姐进入运行状态

    正在睡眠:用sleep(long t) /wait(带参)方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

    正在等待:调用wait()方法。(调用notify()方法回到就绪状态)

     

    被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

    4)死亡(dead

    当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

    自然终止:正常运行run()方法后终止

    异常终止:调用stop()方法让一个线程终止运行

    线程控制操作

    1):线程睡眠: 让执行的线程进入等待状态,睡眠一段时间.

    方法:public static void sleep(long millis)  throws InterruptedException

    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行).调用sleep方法后,当前线程放弃CPU,在指定时间段类,sleep所在线程不会获得执行的机会.

    该状态下的线程不会释放同步锁. 

    2)联合线程:线程的join方法表示一个线程等待另一个线程完成后才执行,join方法被调用之后,线程对象处于阻塞状态.

     

    4):后台线程:在后台运行的线程,其目的是为了其他线程提供服务,也称之为”守护线程”,JVM的垃圾会竖起就是一个典型的后台线程.

    如果主线程结束了,那么后台线程也跟着结束了

    方法:void setDaemon(boolean on)

    将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

    参数:

    on - 如果为 true,则将该线程标记为守护线程。

    线程优先级:

    每个线程都有优先级,优先级高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度.

    MAX_PRIORITY = 10       最高优先级

    MIN_PRIORITY = 1          最低优先级

    NORM_PRIORITY = 5      默认优先级

    -----------------------------------------------

    int getPriority();  返回线程的优先级。 
    void setPriority(int newPriority); 更改线程的优先级。
     
    public static Thread currentThread();返回对当前正在执行的线程对象的引用。 
    注意:不同的操作系统支持的线程优先级不同的,建议使用上述三个优先级,不要自定义.
     
     
    线程礼让(一般不用)
    public static void yield()

    对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。

    产量是一种启发式尝试,以改善否则会过度利用CPU的线程之间的相对进度。 其使用应与详细的分析和基准相结合,以确保其具有预期的效果。

    很少使用这种方法。 它可能对调试或测试有用,可能有助于根据种族条件重现错误。 在设计并发控制结构(例如java.util.concurrent.locks包中的并行控制结构)时也可能有用。

    线程组:
     Thread(ThreadGroup group,Runnable target)分配新的 Thread 对象。这种构造方法与 Thread(group, target, gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 "Thread-"+n ,其中的 n 为整数。

    类 ThreadGroup线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。



  • 相关阅读:
    143、Java内部类之访问方法中定义的参数或变量
    142、Java内部类之在普通方法里面定义内部类
    141、Java内部类之实例化外部类对象
    140、Java内部类之实例化内部类对象
    139、Java内部类之使用this访问外部类属性
    138、Java内部类之访问内部类的私有属性
    137、Java内部类之把内部类放到外部
    136、Java的内部类
    135、Java中的静态块,构造方法和构造块
    134、Java中的构造方法和构造块
  • 原文地址:https://www.cnblogs.com/it-xiaoBai/p/8081482.html
Copyright © 2011-2022 走看看