48、线程

news2024/11/28 12:40:05

 一、线程相关概念:

1、程序(program):

是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的代码。

2、进程:
(1)进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
(2)进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程.

3、什么是线程:

(1)线程由进程创建的,是进程的一个实体

(2)一个进程可以拥有多个线程

4、单线程:

同一个时刻,只允许执行一个线程。

5、多线程:

同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。

6、并发:

同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发

7、并行:(有同伴一起走)

同一个时刻,多个任务同时执行,多核cpu可以实现并行

·查看电脑CPU的方式:

方式一:右击屏幕底下的长条形任务栏,打开“任务管理器”,“打开资源管理器” 

方式二:右击“我的电脑”,选择“管理”,“设备管理器”,“处理器”

方式三:用java代码查看

package event_;

public class CpuNum {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        //获取当前电脑的cpu数量/核心数
        int cpuNums = runtime.availableProcessors();
        System.out.println("当前有cpu个数:" + cpuNums);
    }
}
//当前有cpu个数:8

我的是4核8线程

二、线程基本使用
1、创建线程的两种方式

(1)继承Thread类,重写run方法

package threaduse;

public class Thread01 {
    public static void main(String[] args) throws Exception{
        //创建Cat对象,可以当做线程使用
        Cat cat=new Cat();
        cat.start();//启动线程

        //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
        //这时,主线程和子线程是交替执行
        System.out.println("主线程继续执行"+Thread.currentThread().getName());
        for(int i=0;i<10;i++){
            System.out.println("主线程 i="+i);
            //让主线程休眠
            Thread.sleep(1000);
        }
    }
}

//1、当一个类继承了Thread,该类就可以当做线程使用
//2、要重写run方法,写上自己的业务逻辑
//3、run方法是Thread类实现了Runnable接口用的
/*  @Override
    public void run() {
        Runnable task = holder.task;
        if (task != null) {
            task.run();
        }
    }
 */
class Cat extends Thread{
    int times=0;
    public void run() {
        //如果不加循环,代码执行一次就退出了
        while(true){
            //该线程每隔1秒,在控制台输出"喵喵,我是小猫咪"
            System.out.println("喵喵,我是小猫咪"+(++times)+" 线程名:"+Thread.currentThread().getName());
            //让线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(times==10){
                break;
            }
        }
    }
}
//主线程继续执行main
//主线程 i=0
//喵喵,我是小猫咪1 线程名:Thread-0
//喵喵,我是小猫咪2 线程名:Thread-0
//主线程 i=1
//喵喵,我是小猫咪3 线程名:Thread-0
//主线程 i=2
//喵喵,我是小猫咪4 线程名:Thread-0
//主线程 i=3
//喵喵,我是小猫咪5 线程名:Thread-0
//主线程 i=4
//喵喵,我是小猫咪6 线程名:Thread-0
//主线程 i=5
//主线程 i=6
//喵喵,我是小猫咪7 线程名:Thread-0
//主线程 i=7
//喵喵,我是小猫咪8 线程名:Thread-0
//主线程 i=8
//喵喵,我是小猫咪9 线程名:Thread-0
//主线程 i=9
//喵喵,我是小猫咪10 线程名:Thread-0
深入解析cat.start();方法        
        //源码:
        /*1、
            void start(ThreadContainer container) {
                synchronized (this) {
                    start0();
                }
            }
          2、
          //start0()是酵方法,是JVM调用,底层是c/c++实现
          //真正实现多线程的效果,是start0(),而不是run()
              private native void start0();
         */
        cat.start();//启动线程--->最终会执行cat的run方法

        cat.run();//如果直接调用run()方法,这里的run()方法就是一个普通的方法,没有真正地启动一个线程
        //等执行完这个run()方法后,才继续往下走,此时已经不再是多线程了
        //在main里调用run()方法,输出的就是main的线程名,不是要启动的线程名
        //输出:喵喵,我是小猫咪1 线程名:main

 补:主线程决定进程开启,最后结束的线程决定进程结束

 (2)实现Runinable接口,重写run方法 

 

package threaduse;

