JAVA高级--多线程

本文详细解析了线程的概念,进程与线程的区别,线程的组成、特点和创建方式,包括继承Thread、Runnable接口和Callable接口。此外,讲解了线程安全、死锁、通信、线程池及其应用,以及手动锁和Lock接口的使用。

1.什么是线程?

        线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

2.进程和线程的区别

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

2. 一个程序运行后至少有一个进程。

3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。

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

2.1线程的组成

任何一个线程都具有基本的组成部分CPU时间片: 操作系统(OS)会为每个线程分配执行时间。    运行数据:

堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。 栈空间:  存储线程需使用的局部变量,每个线程都拥有独立的栈。            

线程的逻辑代码.

2.2线程的特点

1. 线程抢占式执行,效率高,可防止单一线程长时间独占CPU。

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

3.创建线程的三种方式

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

 2. 实现Runnable接口 

3.  实现Callable接口

继承Thread类的代码:

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("线程======================"+i);
        }
    }
}

 测试:

public class Test01 {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        m1.start();

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

继承Runnable接口:四个窗口卖100张票

public class MyThread implements Runnable{
private Integer ticket = 100;
Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                if (ticket>0){
                        ticket--;
                        System.out.println(Thread.currentThread().getName()+"卖了一张还剩:"+ticket+"张");
                }else {
                    break;
                }
            } 
        }
    }
}

 测试:

public class Test01 {
    public static void main(String[] args) {
       MyThread myThread = new MyThread();
       Thread t1 = new Thread(myThread,"窗口A");
       Thread t2 = new Thread(myThread,"窗口B");
       Thread t3 = new Thread(myThread,"窗口C");
       Thread t4 = new Thread(myThread,"窗口D");
       t1.start();
       t2.start();
       t3.start();
       t4.start();
    }
}

 获取线程的名字和设置线程的名字:

MyThread m1 = new MyThread();
m1.setName("一号窗口");
String name = m1.getName();
System.out.println(name);

4.线程中的方法

4.1休眠

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

public class TakeSleep {
    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++){
            try {
                Thread.sleep(100);//是当前的线程进入一段休眠状态
                System.out.println("这是第"+i+"次");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
        }
    }
}

4.2放弃

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

代码:

public class T extends Thread{
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            Thread.yield();//可能导致t1和t2的交替的频率变得高了。当前的线程主动放弃时间片,回到就绪状态,再和其他的竞争下一次的时间片
            System.out.println(Thread.currentThread().getName()+"第"+i+"次");
        }
    }
}

 测试:

public class TestYield {
    public static void main(String[] args) {
        T t1 = new T();
        t1.setName("张三");
        T t2 = new T();
        t2.setName("李四");
        t1.start();
        t2.start();
    }
}
public class TestYield {
    public static void main(String[] args) {
        T t1 = new T();
        t1.setName("张三");
        T t2 = new T();
        t2.setName("李四");
        t1.start();
        t2.start();
    }
}

4.3加入

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

代码:

public class ThreadJoin extends Thread{
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"这是第"+i+"次");
        }
    }
}

 测试:

