Java基础_多线程

章节地址
Java基础_枚举类和注解https://blog.csdn.net/weixin_46349544/article/details/122450137
Java基础_泛型https://blog.csdn.net/weixin_46349544/article/details/122489562
Java基础_Lambda表达式https://blog.csdn.net/weixin_46349544/article/details/123122401
Java基础_函数式接口https://blog.csdn.net/weixin_46349544/article/details/123123017
Java基础_Streamhttps://blog.csdn.net/weixin_46349544/article/details/123382892
Java基础_多线程https://blog.csdn.net/weixin_46349544/article/details/123162259
Java基础_Java比较器https://blog.csdn.net/weixin_46349544/article/details/123236205

程序,进程,线程

在这里插入图片描述

JVM中的程序计数器和虚拟机栈是线程私有的
方法区和堆是属于进程的,多个线程可以共享方法区和堆

在这里插入图片描述
在这里插入图片描述

线程的创建和使用

在这里插入图片描述

创建线程方式1:重写Thead run方法

  1. .新建类继承Thread类
  2. 新建的类重写run方法
  3. 创建新建的这个类对象调用start方法

start方法的作用:

  • 启动当前线程
  • 调用run方法

Thread.currentThread().getName() 获取当前线程的名称

package com.peppacatt.wswtest.javatest.basic;

class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("线程"+Thread.currentThread().getName()+":执行代码");
        }
    }
}

/**
 * 多线程创建的几个步骤:
 * 1.继承Thread类
 * 2.重写run方法
 * 3.调用start方法
 */
public class ThreadTest {

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println("线程"+Thread.currentThread().getName()+":执行代码");
        }
    }

}

在执行到t.start()时还是在主线程内.当start方法被调用后,另一个线程就开始与主线程同时执行.start方法后的代码还是在主线程内
如果将t.start()换为t.run()那么run方法还是会执行,但是就没有另起一个线程了,始终就只有一个线程.程序执行的结果就是:先将run方法内的代码执行完再执行后面的方法

执行结果:
由于多个线程同时执行,cpu分给各个线程的时间片不同,可以看出,主线程和线程1是交替执行的
在这里插入图片描述

说明

在这里插入图片描述

匿名子类的方式创建线程

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest1 {
    public static void main(String[] args) {

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("线程" + Thread.currentThread().getName() + ":执行");
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("线程" + Thread.currentThread().getName() + ":执行");
                }
            }
        }.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行");
        }

    }
}

执行结果:
在这里插入图片描述

创建线程方式2:重写Runnable run方法

  1. 新建类实现Runnable接口
  2. 新建的类重写run方法
  3. 创建Thread对象并将新建的类作为参数传入
  4. 调用对象的start方法
package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
        }
    }
}

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest {

    public static void main(String[] args) {

        new Thread(new MyRunnable()).start();

        for (int i = 0; i < 50; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
        }

    }

}

Lambda方式简写

Runnable是一个函数式接口
在这里插入图片描述

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest {

    public static void main(String[] args) {

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
            }
        }).start();
        
    }

}

两种方式对比

在这里插入图片描述

创建线程方式3:实现Callable接口

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码示例:

package com.peppacatt.wswtest.javatest.basic;

import java.util.concurrent.Callable;

public class MyCallable implements Callable {


    @Override
    public Object call() throws Exception {
        System.out.println("MyCallable");
        return null;
    }


}

package com.peppacatt.wswtest.javatest.basic;

import java.util.concurrent.FutureTask;

public class ThreadTestCallable {

    public static void main(String[] args) {

        MyCallable mc = new MyCallable();
        FutureTask ft = new FutureTask(mc);

        //FutureTask也实现了Runnable接口
        Thread t = new Thread(ft);
        t.start();

//        try {
//            //get()返回值就是MyCallable类中重写的call()的返回值
//            ft.get();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }

    }

}

创建线程方式4:使用线程池

在这里插入图片描述
在这里插入图片描述

package com.peppacatt.wswtest.javatest.basic;

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

public class ThreadTestThreadPool {

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        es.execute(new MyRunnable());
    }

}

在这里插入图片描述

Thread类中的常用方法

在这里插入图片描述

yield方法

yield方法就是释放当前CPU执行权,让CPU去执行其他线程,但是后面CPU在分配时间片的时候还是会分配到当前线程

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest2 {

    public static void main(String[] args) {

        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("线程" + Thread.currentThread().getName() + ":执行"+i);
                    if (i == 5000) {
                        System.out.println("------------------------释放当前cpu执行权---------------------------------");
                        Thread.yield();
                    }
                }
            }
        }.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行"+i);
        }

    }

}

在这里插入图片描述
可以看出当Thread-0执行到5000时就暂停,让其他线程去执行.下次一定时间片分配的时候,还是可能会分配到Thead-0

join方法

join方法就是在当前线程调用另一个线程的join方法的时候,当前线程进入阻塞状态,先把另一个线程的代码全部执行完毕再来执行当前线程

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest2 {

    public static void main(String[] args) {

        Thread t = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
                }
            }
        };

        t.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
            if (i == 40) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

执行结果:
在这里插入图片描述
可以看出mian线程在执行到40时,先让Thread-0把它的代码全部执行完毕,然后再来执行main线程

sleep

