多线程小结

程序

  • 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程

  • 是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。 ——生命周期
  • 进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域

线程

  • 进程可进一步细化为线程,是一个程序内部的一条执行路径。
  • 若一个进程同一时间并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

单核CPU和多核CPU的理解

  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时
    间单元特别短,因此感觉不出来。
  • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
  • 一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc()
    垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

  • 并行: 多个CPU同时执行多个任务。
  • 并发: 一个CPU(采用时间片)同时执行多个任务。

Thread类的有关方法

  • void start(): 启动线程,并执行对象的run()方法
  • run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield(): 线程让步
    暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    若队列中没有同优先级的线程,忽略此方法
  • join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
    低优先级的线程也可以获得执行
  • static void sleep(long millis): (指定时间:毫秒)
    令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
    抛出InterruptedException异常
  • stop(): 强制线程生命期结束,不推荐使用
  • boolean isAlive(): 返回boolean,判断线程是否还活着

线程的调度

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略
  • 抢占式: 高优先级的线程抢占CPU

线程的优先级

  • 线程的优先级等级
    MAX_PRIORITY: 10
    MIN _PRIORITY: 1
    NORM_PRIORITY: 5
  • 涉及的方法
    getPriority() : 返回线程优先值
    setPriority(int newPriority) : 改变线程的优先级
  • 说明
    线程创建时继承父线程的优先级
    低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

线程的分类

  • Java中的线程分为两类:一种是守护线程,一种是用户线程。

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

  • Java垃圾回收就是一个典型的守护线程。

  • 若JVM中都是守护线程,当前JVM将退出

线程的生命周期

  • JDK中用Thread.State类定义了线程的几种状态
  • 要想实现多线程, 必须在主线程中创建新的线程对象。 Java语言使用Thread类及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历如下的五
    种状态:
    新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
    阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
    死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

在这里插入图片描述

线程的创建方式:4种

  • 方式1:继承Thread类
package com.itany.thread;

/**
 * Author: zhh
 * Date: 2022-10-05 22:48
 * Description: 创建多线程1
 */
public class Test01 {

    public static void main(String[] args) {
        MyThread myThread   = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread().getName() +"hello world!");

        MyThread myThread2   = new MyThread();
        myThread2.start();
    }


}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <100;i++) {

            System.out.println(Thread.currentThread().getName()+"  "+i);
        }
    }
}

  • 方式2:实现Runnable接口
package com.itany.thread;

import  java.lang.Thread;


/**
 * Author: zhh
 * Date: 2022-10-06 0:02
 * Description: 
 */
