阶段二-Day04-进程和线程

一.进程 

1.1进程的概念

所谓进程计算机中正在运行的一个应用程序就是一个进程。

1.2 进程的特点

1、每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(生命周期)

2、程序是静态的,进程是动态的

3、进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。

二.线程

线程包含在进程中,线程是进程的一条执行路径

所以线程与线程之间是相互独立的,相互不影响

三.多线程

当进程中仅包含 1 个执行程序指令的线程时,这样的进程称为单线程该线程又称“主线程”

各个线程并行处理各自的任务,这就相当于多线程处理

3.1 多线程的优点

提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

提高计算机系统CPU的利用率

改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

3.2 多线程中的相关概念

并发:

并发指在一段时间内宏观上同时(交替执行的)去处理多个任务。  

一个cpu工作

单核CPU,在一个时间单元内,只能执行一个线程的任务。

并行:

同一个时刻,多个任务确实真的同时运行。 在同一时刻,多个人同时的做多件事情

多个cpu工作

多核CPU

四.线程的创建

 Thread对象的创建(线程的创建)

  • 在Java中创建线程就是创建Thread对象,常用构造方法有:
  1. Thread()
  2. Thread(Runnable target)  

4.线程池 

四种方式创建线程

main本身就是一个线程,是主线程

都要用到Thread下的start()方法

4.1 继承Thread类的方式

public class SubThread extends Thread{
    String threadName;
    public SubThread(String threadName){
        this.threadName = threadName;
    }

    @Override
    public void run() {
        //打印五个数
        for (int i = 0; i < 5; i++) {
            System.out.println(this.threadName+"-->"+i);
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        Thread t1 = new SubThread("线程1");
        Thread t2 = new SubThread("线程2");

        //使用start方法开启线程,而不是run方法
        //线程是交替执行的
        t1.start();
        t2.start();
    }
}

4.2 实现Runnable接口

public class SubThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("接口1------->"+i);
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        //调用Thread(Runnable)构造方法创建线程对象,
        // 形参是Runnable接口, 实参传递接口实现类对象
        Thread t1 = new Thread( new SubThread());
        //也可以写成
        Runnable runnable = new SubThread();
        Thread thread = new Thread(runnable);
        //开启线程
        t1.start();

        //还有一种方式,使用匿名内部类,免去了创建类的过程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++){
                    System.out.println("接口2------->"+i);
                }
            }
        });

        t2.start();

        //调用Thread(Runnable)构造方法创建线程对象,
        // 形参Runnable接口只需要重写一个抽象方法,
        // 实参传递Lambda表达式. Lambda本身具有懒加载特性
        // 懒加载,前面两个加载完它才加载,一般不使用
        Thread t3 = new Thread( () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("lambda ==> " + i );
            }
        });
        t3.start();
    }
}

实现Runnable接口比继承Thread类所具有的优势

1.避免了单继承的局限性

2.多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

4.3 实现Callable接口

public class SubThread implements Callable<Integer> {
    //这里重写的是call()方法
    //该接口可以是线程运行的方法有返回值
    //主线程想要获取子线程的数据,就用Callable
    @Override
    public Integer call() throws Exception {
        System.out.println("call中的");
        //生成随机数
        int num = new Random().nextInt(100);
        System.out.println("在子线程中得出一个结果: " + num);
        return num;
    }
}
public class Test {
    public static void main(String[] args) {
        Callable callable = new SubThread();
        /*Thread thread = new Thread(callable);*/
        //Thread 无法直接获取Callable对象,还是要接收Runnable接口
        //要把Callable变为Runnable
        //使用FutureTask类
        FutureTask futureTask = new FutureTask(callable);
        /*调用Thread( Runnable ) 构造方法创建Thread对象, 借助FutureTask类
         FutureTask类实现了RunnableFuture接口, 而RunnableFuture接口继承了Runnable接口,
         所以FutureTask类是Runnable接口的实现类, 在调用Thread(Runnable)构造方法时,
         实参可以传递FutureTask对象,调用FutureTask(Callable)构造方法创建对象,
         形参是Callable接口,实参传递接口实现类对象. 通过泛型指定任务返回值类型为Integer
        * */
        Thread thread = new Thread(futureTask);

        thread.start();
    }
}

4.4 线程池(后面会讲到)

好处
  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理
  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止

4.5 多线程创建方式的区别

Runnable

Runnable是个接口

Thread

Thread是其实现类