public class Test01 {
    public static void main(String[] args) throws Exception{
        ThreadJoin j = new ThreadJoin();
        j.setName("A");

        j.start();
        j.join();
        //把线程ThreadJoin加入当前的main线程中,直到ThreadJoin线程执行完毕后,main才会执行

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

4.4守护线程

线程对象.setDaemon(true);设置为守护线程。 线程有两类:用户线程(前台线程)和守护线程(后台线程)如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。

代码:

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

 测试:

public class Test01 {
    public static void main(String[] args) {
        ThreadDaemon t = new ThreadDaemon();
        t.setName("A");
        t.setDaemon(true);//设置ThreadDaemon为守护线程,当前台线程都执行完毕后,守护线程也会自动结束
        t.start();
        for (int i=0;i<20;i++){
            System.out.println("main==========="+i);
        }
    }
}

4.5线程安全问题

代码:

public class ThreadSaft {
    private static String[] array = new String[5];
    private static int index=0;

    public static void main(String[] args) throws Exception{
        Runnable hello = new Runnable() {
            @Override
            public void run() {
                synchronized (array) {
                    if (array[index] == null) {
                        array[index] = "hello";
                        index++;
                    }
                }
            }
        };
        Runnable world = new Runnable() {
            @Override
            public void run() {
                synchronized (array) {
                    if (array[index] == null) {
                        array[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(array));
    }
}

 4.6线程安全问题的解决办法

同步方法:

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

        // 代码(原子操作)

}

synchronized (Lock.b){
    System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
    System.out.println("可以吃饭了");
}

 5.线程死锁

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

死锁的原因:锁与锁之间有嵌套导致。

解决死锁的办法:

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

public class Lock {
    public static Object a = new Object();
    public static Object b = new Object();
}
public class Boy extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (Lock.a){
            System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
            synchronized (Lock.b){
                System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
                System.out.println("可以吃饭了");
            }
        }
    }
}

public class Girl extends Thread{
    @Override
    public void run() {
        synchronized (Lock.b){
            System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
            synchronized (Lock.a){
                System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
                System.out.println("可以吃饭了");
            }
        }
    }
}

 测试:

public class Test {
    public static void main(String[] args) {
        Boy b = new Boy();
        Girl g = new Girl();
        b.setName("李先生");
        g.setName("摩托哥");
        b.start();
        g.start();
    }
}

6.线程通信

都不是Thread类中,而是Object类中

等待:释放锁,进入等待队列

public final void wait()

public final void wait(long timeout)

通知:唤醒等待队列中的线程,进入到就绪队列,参与CPU的竞争

public final void notify();唤醒一个

public final void notifyAll()唤醒所有

取钱存钱:

public class BankCard01 {
    private double balance;
    private boolean flag = false;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    public synchronized void save(double money){
        if (flag==true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //存钱
        this.balance = this.balance+money;
        System.out.println(Thread.currentThread().getName()+"存入了"+money+"现在卡里的金额为:"+this.balance);
        flag=true;
        this.notify();
    }
    public synchronized void task(double money){
        if (flag==false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取钱
        this.balance = this.balance-money;
        System.out.println(Thread.currentThread().getName()+"取出了"+money+"现在卡里的金额为:"+this.balance);
        flag=false;
        this.notify();
    }
}

存钱:

public class BoyTask extends Thread{
    private BankCard01 bankCard01;
    public BoyTask(BankCard01 bankCard01){
        this.bankCard01 = bankCard01;
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            bankCard01.save(1000);
        }
    }
}

 取钱:

public class GirlTask extends Thread{
    private BankCard01 bankCard01;
    public GirlTask(BankCard01 bankCard01){
        this.bankCard01 = bankCard01;
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            bankCard01.task(1000);
        }
    }
}

 测试:

public class Test02 {
    public static void main(String[] args) {
        BankCard01 bankCard01 = new BankCard01();
        BoyTask b = new BoyTask(bankCard01);
        GirlTask g = new GirlTask(bankCard01);
        b.setName("king");
        g.setName("马闹鸡");
        b.start();
        g.start();
    }
}

7.线程池

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

7.1线程池的作用

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

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

避免频繁的创建和销毁

 7.2创建线程池的方式

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

public class Test {
    public static void main(String[] args) {
        //创建单一线程池:适应场景:队列要求线程有序执行
        //ExecutorService executorService = Executors.newSingleThreadExecutor();
        //创建固定长度的线程池对象
        //ExecutorService executorService = Executors.newFixedThreadPool(3);
        //创建可变长度的线程池
        //ExecutorService executorService = Executors.newCachedThreadPool();
        //创建延迟线程池对象
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        for (int i=0;i<5;i++){
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~~~");
                }
            },3,TimeUnit.SECONDS);
        }
        executorService.shutdown();
    }
}

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

//int corePoolSize, 核心线程数
//int maximumPoolSize, 最大线程数
//long keepAliveTime, 空闲时间
//TimeUnit unit, 时间单位
//BlockingQueue<Runnable> workQueue 堵塞队列

public class Test02 {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);
        for (int i=0;i<5;i++){
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~~~~~~~~");
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
}

9.创建线程的第三种方式

实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。

public class My1 implements Callable<Integer> {
    private Integer sum=0;
    @Override
    public Integer call() throws Exception {
        for (int i=1;i<=50;i++){
            sum+=i;
        }
        return sum;
    }
}
public class My2 implements Callable<Integer> {
    private Integer sum=0;
    @Override
    public Integer call() throws Exception {
        for (int i=51;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
}

 测试:

public class Test {
    public static void main(String[] args) throws Exception{
        My1 m1 = new My1();
        My2 m2 = new My2();
        //自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个                    FutureTask类中,建议使用线程池来提交任务。
        //FutureTask futureTask = new FutureTask(m1);
        //Thread t1 = new Thread(futureTask);
        //t1.start();
        //System.out.println(futureTask.get());
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<Integer> future1 = executorService.submit(m1);
        Future<Integer> future2 = executorService.submit(m2);
        Integer integer1 = future1.get();
        Integer integer2 = future2.get();
        System.out.println(integer1+integer2);
    }
}

10.手动锁

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

lock()方法。

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值