Java的高级篇--多线程

1.什么是进程

  正在运行的程序,是系统进行资源分配的基本单位

2.什么是线程

线程,又称轻量级进程(Light Weight Process)。进程中的一条执行路径,也是CPU的基本调

度单位。 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

3.进程和线程的区别

  1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
  2. 一个程序运行后至少有一个进程。
  3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的。
  4. 进程间不能共享数据段地址,但是同进程的线程之间可以。

4.线程的组成

  • 任何一个线程都具有基本的组成部分
  • CPU时间片:操作系统(OS)会为每个线程分配执行时间
  • 运行数据:(堆空间、栈空间)
  • 堆空间:存储线程需要的对象,多个线程可以共享堆中的数据
  • 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈.
  • 线程的逻辑代码

5.线程的特点

5.1线程抢占式执行

  • 效率高
  • 可防止单一线程长时间独占CPU.

5.2在单核CPU中,宏观上同时执行,微观上属性执行

6.线程的创建方式

6.1【继承Thread类,重写run方法】

6.1.1创建线程

6.1.2获取和设置线程的名称

获取线程ID和线程名称

  1. 在Thread的子类中调用this.getId()或this.getName()
  2. 使用Thread.currentThread().getId和Thread.currentThread().getName()

 修改线程名称

  1. 调用线程对象的setName()方法
  2. 使用线程子类的构造方法赋值

案例:

public class Ticket extends Thread{

    @Override
    public void run(){
        for (int i=100;i>0;i--){
            System.out.println(Thread.currentThread().getName()+ "卖了一张票"+"剩余:"+i+"张");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        t1.setName("张三");
        Ticket t2 = new Ticket();
        t2.setName("李四");
        Ticket t3 = new Ticket();
        t3.setName("王五");
        Ticket t4 = new Ticket();
        t4.setName("赵六");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

6.2【实现Runnable接口】

6.2.1创建线程

 案例:

public class Ticket implements Runnable{
    private int ticket =100;
    @Override
    public void run() {
        while (true){
                if (ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余:"+ticket+"张票");
                }else {
                    break;
                }
            }

    }
}
public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"曹操");
        Thread t2 = new Thread(ticket,"刘备");
        Thread t3 = new Thread(ticket,"孙权");
        Thread t4 = new Thread(ticket,"司马懿");


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

package com.ykq.demo05;

/**
 * @program: qy151-java高级-多线程
 * @description:
 * @author: YSH
 * @create: 2022-07-17 12:22
 **/
public class TestBank {
    public static void main(String[] args) {
           BankCard bankCard=new BankCard(0);
           SaveMoney save=new SaveMoney(bankCard);
           TakeMoney take=new TakeMoney(bankCard);

           Thread t1=new Thread(save,"喜羊羊");
           Thread t2=new Thread(take,"美羊羊");
           t1.start();
           t2.start();
    }
}
//取钱得任务
class TakeMoney implements  Runnable{
    private BankCard card;
    public TakeMoney(BankCard card){
        this.card=card;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(card.getBalance()>=1000) {
                card.setBalance(card.getBalance() - 1000);
                System.out.println(Thread.currentThread().getName() + "从卡中取出1000,卡中余额为:" + card.getBalance());
            }else{
                System.out.println("赶紧存钱,卡中没钱了.");
                i--;
            }
        }
    }
}
//存钱得任务
class SaveMoney implements  Runnable{
    private BankCard card;
    public SaveMoney(BankCard card){
         this.card=card;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            card.setBalance(card.getBalance()+1000);
            System.out.println(Thread.currentThread().getName()+"往卡中存入1000,卡中余额为:"+card.getBalance());
        }
    }
}

//抽取类:---银行卡类
class BankCard {
    private double balance;

    public BankCard(double balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

6.3【实现Callable接口】

package com.qy151.demo04;

import java.util.concurrent.*;

/**
 * @unthor : YSH
 * @date : 21:16 2022/7/18
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        My task = new My();
      /*  FutureTask futureTask = new FutureTask(task);
        Thread t1 = new Thread(futureTask);
        t1.start();
        System.out.println(futureTask.get());
        //自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个FutureTask类种, 建议使用线程池来提交任务*/


        My2 task2 = new My2();

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<Integer> future = executorService.submit(task);
        Future<Integer> future2 = executorService.submit(task2);


        Integer sum = future.get();//需要等线程执行完毕后,才会把结果返回给该变量
        Integer sum2 = future2.get();//需要等线程执行完毕后,才会把结果返回给该变量
        System.out.println(sum+sum2);

        //应用场景适合大文件上传。
    }
}