本质:继承的不足(单重继承,不能实现多重继承 Cat extends Animal 如果需要重写多个方法是办不到的接口),所以说通过实现Runnable接口构建线程,可以让线程类重写方法更灵活(可实现多个接口)

Callable

与使用Runnable相比, Callable功能更强大些

1.相比run()方法,可以有返回值

2.方法可以抛出异常

3.支持泛型的返回值(需要借助FutureTask类,获取返回结果)

Future接口(了解)

1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

2.FutureTask是Futrue接口的唯一的实现类

3.FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

  • 缺点在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。

五.线程的常用方法

构造方法:

public Thread() :分配一个新的线程对象。

public Thread(String name) :分配一个指定名字的新的线程对象。

public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法

public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

其它方法:

public void run() :此线程要执行的任务在此处定义代码。

public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

public String getName() :获取当前线程名称

public void setName(String name):设置该线程名称。

public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类

public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

void join() :等待该线程终止。

void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。

void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

 5.1 获取当前线程的名字

public class Test04 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                //哪条线程执行到这个方法,此时获取的就是哪条线程的对象
            }
        };
         /* Thread t4 = new Thread(r); //创建线程时,没有在构造方法指定线程名称,就默认计数
        t4.start();*/

        //开启10个线程, 线程名称默认为Thread-0, Thread-1....
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(r);
            t.start();
        }

        //在创建线程对象时, 通过构造方法指定线程名称
        Thread t2 = new Thread(r, " t2 ");
        t2.start();

        Thread t3 = new Thread(r);
        t3.setName(" t3 ");     //t3线程名称系统先默认为Thread-10, 再重新赋值为t3
        t3.start();
    }
}

5.2  让线程休眠指定的时间

public class Test02 {
    public static void main(String[] args) {
        System.out.println("1111");
        try {
            //主线程休眠3000毫秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("2222");
    }
}
public class Test02 {
    public static void main(String[] args) {
       Thread thread = new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 5; i++) {
                   System.out.println(Thread.currentThread().getName());
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
       },"t1");
       thread.start();
    }
}

5.3 线程优先级设置

线程优先级取值范围:1~10
 所有线程优先级默认为:5

 在实际开发中, 慎重使用优先级, 设计不当,在线程竞争激烈的情况下, 可能会导致有的线程一直无法被调度, 称为线程饥饿

这个优先级也不是绝对的

public class Test03 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++){
                    System.out.println(Thread.currentThread().getPriority());
                }
            }
        };
        Thread t1 = new Thread(r);
        t1.setPriority(10);
        t1.start();
        
        Thread t2 = new Thread(r);
        t2.start();
    }
}

5.4 守护线程

某个线程在另一个线程执行完后,某个线程才陆续结束。

这个也不是绝对的

public class Test04 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + " -----------> " + i );
                }
            }
        });
        t1.setName("t1");
        t1.setDaemon(true); //把t1线程设置为守护线程

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + " ========> " + i );
                }
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

非守护线程先执行结束了,守护线程还没有执行完。

5.5  插入线程

让某个线程插入到另一个线程前执行

public class Test05 {
    public static void main(String[] args) {
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++){
                    if (i % 2 == 0){
                        System.out.println("偶数"+i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++){
                    if (i % 2 != 0){
                        System.out.println("奇数"+i);
                        if (i == 5){
                            try {
                                t2.join();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}
public class Test06 {
    static  int num = 0 ;
    public static void main(String[] args) throws InterruptedException {
        //创建线程对num自增10000次
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    num++;
                }
            }
        });
        t.start();
        //需要把t进程插入到a进程之前
        t.join();
        System.out.println( num );//a线程
    }
}

六.线程的生命周期

6.1 JDK1.5之前:5种状态

线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。

6.1.1 新建

当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。

6.1.2 就绪

当线程对象调用了start()方法之后,线程就从新建状态转为就绪状态,处于这个状态中的线程并没有开始运行,随时可以被调度

注意:

程序只能对新建状态的线程调用start(),并且只能调用一次,如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。

6.1.3  运行

如果处于就绪状态的线程获得了CPU资源时,开始执行run()方法的线程体代码,则该线程处于运行状态。每个可执行的线程只有分配的一小段时间来处理任务,当该时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。选择时会考虑优先级.

6.1.4  阻塞

当在运行过程中的线程遇到如下情况时,会让出 CPU 并临时中止自己的执行,进入阻塞状态:

1、线程调用了sleep()方法,主动放弃所占用的CPU资源;

2、线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;

3、线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify);

4、线程执行过程中,同步监视器调用了wait(time)

5、线程执行过程中,遇到了其他线程对象的加塞(join);

6、线程被调用suspend方法被挂起(已过时,因为容易发生死锁);

阻塞状态变为就绪状态

1、线程的sleep()时间到;

2、线程成功获得了同步监视器;

3、线程等到了通知(notify);

4、线程wait的时间到了

5、加塞的线程结束了;

6、被挂起的线程又被调用了resume恢复方法(已过时,因为容易发生死锁);

6.2 JDK1.5及之后:6种状态