public class Thread02 {
    public static void main(String[] args) {
        Dog dog=new Dog();
        //dog.start();报错,这里不能调用start
        //创建了Thread对象,把dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}
class Animal{}
class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫...");
    }
}

//线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable{//
    private Runnable target=null;//类型是Runnable
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定,运行类型是传进来的参数的类型
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }
    public void start(){
        start0();//这个方法才是真正实现多线程的方法
    }
    public void start0(){
        run();
    }
}
class Dog implements Runnable{
    int count=0;
    @Override
    public void run() {
        while(true){
            System.out.println("小狗汪汪叫...hi"+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);//休眠一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

线程这样设计的意义,最根本的意义就在于解耦,权责分明:把创建线程交给Thread类,创建线程任务交给实现了Runnable接口的任意类

这样做的好处:

(1)解除类的单继承限制,更好地实现多线程;

(2)解耦之前,线程任务改动需要修改run()方法,解耦后,只需要修改外部类,实现非侵入式的修改,提高代码的可扩展性,这是接口的主要好处

(3)线程任务执行完后,不需要再次创建、销毁线程,只需要实现Runnable接口提交新的的线程任务即可,提高性能节省开销,后面的线程池设计主要体现这一点

2、应用线程

package threaduse;

import javax.swing.plaf.TableHeaderUI;
@SuppressWarnings({"all"})
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1=new Thread(t1);
        Thread thread2= new Thread(t2);
        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{
    int count=0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hello, world! "+(++count)+Thread.currentThread().getName());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}
class T2 implements Runnable{
    int count=0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi "+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                break;
            }
        }
    }
}
//hello, world! 1Thread-0
//hi 1Thread-1
//hello, world! 2Thread-0
//hi 2Thread-1
//hi 3Thread-1
//hello, world! 3Thread-0
//hello, world! 4Thread-0
//hi 4Thread-1
//hello, world! 5Thread-0
//hi 5Thread-1
//hello, world! 6Thread-0
//hello, world! 7Thread-0
//hello, world! 8Thread-0
//hello, world! 9Thread-0
//hello, world! 10Thread-0

3、继承Thread和实现Runnable的区别:

(1)从java的设计来看,通过继承Thread或实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口,都是通过调用start()----->最终实现调用start0();

(2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用

        T3 t3 = new T3("Hello");//一个对象
        Thread thread01=new Thread(t3);
        Thread thread02=new Thread(t3);
        thread01.start();
        thread02.start();
        System.out.println("主线程完毕");

 4、

//使用Thread
package ticket;

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket1 = new SellTicket01();
        SellTicket01 sellTicket2 = new SellTicket01();
        SellTicket01 sellTicket3 = new SellTicket01();

        //三个售票窗口
        sellTicket1.start();
        sellTicket2.start();
        sellTicket3.start();
    }
}
class SellTicket01 extends Thread{
    private static int ticketNum=10;//让多个线程共享ticketNum
    public void run(){
        while(true){
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
            +" 剩余票数="+(--ticketNum));
        }
    }
}
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-0 售出一张票 剩余票数=7
//窗口 Thread-2 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=6
//窗口 Thread-0 售出一张票 剩余票数=4
//窗口 Thread-1 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-1 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-1 售出一张票 剩余票数=0
//窗口 Thread-2 售出一张票 剩余票数=0
//售票结束
//窗口 Thread-0 售出一张票 剩余票数=-1
//售票结束
//售票结束

//出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
//使用Runnable
package ticket;

public class SellTicket {
    public static void main(String[] args) {
        SellTicket02 sellTicket = new SellTicket02();
        //三个售票窗口
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
    }
}
//使用Thread
class SellTicket02 implements Runnable{
    private static int ticketNum=10;//让多个线程共享ticketNum
    public void run(){
        while(true){
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
            +" 剩余票数="+(--ticketNum));
        }
    }
}
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-0 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=7
//窗口 Thread-1 售出一张票 剩余票数=7
//窗口 Thread-0 售出一张票 剩余票数=6
//窗口 Thread-0 售出一张票 剩余票数=4
//窗口 Thread-1 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-1 售出一张票 剩余票数=2
//窗口 Thread-2 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-2 售出一张票 剩余票数=0
//售票结束
//窗口 Thread-1 售出一张票 剩余票数=-1
//售票结束
//窗口 Thread-0 售出一张票 剩余票数=0
//售票结束