class My implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 1; i <=50; i++) {
            sum+=i;
        }


        return sum;
    }

}
class My2 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 51; i <= 100; i++) {
            sum += i;
        }


        return sum;
    }
}

7.线程的状态

8.线程的常见方法

8.1休眠

public static void sleep(long millis)

当前线程主动休眠millis毫秒

package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 13:03 2022/7/17
 */
public class TestSleep {
    public static void main(String[] args) {
        T t = new T();
        t.start();

        for (int i =0;i<20;i++){
            System.out.println("main没有休眠======="+i);
        }
    }
}
class T extends Thread{
    @Override
    public void run(){
        for (int i=0;i<20;i++){
            System.out.println("main没得休眠======="+i);
            try {
                Thread.sleep(100);//使当前线程休眠一段时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("休眠了================"+i);
        }
    }
}

8.2放弃

public static void yield()

当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 13:14 2022/7/17
 */
public class TestYield {
    public static void main(String[] args) {
        T2 t01 = new T2();
        T2 t02 = new T2();
        t01.setName("王五");
        t02.setName("赵六");
        t01.start();
        t02.start();
    }
}
class T2 extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            Thread.yield();//可能导致t1,t2交替的频率高了
            System.out.println(Thread.currentThread().getName()+"============"+i);
        }
    }
}

8.3加入

public final void join()

允许其它线程加入到当前线程中

package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 13:23 2022/7/17
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
       /* T3 t01 = new T3();
        t01.setName("张三");
        t01.start();

        t01.join();//把线程t01加入到main线程中,直到t01执行完毕后, main线程再执行
        for (int i = 0; i < 15; i++) {
            System.out.println("main============"+i);
        }*/

        //t1,t2,t3  如何保证这三个线程有序的执行--t1执行完毕后t2再执行--t2执行完毕后t3再执行
        T3  t01 = new T3();
        T3  t02 = new T3();
        T3  t03 = new T3();

        t01.setName("一一");
        t02.setName("二二");
        t03.setName("三三");

        t01.start();

        t01.join();
        t02.start();

        t02.join();
        t03.start();
    }
}
class T3 extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 15; i++) {
            System.out.println(Thread.currentThread().getName()+"============"+i);
        }
    }
}

8.4优先级

线程对象.setPriority()

线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高

8.5守护线程

线程对象.set.Daemon(true);设置为守护线程

线程有两类:用户线程(前台线程)和守护线程(后台线程)

如果程序中所有前台线程都执行完毕了,后台线程也会自动结束

垃圾回收线程属于守护线程。

package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 13:39 2022/7/17
 */
public class TsetDaemon {
    public static void main(String[] args) {
        T4 t4 = new T4();
        t4.setDaemon(true);//设置t4为守护线程,当前台线程都执行完毕后,守护线程也会自动结束
        t4.start();


        for (int i = 0; i < 20; i++) {
            System.out.println("main==============="+i);
        }
    }
}

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

9.线程的状态(等待)

10.线程安全问题

package com.qy151.demo03;

import java.util.Arrays;

/**
 * @unthor : YSH
 * @date : 13:55 2022/7/17
 */
public class TestSaft {
    private static String[] arr =new String[5];
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //匿名对象
        Runnable hello = new Runnable() {
            @Override
            public void run() {
                if (arr[index]==null){
                    arr[index]="hello";
                    index++;
                }
            }
        };
        Runnable world = new Runnable() {
            @Override
            public void run() {
                if (arr[index]==null){
                    arr[index]="world";
                    index++;
                }
            }
        };
        Thread t1 =new Thread(hello);
        Thread t2 =new Thread(world);
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(Arrays.toString(arr));
    }
}

多线程安全问题

如何保证线程的安全性?

package com.qy151.demo03;

import java.util.Arrays;

/**
 * @unthor : YSH
 * @date : 14:09 2022/7/17
 *
 * 加锁
 * 如何解决线程安全问题--加锁
 */
public class TestSaft02 {
    private static String[] arr =new String[5];
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //匿名对象
        Runnable hello = new Runnable() {
            @Override
            public void run() {
                synchronized (arr){
                if (arr[index]==null){
                    arr[index]="hello";
                    index++;
                    }
                }
            }
        };
        Runnable world = new Runnable() {
            @Override
            public void run() {
                synchronized (arr) {//临界资源或共享资源
                    if (arr[index] == null) {
                        arr[index] = "world";
                        index++;
                    }
                }
            }
        };
        Thread t1 =new Thread(hello);
        Thread t2 =new Thread(world);
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(Arrays.toString(arr));
    }
}

