Java--多线程

1.什么是线程

线程,又称轻量级进程(Light Weight Process)。 进程中的一条执行路径,也是CPU的基本调度单位。 一个进程由一个或多个线程组成,彼此间完成不同的工作, 同时执行,称为多线程。

2.进程与线程的区别

1.进去是操作系统资源分配的基本单位,而线程是CPU基本调度单位。

2.一个程序运行后至少有一个进程。一个进程可以包含多个线程,但至少需要一个线程。

3.进程间不能共享数据段地址,但是同进程的线程可以。

3.线程的组成

任何一个线程都具有最基本的组成部分

1.CPU时间片:操作系统(os)会为每个线程分配执行时间。

2.运行数据: 堆空间:存储线程需要的对象,多个线程可以共享堆的中的数据。

                       栈空间:存储线程需要的局部变量,每个线程都拥有独立的栈。

3.线程的逻辑代码。

4.线程的特点

1.线程抢占式执行。

效率高  可防止单一线程长时间占用CPU

2.在单核CPU中,宏观上同时执行,微观上顺序执行。

5.线程的创建

1.继承 Thread类,重写run方法

package com.lzl.test01;

/**
 * @create 2022-07-18
 */
//继承Thread 方法重写 run方法
public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("----------");
    }
}


package com.lzl.test01;

/**
 * @create 2022-07-18
 */
public class Test01 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread =new MyThread();
        //开启线程
        myThread.start();
    }
}

2.实现Runnable接口

package com.lzl.test01;

/**
 * @create 2022-07-18
 */
//实现 Runnable 接口 
public class MyRunnable implements Runnable{
    @Override
    //重写run方法
    public void run() {
        System.out.println("-----------");
    }
}

package com.lzl.test01;

/**
 * @create 2022-07-18
 */
public class Test02 {
    public static void main(String[] args) {
        //创建线程对象
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        //开启线程
        thread.start();
    }
}

3.实现Callable接口, 他和实现Runnable接口差不多,就是该接口中的方法有返回值和异常抛出

package com.lzl.test01;

import java.util.concurrent.Callable;

/**
 * @create 2022-07-18
 */
//实现 Callable 接口
public class MyCallable implements Callable {
    @Override
    //该接口中的方法有 返回值 和 抛出异常
    public Object call() throws Exception {
        return 200;
    }
}



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

/**
 * @create 2022-07-18
 */
public class Test03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程对象
        MyCallable myCallable = new MyCallable();
        //创建一个单一线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //提交任务给线程池中线程对象、Runnable和Callable类型的任务。
        Future submit = executorService.submit(myCallable);
        //获取执行方法后的返回值
        Object o = submit.get();
        System.out.println(o);
    }
}

6.获取和设置线程的名称

1.获取线程的ID和线程名称

        1.在Thread的子类中调用this.getid()或this.getName();

        2.使用Thread.currentThread().getId()或Thread.currentThread().getName().

package com.lzl.test02;

/**
 * @create 2022-07-18
 */
public class Test01 {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        MyThread m1 =new MyThread();
        m1.start();
    }

}
class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println(getId());
        System.out.println(getName());
        System.out.println(Thread.currentThread().getId());
        System.out.println(Thread.currentThread().getName());
    }
}

2.修改线程的名称

        1. 调用线程对象的setName()方法

        2. 使用线程子类的构造方法赋值

package com.lzl.test02;

/**
 * @create 2022-07-18
 */
public class Test01 {
    public static void main(String[] args) {
        MyThread m = new MyThread("线程1");
        /*m.setName("线程1");*/
        m.start();
        MyThread m1 =new MyThread("线程2");
        /*m1.setName("线程2");*/
        m1.start();
    }

}
class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getId());
        System.out.println(getName());
        System.out.println(Thread.currentThread().getId());
        System.out.println(Thread.currentThread().getName());
    }
}