//出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短

出现了问题:票数超卖,多个线程同时进入循环,都检测到了最后的一个资源,所以都卖了出去,出现了超卖现象。

5、线程终止:

(1)基本说明:

1)当线程完成任务后,会自动退出

2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

(2)应用案例:

 6、线程常用方法:

 

补:中断可以类比闹钟来理解

//解释join:线程插队
package threaduse;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
        t3.start();
        
        for(int i=1;i<=20;i++){
            Thread.sleep(1000);
            System.out.println("主线程(小弟)吃了 "+i+" 包子");
            if(i==5){
                System.out.println("主线程(小弟)让了子线程(老大)先吃");
                t3.join();//这里相当于让t2线程先执行完毕
                System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
            }
        }
    }
}
class T3 extends Thread{
    public void run(){
        for(int i=1;i<=20;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了 " +i+" 包子");
        }
    }
}
//主线程(小弟)吃了 1 包子
//子线程(老大)吃了 1 包子
//主线程(小弟)吃了 2 包子
//子线程(老大)吃了 2 包子
//子线程(老大)吃了 3 包子
//主线程(小弟)吃了 3 包子
//子线程(老大)吃了 4 包子
//主线程(小弟)吃了 4 包子
//子线程(老大)吃了 5 包子
//主线程(小弟)吃了 5 包子
//主线程(小弟)让了子线程(老大)先吃
//子线程(老大)吃了 6 包子
//子线程(老大)吃了 7 包子
//子线程(老大)吃了 8 包子
//子线程(老大)吃了 9 包子
//子线程(老大)吃了 10 包子
//子线程(老大)吃了 11 包子
//子线程(老大)吃了 12 包子
//子线程(老大)吃了 13 包子
//子线程(老大)吃了 14 包子
//子线程(老大)吃了 15 包子
//子线程(老大)吃了 16 包子
//子线程(老大)吃了 17 包子
//子线程(老大)吃了 18 包子
//子线程(老大)吃了 19 包子
//子线程(老大)吃了 20 包子
//子线程(老大)吃完了,主线程(小弟)接着吃...
//主线程(小弟)吃了 6 包子
//主线程(小弟)吃了 7 包子
//主线程(小弟)吃了 8 包子
//主线程(小弟)吃了 9 包子
//主线程(小弟)吃了 10 包子
//主线程(小弟)吃了 11 包子
//主线程(小弟)吃了 12 包子
//主线程(小弟)吃了 13 包子
//主线程(小弟)吃了 14 包子
//主线程(小弟)吃了 15 包子
//主线程(小弟)吃了 16 包子
//主线程(小弟)吃了 17 包子
//主线程(小弟)吃了 18 包子
//主线程(小弟)吃了 19 包子
//主线程(小弟)吃了 20 包子
//解释yeild:礼让,不一定成功
package threaduse;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
        t3.start();

        for(int i=1;i<=20;i++){
            Thread.sleep(1000);
            System.out.println("主线程(小弟)吃了 "+i+" 包子");
            if(i==5){
                System.out.println("主线程(小弟)让了子线程(老大)先吃");
                Thread.yield();//礼让,不一定成功...
                System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
            }
        }
    }
}
class T3 extends Thread{
    public void run(){
        for(int i=1;i<=20;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了 " +i+" 包子");
        }
    }
}
//主线程(小弟)吃了 1 包子
//子线程(老大)吃了 1 包子
//子线程(老大)吃了 2 包子
//主线程(小弟)吃了 2 包子
//子线程(老大)吃了 3 包子
//主线程(小弟)吃了 3 包子
//子线程(老大)吃了 4 包子
//主线程(小弟)吃了 4 包子
//主线程(小弟)吃了 5 包子
//主线程(小弟)让了子线程(老大)先吃
//子线程(老大)吃了 5 包子
//子线程(老大)吃完了,主线程(小弟)接着吃...
//主线程(小弟)吃了 6 包子
//子线程(老大)吃了 6 包子
//子线程(老大)吃了 7 包子
//主线程(小弟)吃了 7 包子
//主线程(小弟)吃了 8 包子
//子线程(老大)吃了 8 包子
//子线程(老大)吃了 9 包子
//主线程(小弟)吃了 9 包子
//主线程(小弟)吃了 10 包子
//子线程(老大)吃了 10 包子
//主线程(小弟)吃了 11 包子
//子线程(老大)吃了 11 包子
//主线程(小弟)吃了 12 包子
//子线程(老大)吃了 12 包子
//主线程(小弟)吃了 13 包子
//子线程(老大)吃了 13 包子
//主线程(小弟)吃了 14 包子
//子线程(老大)吃了 14 包子
//主线程(小弟)吃了 15 包子
//子线程(老大)吃了 15 包子
//主线程(小弟)吃了 16 包子
//子线程(老大)吃了 16 包子
//主线程(小弟)吃了 17 包子
//子线程(老大)吃了 17 包子
//主线程(小弟)吃了 18 包子
//子线程(老大)吃了 18 包子
//主线程(小弟)吃了 19 包子
//子线程(老大)吃了 19 包子
//主线程(小弟)吃了 20 包子
//子线程(老大)吃了 20 包子