10.1同步方式

同步代码块

synchronized(临界资源对象){//对临界资源对象加锁

                //代码()原子操作

}

注意:

  • 每个对象都有一个互斥锁标记,用来分配给线程的
  • 只有拥有对象互斥锁标记的线程,才可以进入该对象加锁的同步代码块
  • 线程退出同步代码块时,会释放相应的互斥锁标记

10.2同步方法

synchronized 返回值类型 方法名称(形参列表0){ //当前对象(this)加锁

                        //代码(原子操作)

}

注意

  • 只有拥有对象互斥锁标记的线程,才可以进入该对象加锁的同步代码块
  • 线程退出同步代码块时,会释放相应的互斥锁标记

 10.3同步规则

注意:

    只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
    如果用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用

已知JDK中线程安全的类:

    StringBuffer
    Vector
    Hashtable
    以上类中的公开方法,均为synchonized修饰的同步方法

11.线程死锁

当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。

例子: 情人节---两个情人去餐厅吃饭---必须具有两个筷子。

男方拥有一根筷子,女方拥有另一个筷子。

package com.qy151.demo05;

/**
 * @unthor : YSH
 * @date : 14:38 2022/7/18
 */
public class LockObject {
    public static Object a = new Object();
    public static Object b = new Object();
}






package com.qy151.demo05;

/**
 * @unthor : YSH
 * @date : 14:38 2022/7/18
 */
public class Boy extends Thread{
    @Override
    public void run(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (LockObject.a){
            System.out.println(Thread.currentThread().getName()+"拥有一个筷子a");
            synchronized (LockObject.b){
                System.out.println(Thread.currentThread().getName()+"拥有一个筷子b");
                System.out.println("可以吃饭了");
            }
        }
    }
}





package com.qy151.demo05;

/**
 * @unthor : YSH
 * @date : 14:40 2022/7/18
 */
public class Girl extends Thread{
    @Override
    public void run(){
        synchronized (LockObject.b){
            System.out.println(Thread.currentThread().getName()+"拥有一个筷子b");
            synchronized (LockObject.a){
                System.out.println(Thread.currentThread().getName()+"拥有一个筷子a");
                System.out.println("可以吃饭了");
            }
        }
    }
}










package com.qy151.demo05;

/**
 * @unthor : YSH
 * @date : 14:41 2022/7/18
 */
public class Test {
    public static void main(String[] args) {
        Boy boy = new Boy();
        Girl girl = new Girl();

        boy.setName("邓超");
        girl.setName("孙俪");

        boy.start();
        girl.start();
    }
}

11.1造成死锁得原因:  

 锁与锁之间有嵌套导致。

11.2如何解决死锁:

1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。

11.3经典问题

死锁:

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

12.线程通信

12.1线程通信中使用得方法

例子: 存钱和取钱。

先存钱---再取钱-

package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 19:47 2022/7/18
 */
public class BankCard {
    private double balance;