public class Test02 {

    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);

        Thread t2 = new Thread(mThread);
        Thread t3 = new Thread(mThread);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}
  class MThread implements Runnable {

    private int ticket = 100;
    private final Object lock = new Object();//锁要是同一个才行

    @Override
    public void run() {

        while (true) {

            synchronized (MThread.class) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "窗口卖的票为:" + ticket + "号");
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

  • 方式3:实现Callable接口
package com.itany.thread;

import netscape.security.UserTarget;

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

/**
 * Author: zhh
 * Date: 2022-10-06 16:23
 * Description: 实现Callable接口方式实现多线程
 */
public class Test08 {
    public static void main(String[] args) {
        ThreadX1 th=new ThreadX1();
        FutureTask futureTask=new FutureTask(th);
        new Thread(futureTask).start();

        try {
            //获取call方法中的返回值,没需要就不需要这一步
            Object sum=futureTask.get();
            System.out.println("结果是:"+sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadX1 implements Callable{

    @Override
    public Object call() throws Exception {
        Integer num=0;
        for(int i=0;i<100;i++) {
            System.out.println(i);
            if(i%2==0){
                num+=i;
            }
        }
        return num;
    }
}

  • 方式4:通过线程池来创建
package com.itany.thread;

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

/**
 * Author: zhh
 * Date: 2022-10-06 16:51
 * Description: 通过线程池创建多线程
 */
public class Test09 {
    public static void main(String[] args) {
        //1、提供指定线程数量的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        //设置线程池的属性
        ThreadPoolExecutor service=(ThreadPoolExecutor)executor;
        service.setCorePoolSize(15);
        service.setMaximumPoolSize(20);


        //2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的类对象
        executor.execute(new NumberThread());//适用于Runnable
        executor.execute(new NumberThread());//适用于Runnable

//        executor.submit(new ThreadX1());//适用于Callable
        //3、关闭连接池
        executor.shutdown();

    }
}

class NumberThread implements Runnable {

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

前两种方式注意点:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
  3. 想要启动多线程,必须调用start方法。
  4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException” 。

前两种方式对比

  • 开发中:优先选择实现Runnable接口的方式
  • 原因:
    1、实现的方式没有单继承的局限性
    2、实现的方式更适合来处理多个线程共享数据的情况
  • 联系:public class Thread implements Runnable
  • 相同点:
    1、两种方式都需要重写run方法,将线程执行的逻辑代码放入其中。
    2、想要启动线程都是通过调用Thread类中的start方法

如何理解实现多线程时通过实现Callable接口的方式比实现Runnable接口的方式更强大

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

使用线程池的好处:

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

线程的同步

  • 多个线程执行的不确定性引起执行结果的不稳定
  • 问题的原因:
    当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
  • 解决办法:
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行

对于多线程安全问题的三种解决方式:同步机制

方式1:同步代码块

synchronized (对象){
// 需要被同步的代码;
}
package com.itany.thread;

import  java.lang.Thread;


/**
 * Author: zhh
 * Date: 2022-10-06 0:02
 * Description: 同步代码块
 */
public class Test02 {

    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);

        Thread t2 = new Thread(mThread);
        Thread t3 = new Thread(mThread);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}
  class MThread implements Runnable {

    private int ticket = 100;
    private final Object lock = new Object();//锁要是同一个才行

    @Override
    public void run() {

        while (true) {
 //           synchronized (lock) {
  //          synchronized (this) {
            synchronized (MThread.class) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "窗口卖的票为:" + ticket + "号");
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

方式2:同步方法

//synchronized还可以放在方法声明中,表示整个方法为同步方法。
//例如:
public synchronized void show (String name){
//….
}
package com.itany.thread;

/**
 * Author: zhh
 * Date: 2022-10-06 14:03
 * Description: 同步方法
 */
public class Test03 {
    public static void main(String[] args) {
        MyThreadX t1=new MyThreadX();
        MyThreadX t2=new MyThreadX();
        MyThreadX t3=new MyThreadX();
        t1.setName("1");
        t2.setName("2");
        t3.setName("3");
        t1.start();
        t2.start();
        t3.start();

    }

}

class MyThreadX extends Thread {
    private static int ticket=100;

    @Override
    public void run() {
       while (true) {
            show();
       }
    }

    public synchronized static void show()  {//加static,锁对象变为当前类对象,是唯一的
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println( Thread.currentThread().getName()+"窗口的票号是:"+ticket);
            ticket--;
        }
    }
}

同步方法小结

  • 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
  • 静态方法(类名.class) 、 非静态方法(this)
  • 非静态的同步方法,同步监视器是:this
  • 静态的同步方法,同步监视器是:当前类本身

方式3:lock锁

  • 从JDK 5.0开始, Java提供了更强大的线程同步机制——通过显式定义同
    步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义, 在实现线程安全的控制中,比较常用的是ReentrantLock, 可以显式加锁、释放锁。
package com.itany.thread;

import  java.lang.Thread;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Author: zhh
 * Date: 2022-10-06 0:02
 * Description: lock锁
 */
public class Test06 {

    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);

        Thread t2 = new Thread(mThread);
        Thread t3 = new Thread(mThread);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}

class MThread1 implements Runnable {

  private int ticket = 100;
  private final ReentrantLock lock = new ReentrantLock(true);

  @Override
  public void run() {

      while (true) {
//
          try{
              lock.lock();

              if (ticket > 0) {
                  System.out.println(Thread.currentThread().getName() + "窗口卖的票为:" + ticket + "号");
                  ticket--;
              } else {
                  break;
              }
          }finally {
              lock.unlock();
          }

      }

  }
}

优先使用顺序:

  • Lock -->同步代码块(已经进入了方法体,分配了相应资源) -->同步方法 (在方法体之外)

synchronized和Lock锁的异同

  • 同:两者都可以解决线程的安全问题
  • 异:
    1、 synchronized机制在执行完相同的同步代码以后,自动的释放同步监视器
    2、Lock锁需要手动启动同步(Lock()),同时结束同步也需要手动实现(unLock())
    3、Lock只有代码块锁, synchronized有代码块锁和方法锁
    4、使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

注意:

  • 操作共享数据的代码,即为需要被同步的代码
  • 共享数据:多个线程共同操作的变量
  • 同步监视器:俗称锁。任何一个类的对象都可以作为锁
    注意:多个线程必须共同使用同一把锁

好处和缺点

  • 好处:同步的方法,解决了线程的安全问题
  • 缺点:操作同步代码时,只能有一个线程参与,其它线程等待,相当于一个单线程,效率低

怎样判断代码是否存在线程安全

  • 明确哪些代码是多线程运行的代码
  • 明确多线程是否有共享数据
  • 明确多线程运行中是否有多条语句操作共享数据
  • 切记:范围不能过大也不能过小
    范围太小:没锁住所有有安全问题的代码
    范围太大:没发挥多线程的功能

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。

  • 当前线程在同步代码块、同步方法中遇到break、 return终止了该代码块、 该方法的继续执行。

  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束。

  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。

不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行

  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。
    应尽量避免使用suspend()和resume()来控制线程

死锁问题

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则

  • 尽量减少同步资源的定义

  • 尽量避免嵌套同步

package com.itany.thread;

/**
 * Author: zhh
 * Date: 2022-10-06 14:32
 * Description: 死锁
 */
public class Test05 {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("b");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("1");
                        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("d");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("3");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();

    }
}

线程的通信

实现两个窗口交替卖票

package com.itany.thread;

/**
 * Author: zhh
 * Date: 2022-10-06 15:38
 * Description: 线程通信
 */
public class Test07 {

    public static void main(String[] args) {
        MThreadXX mThread = new MThreadXX();
        Thread t1 = new Thread(mThread);

        Thread t2 = new Thread(mThread);

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


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


    }
}
class MThreadXX implements Runnable {

    private int ticket = 100;
    private final Object lock = new Object();//锁要是同一个才行

    @Override
    public void run() {

        while (true) {
            synchronized (lock) {
                lock.notify();//调用者必须是同步代码块或同步方法中的同步监视器,否则会报错
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "窗口卖的票为:" + ticket + "号");
                    ticket--;
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

Object类中的与线程有关的三个方法:

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级最高的那个
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
  • 注意:
    这三个方法必须使用在同步代码块或者同步方法中
    这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常,如下图所示
    三个方法都是定义在java.lang.Object类中的
    在这里插入图片描述
    sleep()和wait()的异同
  • 同:一旦执行方法,都可以使当前线程进入阻塞状态
  • 异:
    1、两个方法声明的位置不一样:Thread类中声明sleep(),Object类中声明wait()
    2、调用的要求不同:sleep()可以在任何需要的情况下调用,而wait()只能在同步代码块或同步方法中调用
    3、关于是否释放 同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep不会释放锁,而wait会释放锁

解决单例设计模式中懒汉式的线程安全问题

package com.itany.thread;

/**
 * Author: zhh
 * Date: 2022-10-06 14:15
 * Description: 懒汉式单例模式
 */
public class Test04 {
}

class Bank{
    private Bank(){}
    private static Bank instance = null;

//    public synchronized static Bank getInstance() {
//        if(instance == null){
//            instance = new Bank();
//        }
//        return instance;
//    }
    public  static Bank getInstance() {
        //方式一:效率较差
//        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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

&*Savior

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值