7、练习题:

 //我的代码

package threaduse;

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        T4 t4 = new T4();
        Thread thread = new Thread(t4);
        for(int i=1;i<=10;i++){
            System.out.println("hi "+i);
            if (i == 5) {
                thread.start();
                thread.join();
            }
            Thread.sleep(1000);
        }
        System.out.println("主线程结束...");
    }
}
class T4 implements Runnable{
    public void run(){
            for(int i=1;i<=10;i++){
                System.out.println("hello"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("子线程结束...");
    }
}
//hi 1
//hi 2
//hi 3
//hi 4
//hi 5
//hello1
//hello2
//hello3
//hello4
//hello5
//hello6
//hello7
//hello8
//hello9
//hello10
//子线程结束...
//hi 6
//hi 7
//hi 8
//hi 9
//hi 10
//主线程结束...

8、用户线程和守护线程:

(1)用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

(2)守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

(3)常见的守护线程:垃圾回收机制

package threaduse;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果我们希望当main线程结束后,子线程自动结束
        //只需将子线程设置守线程即可
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for(int i=1;i<=10;i++){
            System.out.println("宝强在辛苦地工作...");
            Thread.sleep(1000);
        }
    }
}
class MyDaemonThread extends Thread{
    public void run(){
        for(;;){//无限循环
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆居心叵测聊天,哈哈哈~~~");
        }
    }
}
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~

9、线程的生命周期

 

 ·如上为官方的文档,所以线程一共有6种状态