    //true 表示有钱  false 表示没钱
    public boolean flag=false;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public synchronized void saveMoney(double money){
        if (flag==true){
            try {
                this.wait(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        //存钱的功能
        this.balance=this.balance+money;
        System.out.println(Thread.currentThread().getName()+"往卡中存钱"+money+"余额为:"+this.balance);
        flag=true;


        //唤醒等待的队列
        this.notify();
    }


    public synchronized void takeMoney(double money){
        if (flag==false){
            try {
                this.wait(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        //存钱的功能
        this.balance=this.balance-money;
        System.out.println(Thread.currentThread().getName()+"从卡中取钱"+money+"余额为:"+this.balance);
        flag=false;


        //唤醒等待的队列
        this.notify();

    }
}



package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 19:57 2022/7/18
 */
public class BoyTask implements Runnable{
    private BankCard bankCard;
    public BoyTask(BankCard bankCard){
        this.bankCard=bankCard;
    }
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            bankCard.saveMoney(1000);
        }
    }
}



package com.qy151.demo02;

/**
 * @unthor : YSH
 * @date : 19:58 2022/7/18
 */
public class GirlTask implements Runnable{
    private BankCard bankCard;
    public GirlTask(BankCard bankCard){
        this.bankCard=bankCard;
    }
    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            bankCard.takeMoney(1000);
        }
    }
}



package com.qy151.demo02;

import com.qy151.demo01.Boy;
import com.qy151.demo01.Girl;

/**
 * @unthor : YSH
 * @date : 19:47 2022/7/18
 */
public class Test02 {
    public static void main(String[] args) {
        BankCard bankCard = new BankCard();

        BoyTask boyTask = new BoyTask(bankCard);
        GirlTask girlTask = new GirlTask(bankCard);

       Thread t1 = new Thread(boyTask,"喜羊羊");
       Thread t2 = new Thread(girlTask,"美羊羊");

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



    }
}

12.2sleep和wait的区别

1.所在得类不同。sleep属于Thread类,wait属于Object类。
2.使用的地方: sleep可以使用再任何代码块。wait只能再同步代码块中。
3.是否释放锁资源: sleep不释放锁资源,wait会释放锁资源。
4.sleep时间到了自动唤醒,wait必须需要使用notify和notifyAll唤醒

12.3 notify和notyfyAll区别     

12.4经典问题

13.线程池

13.1什么是线程池      

该池子中预先存储若干个线程对象。整个池子就是线程池   

13.2线程池的作用

13.3线程池的原理

13.4线程池的创建方式

所有的线程池---封装了一个父接口---java.util.concurrent.Executor.

​ 它的实现接口: ExecutorService.

有一个工具类。Executors可以帮你创建相应的线程池。

[1] 创建单一线程池 newSingleThreadExecutor()

[2] 创建定长线程池。newFixedThreadPool(n);

[3] 创建可变线程池. newCachedThreadPool()

[4] 创建延迟线程池 .newScheduledThreadPool(n);

执行方式: 

  1. submit
  2. execute
  3. schedule  只有延迟线程池可用
package com.qy151.demo03;

import java.util.concurrent.*;

/**
 * @unthor : YSH
 * @date : 20:27 2022/7/18
 *
 *
 * //创建线程池的方式
 *
Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务
 *       ExecutorService: 线程池的子接口
 *           shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
 *           shutdownNow(): 立即关闭线程池。
 *           isTerminated():判断线程池是否终止了。
 *           submit(): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。
 */
public class Test01 {
    public static void main(String[] args) {
        //单一线程池: 适应场景:队列要求线程有序执行。
        //ExecutorService executorService = Executors.newSingleThreadExecutor();

        //创建固定长度的线程池对象
        //ExecutorService executorService = Executors.newFixedThreadPool(6);//默认长度为6,超出的线程池对象需要等待

        //创建可变长度的线程池对象
        //ExecutorService executorService = Executors.newCachedThreadPool();

        //创建可延迟的线程池对象
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "====================");
                }
            },10, TimeUnit.SECONDS);
        }
        executorService.shutdown();
    }
}

14.使用最原始的方式创建线程池

上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

package com.qy151.demo03;

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @unthor : YSH
 * @date : 20:55 2022/7/18
 *
 *
 *          int corePoolSize, 核心线程数 -- 线程池中始终存活的线程数
 *          int maximumPoolSize, 最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
 *          long keepAliveTime, 最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程
 *          TimeUnit unit, 时间单位
 *          BlockingQueue<Runnable> workQueue: 堵塞队列,
 *
 *  根据你自己的业务以及服务器配置来设置。
 */
//LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
public class Test02 {
    public static void main(String[] args) {
        BlockingDeque blockingDeque = new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,5,10, TimeUnit.SECONDS,blockingDeque);


        for (int i = 0; i < 6; i++) {
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"=====================");
                }
            });
        }

        threadPoolExecutor.shutdown();

    }

}

15.手动锁

Lock它是手动锁的父接口,它下面有很多实现类。

lock()方法。

unlock()释放锁资源,放在finally中

15.1手动锁的使用

15.1.1重入锁

package com.qy151.demo05;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @unthor : YSH
 * @date : 21:40 2022/7/18
 */
public class Ticket implements Runnable{
    private int ticket =100;
    Lock suo = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                suo.lock();
                System.out.println(suo.tryLock());
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余:" + ticket + "张票");
                } else {
                    break;
                }
            } finally {
                suo.unlock();
            }
        }
    }
}




package com.qy151.demo05;

/**
 * @unthor : YSH
 * @date : 21:40 2022/7/18
 */
public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"曹操");
        Thread t2 = new Thread(ticket,"刘备");
        Thread t3 = new Thread(ticket,"孙权");
        Thread t4 = new Thread(ticket,"司马懿");


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

 15.1.2读写锁

感谢观看!!!!!!

猜你喜欢

转载自blog.csdn.net/Ysuhang/article/details/125864719