7.线程的状态

 8.常用的方法

1.休眠

public static void sleep(long millis)      当前线程主动休眠millis毫秒。

2.放弃

public static void yield()       当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

3.加入

public final void join()       允许其他线程加入到当前线程中

4. 优先级

线程对象.setPriority()      线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高。

5.守护线程

 线程对象.setDaemon(true);设置为守护线程。      

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

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

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

9.线程的状态

10.关于线程安全问题 

多线程安全问题:

当多线程并发访问临界资源时,如果破环原子操作,可能会造成数据的不一致。

临界资源:共享资源(同一对象),一次只允许一个线程使用,才能保证其的正确性。

原子操作:不可分割的多部操作,被视为一个整体,其顺序和步骤不可打乱或缺省。

同步方式:

同步代码块:

synchronized(临界资源对象){
    //代码原子操作
}

注意:每个对象都有一个互诉锁标记,用来分配给线程。

           只有拥有对象互诉锁标记的线程,才会进入该对象枷锁的同步代码块。

           线程退出同步代码块,会释放相应的互诉锁标记

同步方法:

同步方法:

synchronized 返回值类型 方法名称(形参列表){
    //对当前对象(this)加锁
    //代码 原子操作
}

注:只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。

        线程退出同步方法时,会释放相应的互斥锁标记。

同步规则

注意:只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。

          如果调用不包含同步代码块的方法,或普通方法时,则不需要锁标记。可直接调用。

已知JDK中线程安全的类:

StringBuffer  Vector  Hashtable  

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

11.关于死锁

当第一线程拥有A对象锁标记,并等待B对象锁标记时,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。

一个线程可以拥有多个对象的锁标记,当线程阻塞时,不会释放已拥有的锁标记,由此可能造成死锁

实例:

package com.lzl.死锁;

import java.util.Date;

/**
 * @create 2022-07-19
 */
public class Test01 {
    public static void main(String[] args) {
        Boy boy = new Boy();
        boy.setName("张三");
        boy.start();
        Gril gril = new Gril();
        gril.setName("李四");
        gril.start();
    }
}
class Lock{
    public static Object a = new Object();
    public static Object b = new Object();
}
class Boy extends Thread{
    @Override
    public void run() {
        synchronized (Lock.a){
            System.out.println(Thread.currentThread().getName()+"获取到筷子a");
            synchronized (Lock.b){
                System.out.println(Thread.currentThread().getName()+"获取到筷子B");
                System.out.println(Thread.currentThread().getName()+"开始吃饭");
            }
        }
    }
}
class Gril extends Thread{
    @Override
    public void run() {
        synchronized (Lock.b){
            System.out.println(Thread.currentThread().getName()+"获取到筷子B");
            synchronized (Lock.a){
                System.out.println(Thread.currentThread().getName()+"获取到筷子A");
                System.out.println(Thread.currentThread().getName()+"开始吃饭");
            }
        }
    }
}

12.线程通信

等待:

public final void wait();

public final void wait(long timeout);

必须对obj加锁的同步代码块中,在一个线程中,调用obj,wait()时,线程会释放其拥有的所有标记。同时此线程堵塞在o的等待队列中。释放锁,进入等待队列。

通知:

public final  void notify();

public final void notifyAll();

package com.lzl.通信;

/**
 * @create 2022-07-19
 */
public class Test01 {
    public static void main(String[] args) {
        Card card = new Card();
        Boy boy = new Boy(card);
        boy.setName("张三");
        boy.start();
        Gril gril = new Gril(card);
        gril.setName("李四");
        gril.start();
    }
}
class Card{
   private double balance;
    private boolean falg = false;

    public double getBalance() {
        return balance;
    }

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