package threaduse;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T5 t5 = new T5();
        System.out.println(t5.getName()+" 状态"+ t5.getState());
        t5.start();
        while (Thread.State.TERMINATED != t5.getState()) {
            System.out.println(t5.getName()+" 状态"+ t5.getState());
            Thread.sleep(500);
        }
        System.out.println(t5.getName()+" 状态"+ t5.getState());
    }
}
class T5 extends Thread{
    public void run(){
        while (true) {
            for(int i=0;i<10;i++){
                System.out.println("hi "+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }

    }
}
//Thread-0 状态NEW
//Thread-0 状态RUNNABLE
//hi 0
//Thread-0 状态TIMED_WAITING
//hi 1
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 2
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 3
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 4
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 5
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 6
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 7
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 8
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 9
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TERMINATED

10、线程的同步:

(1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性

(2)或者理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

(3)

(4)解决售票超卖问题:

//使用Runnable
package ticket;

public class SellTicket {
    public static void main(String[] args) throws InterruptedException {
        SellTicket02 sellTicket = new SellTicket02();
        //三个售票窗口
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
    }
}
//使用Thread
class SellTicket02 implements Runnable{
    private static int ticketNum=100;
    private boolean loop=true;

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行sell方法
        if (ticketNum <= 0) {
            System.out.println("售票结束");
            loop=false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
                +" 剩余票数="+(--ticketNum));
    }

    public void run(){
        while(loop){
            sell();
        }
    }
}
//窗口 Thread-0 售出一张票 剩余票数=99
//窗口 Thread-0 售出一张票 剩余票数=98
//窗口 Thread-0 售出一张票 剩余票数=97
//窗口 Thread-0 售出一张票 剩余票数=96
//窗口 Thread-0 售出一张票 剩余票数=95
//窗口 Thread-0 售出一张票 剩余票数=94
//窗口 Thread-0 售出一张票 剩余票数=93
//窗口 Thread-0 售出一张票 剩余票数=92
//窗口 Thread-0 售出一张票 剩余票数=91
//窗口 Thread-0 售出一张票 剩余票数=90
//窗口 Thread-0 售出一张票 剩余票数=89
//窗口 Thread-2 售出一张票 剩余票数=88
//窗口 Thread-1 售出一张票 剩余票数=87
//窗口 Thread-1 售出一张票 剩余票数=86
//窗口 Thread-1 售出一张票 剩余票数=85
//窗口 Thread-2 售出一张票 剩余票数=84
//窗口 Thread-2 售出一张票 剩余票数=83
//窗口 Thread-2 售出一张票 剩余票数=82
//窗口 Thread-2 售出一张票 剩余票数=81
//窗口 Thread-2 售出一张票 剩余票数=80
//窗口 Thread-2 售出一张票 剩余票数=79
//窗口 Thread-2 售出一张票 剩余票数=78
//窗口 Thread-2 售出一张票 剩余票数=77
//窗口 Thread-2 售出一张票 剩余票数=76
//窗口 Thread-2 售出一张票 剩余票数=75
//窗口 Thread-0 售出一张票 剩余票数=74
//窗口 Thread-0 售出一张票 剩余票数=73
//窗口 Thread-0 售出一张票 剩余票数=72
//窗口 Thread-0 售出一张票 剩余票数=71
//窗口 Thread-0 售出一张票 剩余票数=70
//窗口 Thread-0 售出一张票 剩余票数=69
//窗口 Thread-0 售出一张票 剩余票数=68
//窗口 Thread-0 售出一张票 剩余票数=67
//窗口 Thread-0 售出一张票 剩余票数=66
//窗口 Thread-0 售出一张票 剩余票数=65
//窗口 Thread-0 售出一张票 剩余票数=64
//窗口 Thread-0 售出一张票 剩余票数=63
//窗口 Thread-0 售出一张票 剩余票数=62
//窗口 Thread-0 售出一张票 剩余票数=61
//窗口 Thread-0 售出一张票 剩余票数=60
//窗口 Thread-0 售出一张票 剩余票数=59
//窗口 Thread-0 售出一张票 剩余票数=58
//窗口 Thread-2 售出一张票 剩余票数=57
//窗口 Thread-2 售出一张票 剩余票数=56
//窗口 Thread-2 售出一张票 剩余票数=55
//窗口 Thread-2 售出一张票 剩余票数=54
//窗口 Thread-1 售出一张票 剩余票数=53
//窗口 Thread-2 售出一张票 剩余票数=52
//窗口 Thread-2 售出一张票 剩余票数=51
//窗口 Thread-2 售出一张票 剩余票数=50
//窗口 Thread-2 售出一张票 剩余票数=49
//窗口 Thread-2 售出一张票 剩余票数=48
//窗口 Thread-2 售出一张票 剩余票数=47
//窗口 Thread-2 售出一张票 剩余票数=46
//窗口 Thread-0 售出一张票 剩余票数=45
//窗口 Thread-0 售出一张票 剩余票数=44
//窗口 Thread-0 售出一张票 剩余票数=43
//窗口 Thread-2 售出一张票 剩余票数=42
//窗口 Thread-2 售出一张票 剩余票数=41
//窗口 Thread-2 售出一张票 剩余票数=40
//窗口 Thread-2 售出一张票 剩余票数=39
//窗口 Thread-1 售出一张票 剩余票数=38
//窗口 Thread-1 售出一张票 剩余票数=37
//窗口 Thread-2 售出一张票 剩余票数=36
//窗口 Thread-2 售出一张票 剩余票数=35
//窗口 Thread-2 售出一张票 剩余票数=34
//窗口 Thread-2 售出一张票 剩余票数=33
//窗口 Thread-2 售出一张票 剩余票数=32
//窗口 Thread-2 售出一张票 剩余票数=31
//窗口 Thread-2 售出一张票 剩余票数=30
//窗口 Thread-2 售出一张票 剩余票数=29
//窗口 Thread-2 售出一张票 剩余票数=28
//窗口 Thread-0 售出一张票 剩余票数=27
//窗口 Thread-0 售出一张票 剩余票数=26
//窗口 Thread-0 售出一张票 剩余票数=25
//窗口 Thread-0 售出一张票 剩余票数=24
//窗口 Thread-0 售出一张票 剩余票数=23
//窗口 Thread-0 售出一张票 剩余票数=22
//窗口 Thread-2 售出一张票 剩余票数=21
//窗口 Thread-2 售出一张票 剩余票数=20
//窗口 Thread-2 售出一张票 剩余票数=19
//窗口 Thread-2 售出一张票 剩余票数=18
//窗口 Thread-1 售出一张票 剩余票数=17
//窗口 Thread-2 售出一张票 剩余票数=16
//窗口 Thread-2 售出一张票 剩余票数=15
//窗口 Thread-2 售出一张票 剩余票数=14
//窗口 Thread-0 售出一张票 剩余票数=13
//窗口 Thread-2 售出一张票 剩余票数=12
//窗口 Thread-2 售出一张票 剩余票数=11
//窗口 Thread-1 售出一张票 剩余票数=10
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-1 售出一张票 剩余票数=8
//窗口 Thread-1 售出一张票 剩余票数=7
//窗口 Thread-1 售出一张票 剩余票数=6
//窗口 Thread-2 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=4
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-2 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-0 售出一张票 剩余票数=0
//售票结束
//售票结束
//售票结束

11、互斥锁:

 (1)基本介绍:
1)Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3)关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4)同步的局限性:导致程序的执行效率要降低
5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6)同步方法(静态的)的锁为当前类本身。