让当前线程睡眠指定的毫秒,线程进出阻塞状态,阻塞完之后再执行当前线程的后面代码.在当前线程阻塞的时候其他线程可以执行

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest3 {

    public static void main(String[] args) {

        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
                    if (i == 40) {
                        try {
                            System.out.println("-------------------sleep---------------------------");
                            sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
        }

    }

}

在这里插入图片描述
可以看出Thead-0执行到40时,Thead-0线程沉睡了3秒,再执行后面的代码
在Thead-0线程沉睡的时候,其他线程可以执行

线程调度

在这里插入图片描述
在这里插入图片描述

线程的生命周期

在这里插入图片描述
在这里插入图片描述

卖票问题

要求:多个线程一起卖总共100张票

代码:

package com.peppacatt.wswtest.javatest.basic;

public class MyThread extends Thread {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }

}

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest4 {

    public static void main(String[] args) {

        MyThread t = new MyThread();
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t.start();
        t1.start();
        t2.start();

    }

}

问题一:超卖

当前的ticket是每个线程同时拥有100张,加起来一共300张了

解决方式1:将ticket设为static

package com.peppacatt.wswtest.javatest.basic;

public class MyThread extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }

}

解决方式2: 用Runable

package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest4 {

    public static void main(String[] args) {

        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t.start();
        t1.start();
        t2.start();

    }

}

问题二: 错票和重票

错票

假设的当前只剩下最后一张票了,当前线程在阻塞处1发生了阻塞,后面代码还未执行.然后此时其他线程去执行ticket减1后ticket=0,该进程继续执行后面代码的再执行ticket-1=-1就产生了错票

package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {

                //阻塞处1
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                ticket--;

                //阻塞处2
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }


            } else {
                break;
            }
        }
    }
}

在这里插入图片描述

重票

如果当前票号为50,当前线程已经将票号为50的票卖了,然后在阻塞处2阻塞了,未执行-1.此时其他线程再买票的时候票号还是50.就产生了重票

package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {

                //阻塞处1
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }

                System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                ticket--;

                //阻塞处2
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            } else {
                break;
            }
        }
    }
}

问题分析

重票和错票就是产生了线程安全问题,就是在当前线程的run方法还未执行完的时候,其他线程先执行了,再接着执行当前线程剩余的代码.
如果该run方法内存在共享数据,就会产生问题

在Java中,通过同步机制解决线程安全问题

线程同步

同步代码块

在这里插入图片描述

在同步代码块内,只会让当前线程先执行操作,如果过个线程同时执行到这里,需要等待当前线程执行完该代码块,其他线程才能执行

解决实现Runable方式

package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket > 0) {

                    //阻塞处1
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                    ticket--;

                    //阻塞处2
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }


                } else {
                    break;
                }
            }

        }
    }
}

解决继承Thread方式

new 一个共享的对象两种方式:

  1. obj
  2. 当前类.class
package com.peppacatt.wswtest.javatest.basic;

public class MyThread extends Thread {

    private static int ticket = 100;
//    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
//            synchronized (obj) {
            synchronized (MyThread.class) {
                if (ticket > 0) {

                    //阻塞处1
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                    ticket--;

                    //阻塞处2
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }


                } else {
                    break;
                }
            }

        }
    }

}


同步方法

同步方法中的锁是this

解决实现Runable方式

package com.peppacatt.wswtest.javatest.basic;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            sale();
            if (ticket <= 0) {
                break;
            }
        }
    }

    public synchronized void sale() {
        if (ticket > 0) {

            //阻塞处1
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
            ticket--;

            //阻塞处2
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }


        }
    }
}

解决继承Thread方式

同步方法需要加static

package com.peppacatt.wswtest.javatest.basic;

public class MyThread extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            sale();
            if (ticket <= 0) {
                break;
            }
        }
    }

    public static synchronized void sale() {
        if (ticket > 0) {

            //阻塞处1
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
            ticket--;

            //阻塞处2
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }


        }
    }

}

Lock锁

在这里插入图片描述

package com.peppacatt.wswtest.javatest.basic;

import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    private ReentrantLock rl = new ReentrantLock(true);

    @Override
    public void run() {
        while (true) {
            try {
                rl.lock();
                if (ticket > 0) {

                    //阻塞处1
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                rl.unlock();
            }

        }
    }
}

ReetrantLock锁和synchronized锁的区别

在这里插入图片描述
在这里插入图片描述

同步的优缺点:

好处:

  • 同步的方式解决了线程安全问题

坏处:

  • 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

死锁

在这里插入图片描述

代码演示:

package com.peppacatt.wswtest.javatest.basic;

public class ThreadTest6 {

    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("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(() -> {
            synchronized (s2) {
                s1.append("c");
                s2.append("1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s1) {
                    s1.append("b");
                    s2.append("2");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();


    }

}

线程通信

在这里插入图片描述

wait() notify() notifyAll()的调用者必须是锁对象在下面的案例中相当于this.wait() 和 this.notify()
案例:
使用两个线程打印100-1,线程1和线程2交替打印

package com.peppacatt.wswtest.javatest.basic;

import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private int ticket = 100;

    private ReentrantLock rl = new ReentrantLock(true);

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (ticket > 0) {

                    //阻塞处1
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
                    ticket--;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

在这里插入图片描述
在此案例中:
当线程a第一次进入到synchronized代码块中,a执行notify方法没有可以唤醒的方法,操作完共享数据后,线程a执行wait方法进入阻塞状态并释放锁.
此时线程b进入了同步代码块中执行notify方法唤醒了线程a,然后b操作完共享数据之后也执行wait方法进入阻塞状态并释放锁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值