    public synchronized void save(double maney){
        if (falg==true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.balance=balance+maney;
        System.out.println(Thread.currentThread().getName() + "存入1000大洋,卡里还有" + this.balance + "大洋");
        falg=true;
        this.notify();
    }

    public synchronized void take(double maney){
        if (falg==false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.balance=balance-maney;
        System.out.println(Thread.currentThread().getName() + "取走1000大洋,卡里还有" + this.balance + "大洋");
        falg=false;
        this.notify();
    }
}
class Boy extends Thread{
    private Card card;

    public Boy(Card card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            card.save(1000);
        }

    }
}
class Gril extends Thread{
    private Card card;

    public Gril(Card card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            card.take(1000);
        }

    }
}

sleep()和wait()的区别

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

notify和notifyAll区别

notifyAll()会唤醒所有的线程,notify()只会唤醒一个线程.nofityAll()调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而nofity()只会唤醒一个线程具体唤醒那个线程由虚拟机控制。

13.线程池

1.什么是线程池

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

线程是宝贵的内存资源、单个线程约占用1MB的空间,过多分配易造成内存的溢出。

频繁的创建以及销毁线程会增加虚拟机的回收频率、资源开销、造成程序性能的下降。

2.线程池的作用

线程容器,可设定线程分配的数量上限。

将预先创建好的线程对象存入线程池中,并重用线程池中的线程对象。

避免频繁的创建和销毁。

3.线程池的创建方式

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

​ 它的实现接口: ExecutorService.

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

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

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

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

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

Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务

       ExecutorService: 线程池的子接口
        shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
        shutdownNow(): 立即关闭线程池。
        isTerminated():判断线程池是否终止了。
        submit(): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。

package com.lzl;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @create 2022-07-19
 */
public class 线程池 {
    public static void main(String[] args) {
        //创建单一线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <5 ; i++) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        });
        //创建固定长度的线程池对象
        ExecutorService executorService1 = Executors.newFixedThreadPool(3);
        for (int i = 0; i <5 ; i++) {
            executorService1.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }

            });
        }
        //创建可变长度的线程池对象
        ExecutorService executorService2 = Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            executorService2.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }

            });
        }
        //创建延迟线程池对象
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        for (int i = 0; i <5 ; i++) {
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }

            },3, TimeUnit.SECONDS);
        }
    }
}

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

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

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

package com.lzl;

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

/**
 * @create 2022-07-19
 */
public class Test01 {
    public static void main(String[] args) {
        /*int corePoolSize, 核心线程数
        int maximumPoolSize, 最大线程数
        long keepAliveTime, 空闲时间
        TimeUnit unit, 时间单位
        BlockingQueue<Runnable> workQueue: 堵塞队列  */

        //LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
        BlockingDeque blockingDeque = new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(5,5,10, TimeUnit.SECONDS,blockingDeque);
        for (int i = 0; i <5 ; i++) {
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        //关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
        threadPoolExecutor.shutdown();
    }
}

14.手动锁 ​​​​​​​

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

常用方法: 

void lock() //获取锁,如果锁被占用,则等待

boolean tryLock //尝试获取锁(成功返回true 失败返回false,不堵塞)

void unlock() 释放锁

package com.lzl;

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

/**
 * @create 2022-07-19
 */
public class Test2 {
    public static void main(String[] args) {
        MyRunnable m = new MyRunnable();
        Thread t1 = new Thread(m,"第一个售票点");
        t1.start();
        Thread t2 = new Thread(m,"第二个售票点");
        t2.start();
        Thread t3 = new Thread(m,"第三个售票点");
        t3.start();
        Thread t4 = new Thread(m,"第四个售票点");
        t4.start();
    }

}
class MyRunnable implements Runnable{
    public  Integer ticket = 100;
    public Lock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true) {
            lock.lock();
            try {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":当前卖出一张票还剩" + ticket + "张");
                    ticket--;    //每卖出1张 ,要减去1张
                } else {
                    System.out.println("票已售完");
                    System.exit(0); //退出程序
            }
            } finally {
                lock.unlock();
            }

        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值