class SellTicket02 implements Runnable {
    //...
    //1、public synchronized void sell(){}就是一个同步方法
    public synchronized void sell01() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
        if (ticketNum <= 0) {
            System.out.println("售票结束");
            loop = false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + " 剩余票数=" + (--ticketNum));
    }

    //2、这里锁在this对象
    public void sell02() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到

        synchronized (this) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
        }
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + " 剩余票数=" + (--ticketNum));
    }

    //3、也可以在代码块上写synchronized,同步代码块
    Object object=new Object();
    public void sell03() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到

        synchronized (object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
        }
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + " 剩余票数=" + (--ticketNum));
    }

    //静态的同步方法的锁为当前类本身
    //1、
    public synchronized static void sell04() {}//锁是加在SellTicket02.class

    //2、
    public static void sell05(){
        synchronized(SellTicket02.class){
            System.out.println("sell05");
        }
    }
    //...
}

(2) 注意事项和细节
1)同步方法如果没有使用static修饰:默认锁对象为this

2)如果方法使用static修饰,默认锁对象:当前类.class

3)实现的落地步骤:
  需要先分析上锁的代码
  选择同步代码块或同步方法
  要求多个线程的锁对象为同一个即可!

12、线程的死锁

(1)基本介绍:

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生

package threaduse;

public class DeadLock {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}
class DeadLockDemo extends Thread{
    static Object o1=new Object();//保证多线程,共享一个对象,这里使用static
    static Object o2=new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }
    public void run(){
        //下面业务逻辑分析:
        //1、如果flag为T,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
        //2、如果线程A得不到o2对象锁,就会Blocked
        //3、如果flag为T,线程B就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
        //4、如果线程B得不到o2对象锁,就会Blocked
        if (flag) {
            synchronized (o1){//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName()+"进入1");
                synchronized (o2){//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName()+"进入2");
                }
            }
        }else{
            synchronized (o2){//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName()+"进入3");
                synchronized (o1){//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName()+"进入4");
                }
            }
        }
        //我的理解:
        //有线程走if,拿到了o1的锁,等待着o2的锁,没拿到就没法释放资源,但同时有线程走else,拿到了o2的锁,等待着o1的锁,所以它们永远不可能等到对方
    }
}
//输出:(卡住了)
//A线程进入1
//B线程进入3