 NEW:新建

线程刚被创建,但是并未启动。还没调用start方法。

RUNNABLE:可运行,就绪和运行都算

死亡:分了三种

BLOCKED:🔒(锁)阻塞

WAITING:无限等待

TIMED_WAITING:计时等待

TERMINATED:终止

表明此线程已经结束生命周期,终止运行。

代码获取状态:

public class SubThread extends Thread{
    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 10; i++){
                System.out.println("打印: "+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        SubThread t = new SubThread();
        System.out.println(t.getName() + "状态" + t.getState());
        t.start();
        while (Thread.State.TERMINATED != t.getState()){
            System.out.println(t.getName() + "状态" + t.getState());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(t.getName() + "状态" + t.getState());
    }
}

6.3 线程安全问题和线程同步机制

6.3.1线程安全问题

当多个线程同时操作一个共享数据时,可能会出现数据不一致的情况

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

A线程对共享数据做了修改,其它线程无法第一时间读到最新的数据,即A线程的修改对其他线程来说是不可见的。

当你取钱之后要让初始余额变为1000才能继续继续取钱,不然会出现媳妇也取钱了

6.3.2  线程同步机制

线程同步机制就是一套协调多个线程操作共享数据时,用于保证数据安全的一种机制(工具)

线程同步机制(工具)包括: , 等待/唤醒机制, volatile关键字, final关键字等(final修饰的变量一旦初始化就不能重新赋值,具有天然的线程安全性; )

锁的介绍

不管哪个线程想要访问共享数据,必须先申请锁对象

锁具有排它性, 即在某时刻锁对象最多只能 被一个线程持有, 当A线程持有锁访问共享数据, 这时B线程也想申请锁对象的话则B线程转为阻塞状态;

锁分为synchronized内置锁与Lock显示锁两种

6.3.2.1 同步机制-线程同步锁synchronized

  1. Java中任意一个对象都有一个内置锁, 所以任意一个对象都可以作为锁对象
  2. 同步代码块就是操作共享数据的代码, 也称为临界区代码
  3. 注意: 线程想要同步, 必须使用同一个锁对象; 同样,只要使用了同一个锁对象的同步代码块就可以实现同步
  4. 注意: 不仅在修改数据时需要同步, 在读取共享数据时也需要同步

使用格式:

同步代码块:

synchronized(同步锁){
	需要同步操作的代码
}

synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。

同步方法:

public synchronized void method(){
   可能会产生线程安全问题的代码
}

synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

同步代码块:

public class Test {
    static  int num = 0 ;

    public static void main(String[] args) throws InterruptedException {
        //定义Runnable任务对num自增10000次
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (this){
                        num++;
                    }
                }
            }
        };
        //开启两个 线程都执行r任务
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();

        //在main线程中等t1与t2都结束后,打印num的值
        t1.join();
        t2.join();
        System.out.println( num );
    }
}

同步方法:

public class Test02 {
    static int num = 0;//定义静态变量作为共享数据
    private static final Object OBJ = new Object();//定义变量对象作为锁对象2
    public static void main(String[] args) throws InterruptedException {
        Test02 obj = new Test02();
        //定义Runnable任务对num自增10000次
        Runnable r = new Runnable() {
            @Override
            //或者把这个方法给上锁
            public /*synchronized*/  void run() {
                for (int i = 0; i < 10000; i++) {
                    //同步块的其他上锁方法
                    //synchronized ( OBJ ) 使用常量对象作为锁对象
                    //synchronized ( Test02.class ) 使用当前类的字节码作为锁对象, 有人称之为类锁
                    //synchronized (obj) 使用自定义对象作为锁对象
                    gongXiangData();
                    //}
                }
            }
        };
        //开启两个 线程都执行r任务
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        //在main线程中等t1与t2都结束后,打印num的值
        t1.join();
        t2.join();
        System.out.println(num);
    }
    private synchronized static void gongXiangData() {
        num++;
    }
}

说明:

对于同步代码块来说,同步锁对象是由程序员手动指定的(很多时候也是指定为this或类名.class),但是对于同步方法来说,同步锁对象只能是默认的:

  • 静态方法:当前类的Class对象(类名.class)
  • 非静态方法:this

面对可能存在线程安全问题的程序来说:

1、如何找问题,即代码是否存在线程安全?(非常重要)

  1. 明确哪些代码是多线程运行的代码
  2. 明确多个线程是否有共享数据
  3. 明确多线程运行代码中是否有多条语句操作共享数据

2、如何解决呢?(非常重要)

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中。

3、切记:

(1)范围太小:不能解决安全问题

(2)范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。

public class Test09 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Test09().mm();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Test09().mm2();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                InnerClass.mm3();
            }
        }).start();
    }
    //只要是同一个对象锁,就会解决线程安全问题
    //在实例方法中定义一个同步代码块,
    public  void mm(){
        synchronized ( Test09.class ) {//使用Test09类锁
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " -------> " + i );
            }
        }
    }
    //在静态方法中定义同步代码块
    public static void mm2(){
        synchronized ( Test09.class ) {//使用Test09类锁
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " -------> " + i );
            }
        }
    }
    //在其他类的方法中定义同步代码块
    static class  InnerClass{
        public static void mm3(){
            synchronized ( Test09.class ) {//使用Test09类锁
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + " -------> " + i );
                }
            }
        }
    }
}

必须保证同一个🔒对象

6.3.2.2  同步机制-线程同步锁-lock

class A{
    //1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
private static final Lock lock = new ReentrantLock();
	public void m(){
        //2. 调动lock(),实现需共享的代码的锁定
		lock.lock();
		try{
			//保证线程安全的代码;
		}
		finally{
            //3. 调用unlock(),释放共享代码的锁定
			lock.unlock();  
		}
	}
}

注意:如果同步代码有异常,要将unlock()写入finally语句块。保证unlock的执行

public class MyThread extends Thread{
    static int ticket = 0;
    static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        //1.循环模拟售票过程
        //可以使用lock锁代替synchronized
        while (true) {
            /* synchronized (MyRunnable.class) {*/
            try {
                lock.lock();
                //2.判断共享数据是否达到了末尾
                if (ticket == 100) {//如果到了末尾
                    //lock.unlock();
                    break;
                } else {
                    //3.如果没有到了末尾
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;//票自增,模拟买票
                    System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
                }
            } finally {
                lock.unlock();
            }
            /* }*/
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
synchronized与Lock的对比

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域、遇到异常等自动解锁

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。

6.3.2.3 线程安全锁的互斥性

public class Test01 {
    public static void main(String[] args) {
        Test01 obj = new Test01();

        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.m1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.m2();
            }
        }).start();
    
    }

    //默认是this对象的锁
    //静态方法也可以同步
    private synchronized void m2() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " ========> " + i);
        }
    }


    private void m1() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
}

静态方法也可以使用

小练习:


public class MyData {
    private String name;
    private String pwd;

    public MyData(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    //这里如果不做锁的处理,会造成读脏数据的问题
    public synchronized void set(String name, String pwd) {
        this.name = name;
        //通过睡眠模拟程序执行需要一定时间
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.pwd = pwd;
    }

    //这里如果不做锁的处理,会造成读脏数据的问题
    public synchronized void shoInfo(){
        System.out.println( "name=" + name + ",pwd="+ pwd);
    }
}
public class Test {
    public static void main(String[] args) {
        MyData myData = new MyData("张三","123");

        new Thread(new Runnable() {
            @Override
            public void run() {
                myData.set("李四","456");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                myData.shoInfo();
            }
        }).start();
    }
}

六.死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

​​​​​​​

public class MyThread extends Thread {
    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        while(true){
            if ("线程a".equals(getName())){
                synchronized (objA){
                    System.out.println( "线程a 获得了A锁, 完成任务还需要拿B锁");
                    synchronized (objB){
                        System.out.println("线程a 获得了B锁, 顺利完成任务");
                    }
                }
            } else{
                synchronized (objB){
                    System.out.println( "线程b 获得了B锁, 完成任务还需要拿A锁");
                    synchronized (objA){
                        System.out.println("线程b 获得了A锁, 顺利完成任务");
                    }
                }
            }
        }

    }
}

public class Test {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();

        m1.setName("线程a");
        m2.setName("线程b");

        m1.start();
        m2.start();
    }
}

诱发死锁的原因

互斥条件

占用且等待

不可抢夺(或不可抢占)

循环等待

解决死锁

死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。

针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。

针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值