13、释放锁:

下面操作会释放锁:

(1)当前线程的同步方法、同步代码块执行结束

上厕所,完事出来

(2)当前线程的同步代码块、同步方法中遇到break, return

没有正常地完事,经理叫他修改bug,不得已出来

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

没有正常地完事,发现忘带纸,不得已出来

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

没有正常完事,觉得需要酝酿下,所以出来等会再进去

 下面操作不会释放锁:

(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行,不会释放锁

上厕所,太困了,在坑位上眯了一会

(2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

✿✿✿JavaScript --- BOM、DOM对象

目 录 一、BOM浏览器对象模型 1.Window窗口对象 (1)与弹出有关的方法 (2)与定时器有关的方法 (3)与打开关闭有关的方法 (4) 获取其他对象的属性 2.Location地址栏对象 3.History历史记录对象 二、DOM文档对象模型 1.Document文档对象 (1)获取Element对象 (2)创建…

如何理解CRC循环冗余校验——图解CRC算法模型和C语言实现

如何理解CRC循环冗余校验 循环冗余校验&#xff08;英语&#xff1a;Cyclic redundancy check&#xff0c;通称“CRC”&#xff09;是一种产生定长校验码的算法&#xff0c;主要用来检测或校验数据传输或者保存后可能出现的错误。 它真的太常见了&#xff0c;上至应用软件通信…

Qt QCustomPlot 点状网格线实现和曲线坐标点拾取

Qt QCustomPlot 点状网格线实现和曲线坐标点拾取 文章目录Qt QCustomPlot 点状网格线实现和曲线坐标点拾取摘要我想实现的效果点阵的实现第一版本&#xff0c;使用QPen Style第二版本&#xff0c;通过设置背景第三版本&#xff0c;回到QPen Style取曲线上的点关键字&#xff1a…

[附源码]Python计算机毕业设计Django电影推荐网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【附源码】计算机毕业设计JAVA助农脱贫系统

【附源码】计算机毕业设计JAVA助农脱贫系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…

cpu设计和实现(异常和中断)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 异常和中断几乎是cpu最重要的特性。而异常和中断&#xff0c;本质上其实是一回事。很多熟悉mips的朋友&#xff0c;应该都听过这么一个词&#xff…

算法竞赛入门【码蹄集进阶塔335题】(MT2291-2295)

算法竞赛入门【码蹄集进阶塔335题】(MT2291-2295&#xff09; 文章目录算法竞赛入门【码蹄集进阶塔335题】(MT2291-2295&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f;目录1. MT2291 饿饿!饭饭!2. MT2292 甜甜花的研究3. MT2293 赌…

【2013NOIP普及组】T4. 车站分级 试题解析

【2013NOIP普及组】T4. 车站分级 试题解析 时间限制: 1000 ms 内存限制: 131072 KB 【题目描述】 一条单向的铁路线上,依次有编号为 1,2,…,n 的 n 个火车站。每个火车站都有一个级别,最低为 1 级。现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟…

护眼灯真的有用吗?2022双十二选哪个牌子的护眼台灯好

护眼灯对保护眼睛是真的有用&#xff0c;它不是那种如医学奇迹般的治疗眼睛疾病&#xff0c;或者降低近视度数等等&#xff0c;这样的伪科学只会让人觉得是智商税。护眼灯的作用原理很简单也很有效&#xff0c;即通过各种方法提高光线的舒适度&#xff0c;使人眼在晚上长时间工…

厦门市会展局携手美创:以数据为核心的安全建设守护“云上会展”

新冠疫情影响下&#xff0c;会展业与云计算、大数据、物联网等数字技术加速融合&#xff0c;“云上会展”成为新趋势。然而风口之下&#xff0c;高价值的展会敏感数据无时不面临着被窃取、攻击的风险。因此&#xff0c;成熟配套的数据安全能力体系建设&#xff0c;也是会展业创…

Monaco Editor教程(二十):在编辑器的某个特定位置插入自定义的dom内容,图片,表单,表格,视频

前言 哇咔咔&#xff0c;这是我的第20篇Monaco教程&#xff0c;写完这一篇会暂时休息一段时间&#xff0c;练练字&#xff0c;存存稿&#xff0c;读读书&#xff0c;顺便修修文章。 目前全网成系统的monaco中文专栏应该只有我这一个&#xff0c;欢迎评论区打脸。自结束了GitLa…

面试题------线程池的拒绝策略

面试题------线程池的拒绝策略 线程池有7个核心参数 1.核心线程数 2.最大线程数 3.非核心线程存活时间 4.存活时间的单位 5.工作队列 6.线程自定义的一些配置 7.拒绝策略&#xff08;当达到最大线程数、且工作队列也满了会执行拒绝策略&#xff09; public ThreadPoolExecutor…

马上2023年了,学一下gradle(Gradle)安装及配置

Gradle学习 例如&#xff1a;相信已经很多公司在用了&#xff0c;但是小伙伴对此还是很模糊 文章目录Gradle学习Gradle一、Gradle介绍&#xff1f;二、常见的项目构建工具gradle安装1.下载2. 配置&#xff08;环境变量&#xff09;2.1打开环境变量2.2**新建环境变量**2.3在Pat…

【C++】STL—vector的常用接口

文章目录前言一、vector介绍二、vector的使用1. vector的定义2. vector的遍历2.1.operator[ ]2.2.迭代器2.3.范围for3. vector的空间增长问题3.1.size和capacity3.2.max_size和empty3.3.reserve3.4.resize3.5.Shrink to fit4. vector的增删查改4.1.push_back和pop_backinsert和…

Vue 3的高颜值UI组件库

Vue 3.0 已经发布两年多的时间&#xff0c;今年 2 月 Vue 3.0 也正式成为新的默认版本。今天就来分享 7 个适用于 Vue 3 的高颜值 UI 组件库&#xff01; Element Plus Element Plus 是一套由饿了么开源出品的为开发者、设计师和产品经理准备的基于 Vue 3.0 的组件库。Elemen…

【能源管理】制造行业中汽车厂房综合能效管理平台应用分析

安科瑞 李亚俊 平台概述 壹捌柒贰壹零玖捌柒伍柒AcrelEMS-EV汽车厂房能效管理平台集变电站综合自动化、电力监控、电气安全、电能质量分析及治理、能耗管理、能效分析、照明控制、充电桩运营管理、设备运维于一体&#xff0c;为建立可靠、安全的工厂能源管理体系提供数据支持…

【Flink】处理迟到元素(续)、自定义水位线和多流的合并与合流

文章目录一 处理迟到元素1 处理策略&#xff08;3&#xff09;使用迟到元素更新窗口计算结果a 代码编写b 测试二 自定义水位线1 产生水位线的接口2 自定义水位线的产生逻辑三 多流的合流与分流1 union算子2 水位线传递规则&#xff08;1&#xff09; 分流a 代码编写b 测试&…

virtio-net 实现机制【二】(图文并茂)

4. virio-net前端驱动分析 4.1 重要数据结构 4.1.1 send_queue结构 send_queue结构是对virtqueue的封装&#xff0c;是virtio-net的发送队列&#xff0c;即数据流向从前端驱动&#xff08;Guest&#xff09;到后端驱动&#xff08;Host&#xff09; 4.1.2 receive_queue结构…

【PlasticSCM Could Edition】新版本云托管浅试2

【PlasticSCM Could Edition】新版本云托管浅试2首先修复更改托管提一嘴首先 建议还是使用 PlasticHub&#xff0c;不要去用 PlasticSCM Cloud 原因&#xff1a; 由于比较新&#xff0c;伴随着的就是 —— 太多 bigs 了&#xff01;而且不知道怎么去改。 当时我创建了一个…

数据结构复习

期末的一套模拟题&#xff0c;仅供参考&#xff0c;切莫作为期末考试依据&#xff01;&#xff01;&#xff01; 选择题 数组A[1..5,1..6]每个元素占5个单元&#xff0c;将其按行优先次序存储在起始地址为1000的连续的内存单元中&#xff0c;则元素A[5,5]的地址为&#xff1a;…