SE API第八天:Java 线程——>线程/进程、多线程、API、守护线程、多线程并发问题、互斥/死锁

一、线程:

1、关于进程和线程的介绍:

01.什么是进程(内存开销大)?

1、关于进程和线程的介绍:
01.什么是进程(内存开销大)(1)操作系统中运行的一个任务(一个应用程序运行在一个进程中)
   (2)是一块包含了某些资源的内存区域。操作系统利用进程把他的工作划分为一些功能单元。
   (3)进程中包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被他所包含的线程访问。
   总结:①当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。
        ②而【一个进程又是由多个线程所组成的】
——————————————————————————————————————————————————————
   (4)程序:数据与指令的集合,而且程序是静态的
   (5)进程:运行中的程序,给程序加入了时间的概念,不同时间进程有不同的状态,进程是动态的,代表OS中正在运行的程序;
           进程有独立性,动态性,并发性
   (6)并行:相对来说资源比较充足,多个CPU同时并发处理多个不同的进程
   (7)串行:相对来说资源不太充足,多个资源同时抢占公共资源,比如CPU    

02.什么是线程(内存开销小)?线程5种状态

02.什么是线程(内存开销小)?————>线程分为:线程和多线程
   (1)线程:  一个顺序的单一的程序执行流程就是一个线程。通俗说———>代码一句一句的有先后顺序的执行。
      ①线程是OS能够进行运算调度的最小单位;
      ②一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程称作单线程程序
      ③注意:每个线程也有自己独立的内存空间,当然也有一部分共享区域用来保存共享的数据
      
   (2)多线程: 多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。

03.线程的5种状态:

在这里插入图片描述

03.一个线程完整的生命周期有5种状态:——————>线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
   ①新建状态: 使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。
             它保持这个状态直到程序启动(start)这个线程。
                
   ②就绪状态: Runnable。当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的
             线程处于就绪队列中,要等待JVM里线程调度器的调度。
                
   ③运行状态: Running。如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。
             处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
                
   ④阻塞状态:Sleep Block。——————处于Runnable状态和Running状态之间。
            如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,
            该线程就从运行状态进入阻塞状态。在睡眠时间已到或者获得设备资源后可以重新进入就绪状态。可以分为三种:
             1)等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
             2)同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)3)其他阻塞:通过调用线程的 sleep()join() 发出了 I/O请求时,线程就会进入到阻塞状态。
                        当sleep() 状态超时,join()等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
                          
   ⑤死亡状态:Dead。一个运行状态的线程完成run()任务或者其他终止条件发生时,该线程就切换到终止状态,进行对象垃圾回收。

在这里插入图片描述

2、多线程——>造成"感官上同时运行"的效果

01.多线程的:特点/用途/优缺点

2、多线程: 多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
01.特点:多线程是[并发执行]的。 例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
   (1)并发:多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间
  		   片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。
           当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它。
           如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程。
           所以微观上走走停停,宏观上感觉一起运行的现象成为并发运行!
              
02.多线程何时用:————用途:
   (1)当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行
   (2)一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
  
03.多线程的优点:——————>可以提高CPU的利用率。
   :在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
   
04.多线程的缺点:
   (1)线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
   (2)多线程需要协调和管理,所以需要CPU时间跟踪线程;
   (3)线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
   (4)线程太多会导致控制太复杂,最终可能造成很多Bug

02.创建线程两种方式 和 匿名内部类方式创建线程

02.创建线程有两种方式:————>01.继承Thread并重写run方法/02.implements Runnable接口单独定义线程任务的形式:
   (1)第一种:继承Thread并重写run方法——————————>例:Demo1
      1)步骤:①继承Thread并重写run方法 ②实例化线程 ③启动线程
              定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
        注:启动该线程要调用该线程的start方法,而不是run方法!!!
      2)优缺点:
         优点:结构简单,利于匿名内部类创建
         缺点:①由于java是单继承,直接继承Thread,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。
              ②定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用。
              
   (2)第二种:实现Runnable接口单独定义线程任务:—————>例:Demo2
      具体步骤:0)implements Runnable 1)创建线程任务 2)创建两条线程分别执行任务 3)启动线程
      
   (3)使用匿名内部类的创建方式完成线程的两种创建:————>例:Demo3
   注意:1)启动线程调用的是线程的start方法,不能直接调用run方法!
        2)start方法调用后线程会被纳入到线程调度器中被统一管理。
        3)当它第一次获取了调度器分配给它的CPU时间片后就会开始执行run方法。
(1)第一种:继承Thread并重写run方法
  • 定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
  • 注:启动该线程要调用该线程的start()方法,而不是run方法!!!
多线程编程实现方案一:extends Thread继承方式
1)自定义一个多线程类用来继承Thread2)重写run()里的业务【这个业务是自定义的】
3)创建线程对象【子类对象】
4)通过刚刚创建好的自定义线程类对象调用start()1:不能调用run(),因为这样调用只会把run()看作一个普通的方法,并不会以多线程的方式启动程序
     而且调用start()时,底层JVM会自动调用run(),执行我们自定义的业务
注2:我们除了可以调用默认的父类无参构造以外,还可以调用Thread(String name),给自定义
     的线程对象起名字,相当于super(name);



/** 02.(1)创建线程有两种方式:————>第一种:继承Thread并重写run方法 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        //(1)创建线程任务
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        //(2)启动线程
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("你是谁啊?");
        }
    }
}
class MyThread2 extends Thread{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("我是查水表的!");
        }
    }
}
(2)第二种:实现Runnable接口单独定义线程任务:

为了实现 Runnable,一个类只需执行一个方法调用run(),声明如下:

  • public void run()
    • 你可以重写该方法,重要的是理解的run()可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
    • 在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
多线程编程实现方案二:implements Runnable 实现方式
1)自定义一个类实现接口Runnable
2) 实现接口中唯一一个抽象方法run()
3) 创建接口实现类的对象,这个对象是作为我们的目标业务对象【因为这个自定义类中包含了我们的业务】
4)创建Thread类线程对象,调用的构造函数是Thread(Runnable target)
5)通过创建好的Thread类线程对象调用start(),以多线程的方式启动同一个业务target

注意1:由于Runnable是一个接口,无法创建对象,所以我们传入的目标业务类,也就是接口实现类的对象
注意2:只有调用start()才能把线程对象加入到就绪队列中,以多线程的方式启动,但是:
接口实现类与接口都没有这个start(),所以我们需要创建Thread类的对象来调用start(),并把接口实现类对象交给Thread(target);
大家可以理解成“抱大腿”,创建的是Thread类的线程对象,我们只需要把业务告诉Thread类的对象就好啦

使用实现Runnable接口的优势:
1)耦合性不强,没有继承,后续仍然可以继承别的类
2)采用的是实现接口的方式,后续仍然可以实现别的接口
3)可以给所有的线程对象统一业务,业务是保持一致的
4)面向接口编程,有利于我们写出更加优雅的代码


package apiday.day07.thread;
/**
 * 02.创建线程有两种方式:————>第二种:实现Runnable接口单独定义线程任务
 * (2)第二种:实现Runnable接口单独定义线程任务:
 *     步骤:0)implements Runnable 1)创建线程任务 2)创建两条线程分别执行任务 3)启动线程
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        //(1)创建线程任务
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable1();
        //(2)创建两条线程分别执行任务
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        //(3)启动线程
        t1.start();
        t2.start();
    }
}

class MyRunnable1 implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("你是谁啊?");
        }
    }
}
(3)使用匿名内部类的创建方式完成线程的两种创建:

注意:

  • 1)启动线程调用的是线程的start方法,不能直接调用run方法!
  • 2)start方法调用后线程会被纳入到线程调度器中被统一管理。
  • 3)当它第一次获取了调度器分配给它的CPU时间片后就会开始执行run方法。
package apiday.day07.thread;
/**
 * (3)使用匿名内部类的创建方式完成线程的两种创建:
 * 注意:(1)启动线程调用的是线程的start方法,不能直接调用run方法!
 *      (2)start方法调用后线程会被纳入到线程调度器中被统一管理。
 *      (3)当它第一次获取了调度器分配给它的CPU时间片后就会开始执行run方法。
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        /* 第一种:继承Thread重写run()的形式:*/
        Thread t1 = new Thread(){
            public void run() {
                for(int i=0;i<1000;i++){
                    System.out.println("你是谁啊?");
                }
            }
        };
        //(1)启动线程调用的是线程的start方法,不能直接调用run方法!
        //(2)start方法调用后线程会被纳入到线程调度器中被统一管理。
        //(3)当它第一次获取了调度器分配给它的CPU时间片后就会开始执行run方法。
        t1.start();

        /* 第二种:implements Runnable接口单独定义线程任务的形式:*/
        Runnable r2 = new Runnable(){
            public void run(){
                for(int i=0;i<1000;i++){
                    System.out.println("我是查水表的!");
                }
            }
        };
        Thread t2 = new Thread(r2);
        t2.start();
    }
}

3、线程API

01.获取运行当前这个方法的线程名字-----static Thread currentThread()

(1)Java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"。

  • Thread提供了一个方法:该方法可以获取运行这个方法的线程:
    • static Thread currentThread()
/**
package apiday.day07.thread;
/**
 * java中所有的代码都是靠线程执行的,main方法也不例外。JVM启动后会创建一条线程来执行main
 * 方法,该线程的名字叫做"main",所以通常称它为"主线程"。
 * 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,格式为"thread-x"(x是一个数)。
 *
 * Thread提供了一个静态方法:
 * static Thread currentThread()
 * 获取执行该方法的线程。
 *
 */
public class CurrentThreadDemo {//CurrentThread:当前线程
    public static void main(String[] args) {
        //用于获取执行当前方法的线程的名字:
        Thread main = Thread.currentThread();
        System.out.println("主线程:"+main);//主线程:Thread[main,5,main]

        //主线程调用dosome()方法:
        dosome();
    }

    public static void dosome(){
        //用于获取执行当前方法的线程的名字:
        Thread t = Thread.currentThread();
        System.out.println("执行dosome()方法是线程是:"+t);//执行dosome()方法是线程是:Thread[main,5,main]
    }
}

02.获取线程相关信息的方法:

1、线程提供了一套获取相关信息的方法:
(1)获取运行的方法当前线程的名字:———————>currentThread()
(2)获取线程的名字:——————————————————>getName()
(3)获取线程的唯一标识:———————————————>getId()
(4)获取线程的优先级(priority):———————>getPriority()
(5)检测当前线程是否被中断了:——————————>isInterrupted()
(6)检测是否为守护线程:———————————————>isDaemon()
(7)检测是否活着:————————————————————>isAlive()


练习题:
package apiday.thread.thread_api;
/**
 * 1、线程提供了一套获取相关信息的方法:
 * (1)获取运行的方法当前线程的名字:———————>currentThread()
 * (2)获取线程的名字:——————————————————>getName()
 * (3)获取线程的唯一标识:———————————————>getId()
 * (4)获取线程的优先级(priority):———————>getPriority()
 * (5)检测当前线程是否被中断了:——————————>isInterrupted()
 * (6)检测是否为守护线程:———————————————>isDaemon()
 * (7)检测是否活着:————————————————————>isAlive()
 */
public class Thread_Information {
    public static void main(String[] args) {
        //获取主线程的名字进行查看:
        Thread main = Thread.currentThread();
        System.out.println(main);//Thread[main,5,main]

        //获取线程的名字:
        String name = main.getName();
        System.out.println(name);//main

        //获取线程的唯一标识
        long id = main.getId();
        System.out.println(id);//1

        //获取线程的优先级(priority):
        int priority = main.getPriority();
        System.out.println(priority);//5

        //当前线程是否被中断了:
        boolean isInterrupted = main.isInterrupted();
        System.out.println("是否被中断:"+isInterrupted);//是否被中断:false

        //是否为守护线程:
        boolean isDaemon = main.isDaemon();
        System.out.println("是否为守护线程:"+isDaemon);//是否为守护线程:false

        //是否活着:
        boolean isAlive = main.isAlive();
        System.out.println("是否活着:"+isAlive);//是否活着:true
    }
}

03.线程的优先级(1-10 5为默认值)

线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程。

Java 线程线程有10个优先级,使用整数1-10表示:
1(Thread.MIN_PRIORITY)-------1为最小优先级
10(Thread.MAX_PRIORITY)----10为最高优先级NORM_PRIORITY(5)--------------5为默认值

调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.

线程的优先级(Priority):
   ——————>10个优先级,分别对应整数1-10 其中1为最低优先级,10为最高优先级。5为默认值。
(1)线程start方法调用后便纳入到了线程调度器中统一管理,此时线程只能被动被分配时间片来并发运行而不能主动获取时间片。
(2)调度器会尽可能均匀的将时间片分配给每一个线程。
(3)修改线程的优先级可以最大程度改善获取时间片的次数。
(4)优先级越高的线程获取时间片的次数越多。但要注意:线程是并发运行的,不是说谁先调用start()谁就先执行


练习题:
package apiday.thread.thread_priority;
/**
 * 线程的优先级(Priority):
 *    ——————>共10个优先级,分别对应整数1-10 其中1为最低优先级,10为最高优先级。5为默认值。
 * (1)线程start方法调用后便纳入到了线程调度器中统一管理,此时线程只能被动被分配时间片来并发运行而不能主动获取时间片。
 * (2)调度器会尽可能均匀的将时间片分配给每一个线程。
 * (3)修改线程的优先级可以最大程度改善获取时间片的次数。
 * (4)优先级越高的线程获取时间片的次数越多。但要注意:线程是并发运行的,不是说谁先调用start()谁就先执行
 */
public class PriorityDemo {
    public static void main(String[] args) {
        Thread min = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("min");
                }
            }
        };

        Thread norm = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("norm");
                }
            }
        };

        Thread max = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("max");
                }
            }
        };
        //修改线程的优先级可以最大程度改善获取时间片的次数:此处设置优先级为1:
        //min.setPriority(1);

        //设置为1/5/10三个优先级:——————优先级越高的线程获取时间片的次数越多。
        //但要注意:线程是并发运行的,不是说谁先调用start()谁就先执行
        min.setPriority(Thread.MIN_PRIORITY);
        norm.setPriority(Thread.NORM_PRIORITY);
        max.setPriority(Thread.MAX_PRIORITY);

        //调用start()启动线程:
        min.start();
        norm.start();
        max.start();
    }
}

04.线程状态之----------sleep阻塞

线程提供了一个静态方法:

  • static void sleep(long ms)
  • 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行。
(1)sleep阻塞01:-------------倒计时小程序:
1、sleep阻塞:
   Thread提供了一个静态方法:————>static void sleep():
   可以使运行该方法的线程阻塞指定毫秒;
   ;超时后线程会自动回到RUNNABLE状态等待再次获取时间片继续并发运行
   (1)程序阻塞5秒钟练习题:
 
01.倒计时程序:2种写法:
			    要求:输入一个数字,然后从该数字开始每秒递减,到0时输出时间到



练习题:
package apiday.thread.thread_api;
import java.util.Scanner;
/**
 * 1、sleep阻塞:
 * Thread提供了一个静态方法:————>static void sleep():可以使运行该方法的线程阻塞指定毫秒。
 * 超时后线程会自动回到RUNNABLE状态等待再次获取时间片继续并发运行
 * 程序阻塞5秒钟练习题:
 *
 * 01.倒计时程序:2种写法:————>要求:输入一个数字,然后从该数字开始每秒递减,到0时输出时间到
 *
 */
public class Thread_Sleep {
    public static void main(String[] args) {
        /** (1)程序阻塞5秒钟:*/
//        System.out.println("程序开始");
//        try {
//            //
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        System.out.println("程序结束了");


        /** 1、01.倒计时程序: */
        //要求:输入一个数字,然后从该数字开始每秒递减,到0时输出时间到
        /*
        //第一种写法:try写在for外面
        try {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个数字:");
            for (int num = scanner.nextInt(); num > 0; num--) { //每秒递减
                System.out.println(num);
                Thread.sleep(1000);
            }
            System.out.println("时间到!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        */

        //第2种写法:try在for里面
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入一个数字:");
        for(int num = scanner.nextInt();num>0;num--) {
            System.out.println(num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("时间到!程序结束了...");
         
    }
}
(2)sleep阻塞02:-----sleep方法必处理中断异常

sleep方法要求必须处理中断异常:InterruptedException:

  • 当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt()方法被调用时会立即中断该睡眠阻塞,并抛出中断异常(InterruptedException)。
    • //林的睡眠时间>黄的敲锤时间才会报中断异常(干嘛呢!干嘛呢!都破相了!)
    • //林的睡眠时间<黄的敲锤时间会被中断唤醒不会报中断异常(即不输出:干嘛呢!干嘛呢!都破相了!)
02.sleep方法要求必须处理中断异常(InterruptedException):当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt()方法被
    调用时会立即中断该睡眠阻塞,并且sleep()方法会抛出中断异常。
    
   (1)interrupt():打断,中断。用于立即中断一个处于调用sleep()睡眠的线程,
      并且此时sleep()方法会抛出中断异常(InterruptedException)。




练习题:
package apiday.thread.thread_api;
import java.util.Scanner;
/**
 * 1、sleep阻塞:
 * 02.sleep方法要求必须处理中断异常(InterruptedException):
 *    :当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt()方法被
 *     调用时会立即中断该睡眠阻塞,并且sleep()方法会抛出中断异常。
 *    (1)interrupt():打断,中断。用于立即中断一个处于调用sleep()睡眠的线程,
 *       并且此时sleep()方法会抛出中断异常(InterruptedException)。
 */
public class Thread_Sleep {
    public static void main(String[] args) {
        /** 1、02.sleep方法要求必须处理中断异常:*/
        /*
        当一个线程调用sleep方法处于睡眠阻塞的过程中,
        此时该线程的interrupt()方法被调用时会立即中
        断该睡眠阻塞,并且sleep()方法会抛出中断异常。
		*/
        Thread lin = new Thread() {
            public void run() {
                System.out.println("林:刚美完容,去睡一会~");
                try {
                    //林的睡眠时间>黄的敲锤时间才会报中断异常(干嘛呢!干嘛呢!都破相了!)
                    //林的睡眠时间<黄的敲锤时间会被中断唤醒不会报中断异常(即不输出:干嘛呢!干嘛呢!都破相了!)
                    Thread.sleep(5000000);
                } catch (InterruptedException e) {
                    System.out.println("干嘛呢,干嘛呢");
                }
                System.out.println("吵醒我了!");
            }
        };

        Thread huang = new Thread() {
            public void run() {
                System.out.println("黄:大锤80,小锤40,开始砸墙!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
                System.out.println("咣当!");
                System.out.println("黄:大哥!搞定了!");
                lin.interrupt();//中断lin线程的睡眠阻塞,强制唤醒
            }
        };
        lin.start();
        huang.start();
    }
}

05.线程插队join()-----可以协调线程的同步运行

join() 定义在Thread.java中。
join会阻塞线程仅仅是一个表现,而非目的。其目的是等待当前线程执行完毕后,”计算单元”与主线程汇合。即主线程与子线程汇合之意。

  • 该方法允许调用这个方法的线程在该方法所属线程上等待(阻塞),直到该方法所属线程结束后才会解除等待继续后续的工作.所以join方法可以用来协调线程的同步运行。
  • 同步运行:多个线程执行过程存在先后顺序进行。
  • 异步运行:多个线程各干各的.线程本来就是异步运行的。
Thread中,join()方法的作用:
进行线程插队,
是调用线程等待该线程执行完成后,才能继续用下运行。

如果要join正常生效,调用join方法的对象必须已经调用了start()方法且并未进入终止状态。
(调用download.join()的线程进入等待池并等待 download线程 执行完毕后才会被唤醒。
  并不影响同一时刻处在运行状态的其他线程。)


练习题:
《joinDemo.java》:
//将show线程阻塞,直到download执行完毕(图片下载完毕)
//若没有download.joi()方法,则会抛出异常:throw new RuntimeException("图片加载失败!");
若调用download.join()的线程进入等待池并等待 download线程 执行完毕后才会被唤醒。
并不影响同一时刻处在运行状态的其他线程。)

package thread;
/**
 * 线程提供的方法:join可以协调线程的同步运行
 * 多线程是并发运行,本身是一种异步运行的状态。
 * 同步运行:多个线程执行是存在了先后顺序。
 * 异步运行:各自执行各自的
 *
 * join:加入
 * finish:完成
 */
public class JoinDemo {
    static boolean isFinish = false;//Finish:完成。表示图片是否下载完成

    public static void main(String[] args) {//两个线程:download、show
        Thread download = new Thread(){
            public void run(){
                System.out.println("down:开始下载图片...");
                for(int i=0;i<=100;i++){
                    System.out.println("down:"+i+"%");
                    try{
                        Thread.sleep(50);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
                System.out.println("down:图片下载完成!");
                isFinish = true;
            }
        };


        Thread show = new Thread(){
            public void run(){
                System.out.println("show:开始显示文字");
                try{
                    Thread.sleep(2000);
                }catch(InterruptedException e){
                }
                System.out.println("show:显示文字完毕!");

                //将show线程阻塞,直到download执行完毕(图片下载完毕)
                /*Thread.sleep(?);//会报错,无法准确预估download结束时间*/
                try {
                    /*
                    当show线程调用download.join()后便进入了阻塞状态。
                    直到download线程执行完毕后才会解除阻塞。
                     */
                    download.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("show:开始显示图片...");
                if(!isFinish){
                    throw new RuntimeException("图片加载失败!");//抛出新的异常
                }
                System.out.println("图片显示完毕");
            }
        };

        download.start();
        show.start();

    }
}

4、守护线程(Daemon)------setDaemon(true)

  • (1)守护线程也称为:后台线程

    • 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
    • 守护线程的结束时机上有一点与普通线程不同, 即进程的结束.
      • 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
  • (2)建议哪种线程设置为守护线程?

    • 通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
1Java有两种Thread:——————>【用户线程(User):前台线程】 和 【守护线程(Daemon):后台线程】。
  :任何线程都可以是以上两种。他们在几乎每个方面都是相同的。唯一的不同之处就在于虚拟机的离开。
  (1)用户线程(User):日常开发中编写的业务逻辑代码,运行起来都是一个个用户线程。
  (2)守护线程(Daemon):
     ①守护线程则是用来服务用户线程的,若没有其他用户线程在运行,他也没作用了。也就没有理由继续下去。
     ②守护线程是是运行在后台的一种特殊进程,它并不属于程序本体。
     
***2、守护线程:——————>是通过普通线程调用setDaemon(true)方法设置而来的。
                     注意:设置守护线程的工作必须在start()之前完成
   (1)setDaemon(true)trueDaemon模式,falseUser模式。
   (2)setDaemon(boole on) :将此线程标记为守护线程或用户线程。当唯一运行的线程都是守护线程时,
                            Java 虚拟机退出。此方法必须在线程启动之前调用。
                            
01.守护线程和普通线程的区别?
   体现在一个结束时机上的不同:当进程结束时,进程会强制杀死所有正在运行的守护线程并最终停止。
   
02.Java的守护线程是什么样子的呢?
   当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。
   
03.进程的结束:当java进程中所有的普通线程都结束时,进程就会结束。

04.典型守护线程的例子:————>JVM垃圾回收
   守护线程在没有用户线程可服务时自动离开,用于为系统中的其它对象和线程提供服务:
   典型的守护线程例子是JVM中的系统资源自动回收线程,我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,
   当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收
   线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
   
05.如下jack就是守护线程,守护着最后一个用户线程,如果没有用户线程了,他也没作用了。不退出等什么?


练习题:
package apiday.thread.thread_daemon;
/**
 * 1、Java有两种Thread:——————>【用户线程(User):前台线程】 和 【守护线程(Daemon):后台线程】。
 *   :任何线程都可以是以上两种。他们在几乎每个方面都是相同的。唯一的不同之处就在于虚拟机的离开。
 *   (1)用户线程(User):日常开发中编写的业务逻辑代码,运行起来都是一个个用户线程。
 *   (2)守护线程(Daemon):
 *      ①守护线程则是用来服务用户线程的,若没有其他用户线程在运行,他也没作用了。也就没有理由继续下去。
 *      ②守护线程是是运行在后台的一种特殊进程,它并不属于程序本体。
 *
 * 2、守护线程:—————————>是通过普通线程调用setDaemon(true)方法设置而来的。
 *                      注意:设置守护线程的工作必须在start()之前完成
 *    (1)setDaemon(true):true为Daemon模式,false为User模式。
 *    (2)setDaemon(boole on) :将此线程标记为守护线程或用户线程。当唯一运行的线程都是守护线程时,
 *                             Java 虚拟机退出。此方法必须在线程启动之前调用。
 * 01.守护线程和普通线程的区别?
 *    体现在一个结束时机上的不同:当进程结束时,进程会强制杀死所有正在运行的守护线程并最终停止。
 *
 * 02.那Java的守护线程是什么样子的呢?
 *    当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。
 *
 * 03.进程的结束:当java进程中所有的普通线程都结束时,进程就会结束。
 *
 * 04.典型守护线程的例子:————>JVM垃圾回收
 *    守护线程在没有用户线程可服务时自动离开,用于为系统中的其它对象和线程提供服务:
 *    典型的守护线程例子是JVM中的系统资源自动回收线程,我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,
 *    当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收
 *    线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
 *
 * 05.如下jack就是守护线程,守护着最后一个用户线程,如果没有用户线程了,他也没作用了。不退出等什么?
 *
 */
public class DaemonThread {
    public static void main(String[] args) {
        Thread rose = new Thread(){//rose和jack没有必然关系
            public void run(){
                for(int i=0;i<5;i++){
                    System.out.println("rose:let me go!");//让我走
                    try{
                        Thread.sleep(1000);//设计间隔时间为1秒
                    }catch(InterruptedException e){ //中断异常
                        //e.printStackTrace();//在控制台显示异常的信息
                    }
                }
                System.out.println("rose:啊啊啊啊啊AAAAAAAaaaaa......");
                System.out.println("噗通!");
            }
        };

        Thread jack = new Thread(){
            public void run(){
                while(true){
                    System.out.println("jack:you jump! i jump!");//你跳!我也跳!
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                    }
                }
            }
        };
        rose.start();
        //设置守护线程的工作必须在start()之前完成
        //将此线程标记为守护线程或用户线程。当唯一运行的线程都是守护线程时,
        // Java 虚拟机退出。此方法必须在线程启动之前调用。
        jack.setDaemon(true);//setDaemon:守护线程
        jack.start();

        //当主线程不会结束时,进程就不会结束(因为主线程也是普通线程)
        //输出下面代码时,其余线程结束了,jack也会一直执行(you jump! i jump!)
        while(true);
    }
}

5、多线程并发安全问题-----线程同步:synchronized同步

01.多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!):

  • 当多个线程并发操作同一临界资源,由于线程切换实际不如确定,会导致操作顺序出现混乱而引起各种逻辑错误,严重时可能导致系统瘫痪
  • 临界资源:操作该资源的完整过程同一时刻只能由单线程进行。
一、多线程并发安全问题:-----------------线程同步:synchronized
   (1)当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作顺序出现了混乱,产生不正常后果。
   (2)临界资源:操作该资源的完整过程同一时刻只能有单个线程进行的
101.1:演示多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!)*(1)static void yield():线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
          这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。


(补充)(1)java 中的三种变量,哪种存在线程安全问题?
1)局部变量永远都不会存在线程安全问题,因为局部变量是不共享的(一个线程一一个栈)2)实例变量在堆中,堆只有1个,所以堆是多线程共享的,导致实例变量可能会存在线程安全问题;
3)静态变量在方法区中,方法区只有1个,所以方法区是多线程共享的,导致静态变量可能会存在线程安全问题;

(2)怎么解决线程安全问题?
线程同步机制----------------程序排队执行,会牺牲一部分效率;
1)异步编程模型:线程t1和线程t2,各自执行各自的,其实就是多线程并发(效率较高)
2)同步编程模型:当一个线程正在执行时,其他线程必须等此线程执行完毕才能执行,线程排队执行(效率较低);



练习题:
package apiday.thread.thread_synchronized;
/**
 * 一、多线程并发安全问题:-----------------线程同步:synchronized
 *    (1)当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作顺序出现了混乱,产生不正常后果。
 *    (2)临界资源:操作该资源的完整过程同一时刻只能有单个线程进行的
 *
 * 1、
 * 01.例1:演示多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!):
 *    (1)static void yield():线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
 *      这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。
 */
 public class SyncDemo1 {
    public static void main(String[] args) {
        /** 1、01.例1:多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!): */
        Table table = new Table();
        Thread t1 = new Thread(){
            public void run() {
                //写一个死循环:
                while(true){
                    int bean = table.getBeans();//从桌子上取一个豆子
                    /*
                     * (1):static void yield():yield:让出
                     *     线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
                     *     这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。
                     */
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run() {
                //写一个死循环:
                while(true){
                    int bean = table.getBeans();//从桌子上取一个豆子
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

class Table{
    private int beans = 20;//桌子上有20个豆子

    public int getBean(){
        if(beans==0){
            throw new RuntimeException("没有豆子了!");
        }
        Thread.yield();
        return beans--;
    }
}

02.synchronized两种使用方式之——>①同步方法

  • 当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时
    在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.
02.↑↑↑为了解决例1多线程并发安全问题:————>使用synchronized(同步)修饰该方法
   (2):当一个方法使用synchronized修饰后,这个方法称为【同步方法】,多个线程不能同时执行该方法。
       【将多个线程并发操作临界资源的过程改为[同步操作]】就可以有效的解决多线程并发安全问题。
       相当于让多个线程从原来的抢着操作改为排队操作。
       
2synchronized2种使用方式:
      (1)在方法上修饰,此时该方法变为一个同步方法
         例:public synchronized int getBeans(){。。。}
      (2)同步块,可以更准确的锁定需要排队的代码片段
         例:synchronized(){。。。}




练习题:
package apiday.thread.thread_synchronized;
/**
 * 一、多线程并发安全问题:-----------------线程同步:synchronized
 *    (1)当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作顺序出现了混乱,产生不正常后果。
 *    (2)临界资源:操作该资源的完整过程同一时刻只能有单个线程进行的
 *
 * 1、
 * 01.例1:演示多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!):
 *        (1)static void yield():线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
 *           这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。
 *
 * 02.↑↑↑为了解决例1多线程并发安全问题:————>使用synchronized(同步)修饰该方法
 *    (2):当一个方法使用synchronized修饰后,这个方法称为【同步方法】,多个线程不能同时执行该方法。
 *        【将多个线程并发操作临界资源的过程改为[同步操作]】就可以有效的解决多线程并发安全问题。
 *        相当于让多个线程从原来的抢着操作改为排队操作。

 * 2、synchronized 的2种使用方式:
 *   (1)在方法上修饰,此时该方法变为一个同步方法
 *      例:public synchronized int getBeans(){。。。}
 *   (2)同步块,可以更准确的锁定需要排队的代码片段
 *      例:synchronized(){。。。}
 */
 public class SyncDemo1 {
    public static void main(String[] args) {
        /** 1、01.例1:多线程并发安全问题:控制台有极低几率显示报错(RuntimeException: 没有豆子了!): */
        Table table = new Table();
        Thread t1 = new Thread(){
            public void run() {
                //写一个死循环:
                while(true){
                    int bean = table.getBeans();//从桌子上取一个豆子
                    /*
                     * (1):static void yield():yield:让出
                     *     线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
                     *     这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。
                     */
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run() {
                //写一个死循环:
                while(true){
                    int bean = table.getBeans();//从桌子上取一个豆子
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

class Table{
    private int beans = 20;//桌子上有20个豆子
    /** 1、02.↑↑↑为了解决例1多线程并发安全问题:————>使用synchronized(同步)修饰该方法 */
    /*
     * (2):
     * 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能同时执行该方法。
     * 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发安全问题。
     * 相当于让多个线程从原来的抢着操作改为排队操作。
     *
     * (3)synchronized 的2种使用方式之一:
     * ①在方法上修饰,此时该方法变为一个同步方法
     */
    public synchronized int getBeans(){//———————>变为同步方法
        if(beans==0){
            throw new RuntimeException("没有豆子了!");
        }
        Thread.yield();//让线程主动放弃本次时间片,用来模拟执行到这里时CPU就没有时间了
        return beans--;
    }
}

03.synchronized两种使用方式之——>②同步块

(1)同步块:------------用sychronized修饰的方法 或者 语句块在代码执行完之后锁会自动释放
有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.

1)语法:

同步执行:多个线程执行时有先后顺序
   synchronized(同步监视器/对象上锁的对象){
       //需要多个线程同步执行的代码片段(也就是可能出现问题的操作共享数据的多条语句)
   }

(补充):
当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
在这里插入图片描述
同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个。

2synchronized2种使用方式:
   (1)在方法上修饰时,同步监视器对象就是当前方法的实例。即:this! 没得选!此时该方法变为一个同步方法
      例:public synchronized int getBeans(){。。。}
   (2)同步块可以更准确的锁定需要排队的代码片段  例:synchronized(){。。。}
   
01.同步块:
   (1)优点:有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高【并发效率】;
           同步块可以更准确的控制需要多个线程排队执行的代码片段.
      缺点:会【降低程序的执行效率】,但我们为了保证线程的安全,有些性能是必须要牺牲的
   (2)语法:————————>同步执行:多个线程执行时有先后顺序
      synchronized(同步监视器/对象上锁的对象){
          //需要多个线程同步执行的代码片段(也就是可能出现问题的操作共享数据的多条语句)
      }
   (3)使用同步块效果的两个前提:
      前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
      前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
   (4)同步块synchronized特点:
      ①可以用来【修饰代码块,称为同步代码块】,使用的锁对象类型任意,但注意:必须唯一!
      ②可以用来【修饰方法,称为同步方法】
   ***③可以用来【修饰静态方法】,静态方法上使用同步块时,由于静态方法所属类,所以一定具有同步效果.
        指定的锁对象为当前类的类对象(Class的实例).
      ③同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
      ④但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
      
02.(1)使用同步块注意的问题:——————>包含参数的"()"要指定同步监视器对象
      ①同步块在使用时需要在"()"中指定一个同步监视器对象,即:上锁的对象;
      ②该对象从语法的角度来讲可以是任意引用类型的实例,
      ③只要保证多个需要同步(排队)执行该代码块的线程看到的是同一个对象才可以!
   (2)为什么同步块的锁对象可以是任意的同一个对象,但是同步方法使用的是this?
      :因为同步块可以保证同一个时刻只有一个线程进入,
       但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步。
   (3)注意:!!同步块里若是new的一个对象,不对!无效!
              例:synchronized (new Object())


练习题:
package apiday.thread.thread_synchronized;
/**
 * 2、synchronized 的2种使用方式:
 *    (1)在方法上修饰时,同步监视器对象就是当前方法的实例。即:this! 没得选!此时该方法变为一个同步方法
 *       例:public synchronized int getBeans(){。。。}
 *    (2)同步块可以更准确的锁定需要排队的代码片段  例:synchronized(){。。。}
 *
 * 01.同步块:
 *    (1)优点:有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高【并发效率】;
 *            同步块可以更准确的控制需要多个线程排队执行的代码片段.
 *       缺点:会【降低程序的执行效率】,但我们为了保证线程的安全,有些性能是必须要牺牲的
 *
 *    (2)语法:————————>同步执行:多个线程执行时有先后顺序
 *       synchronized(同步监视器/对象上锁的对象){
 *           //需要多个线程同步执行的代码片段(也就是可能出现问题的操作共享数据的多条语句)
 *       }
 *
 *    (3)使用同步块效果的两个前提:
 *       前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
 *       前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
 *
 *    (4)同步块synchronized特点:
 *       ①可以用来【修饰代码块,称为同步代码块】,使用的锁对象类型任意,但注意:必须唯一!
 *       ②可以用来【修饰方法,称为同步方法】
 *    ***③可以用来【修饰静态方法】,静态方法上使用同步块时,由于静态方法所属类,所以一定具有同步效果.
 *         指定的锁对象为当前类的类对象(Class的实例).
 *       ③同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
 *       ④但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
 *
 * 02.(1)使用同步块注意的问题:——————>包含参数的"()"要指定同步监视器对象
 *       ①同步块在使用时需要在"()"中指定一个同步监视器对象,即:上锁的对象;
 *       ②该对象从语法的角度来讲可以是任意引用类型的实例,
 *       ③只要保证多个需要同步(排队)执行该代码块的线程看到的是同一个对象才可以!
 *    (2)为什么同步块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
 *       :因为同步块可以保证同一个时刻只有一个线程进入,
 *        但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步。
 *    (3)注意:!!同步块里若是new的一个对象,不对!无效!
 *               例:synchronized (new Object())
 *
 */
public class SyncDemo02 {
    public static void main(String[] args) {
        Shop shop = new Shop();
        Thread t1 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        t1.start();
        t2.start();
    }
}

class Shop{
    //在方法上使用synchronized时,同步监视器对象就是当前方法的所属对象,即:this
    public synchronized void buy(){
//    public void buy(){
        try {
            Thread t = Thread.currentThread();//获取运行buy()方法的线程
            System.out.println(t.getName() + ":正在挑衣服...");
            Thread.sleep(5000);

            /*
            同步块在使用时需要在"()"中指定一个同步监视器对象,即:上锁的对象;
            该对象从语法的角度来讲可以是任意引用类型的实例,
            只要保证多个需要同步(排队)执行该代码块的线程看到的是同一个对象才可以!
             */
            synchronized (this) {
//            synchronized (new Object()){//——————>无效!
                System.out.println(t.getName() + ":正在试衣服...");
                Thread.sleep(5000);
            }

            System.out.println(t.getName() + ":结账离开");
        }catch(InterruptedException e){
        }
    }
}

04.synchronized:修饰静态方法 或 写在方法中

  • 当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.
  • 静态方法使用的同步监视器对象为当前类的类对象(Class的实例).
  • 注:类对象会在后期反射知识点介绍.
03.同步块可以用来【修饰静态方法】:
   (1)静态方法上如果使用synchronized,那么该方法一定是同步的。
      由于静态方法所属类,所以一定具有同步效果。
      
   (2)静态方法中指定的同步监视器对象(锁对象)为当前类的类对象。即:Class实例。
      在JVM中,每个被加载的类都有且只有一个Class的实例与之对应。



练习题:
package apiday.thread.thread_synchronized;
/**
 * 03.同步块可以用来【修饰静态方法】:
 *    (1)静态方法上如果使用synchronized,那么该方法一定是同步的。
 *       由于静态方法所属类,所以一定具有同步效果。
 *
 *    (2)静态方法中指定的同步监视器对象(锁对象)为当前类的类对象。即:Class实例。
 *       在JVM中,每个被加载的类都有且只有一个Class的实例与之对应。
 */
public class SyncDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
                Boo.dosome();
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                Boo.dosome();
            }
        };
        t1.start();
        t2.start();
    }
}

class Boo {
    //(1)静态方法上如果使用synchronized,那么该方法一定是同步的。由于静态方法所属类,所以一定具有同步效果。
//    public synchronized static void dosome() {

    //(2)在静态方法中使用同步块时,也可以指定类对象。方式为:类名.class:
    public static void dosome() {
        synchronized (Boo.class) {
            try {
                Thread t = Thread.currentThread();
                System.out.println(t.getName() + ":正在执行dosome方法");
                Thread.sleep(5000);
                System.out.println(t.getName() + ":执行dosome方法完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6、互斥锁

  • (1)介绍:

    • synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
      (每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。)
  • (2)

    • 当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的。
    • 使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。
互斥锁: (1)当使用多个synchronized锁定多个代码片段,
       (2)并且指定的锁对象都相同时,这些代码片段就是互斥的。
          即:多个线程不能同时执行他们
          
 概述:每个对象都有一个【锁标记】,当线程【拥有这个锁标记时】才能访问这个资源,
      没有锁标记便进入锁池。【任何一个对象系统都会为其创建一个互斥锁】,这个锁是【为了分配给线程
      以防止打断原子操作】。每个对象的锁只能分配给一个线程,因此叫做互斥锁。)
      
下面程序例子:(1)不加锁两个线程会同时执行!
            (2)加了锁(前提是一个锁对象/同步监视器对象)则两个线程进行排队,执行完一个后再执行另一个!
            
总结:当启动线程后调用该线程相对应的方法时,给该方法打一个标记(即为上锁),此时需要排队执行。不会同时执行。
     若有的方法没这个synchronized来上锁,则两个线程会同时执行,没有约束了,也就不存在互斥了。


练习题:
package apiday.thread.thread_synchronized.Mutex;
/**
 * 互斥锁: (1)当使用多个synchronized锁定多个代码片段,
 *        (2)并且指定的锁对象都相同时,这些代码片段就是互斥的。
 *           即:多个线程不能同时执行他们
 *  概述:每个对象都有一个【锁标记】,当线程【拥有这个锁标记时】才能访问这个资源,
 *       没有锁标记便进入锁池。【任何一个对象系统都会为其创建一个互斥锁】,这个锁是【为了分配给线程
 *       以防止打断原子操作】。每个对象的锁只能分配给一个线程,因此叫做互斥锁。)
 *
 * 下面程序例子:(1)不加锁两个线程会同时执行!
 *            (2)加了锁(前提是一个锁对象/同步监视器对象)则两个线程进行排队,执行完一个后再执行另一个!
 *
 * 总结:当启动线程后调用该线程相对应的方法时,给该方法打一个标记(即为上锁),此时需要排队执行。不会同时执行。
 *      若有的方法没这个synchronized来上锁,则两个线程会同时执行,没有约束了,也就不存在互斥了。
 */
public class SyncDemo4 {
    public static void main(String[] args) {
        Foo foo1 = new Foo();
        Foo foo2 = new Foo();
        Thread t1 = new Thread() {
            public void run() {
                foo1.methodA();
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                foo2.methodB();
            }
        };
        t1.start();
        t2.start();
    }
}

class Foo {
    /**
     * 想要实现互斥锁的条件:
     * (1)使用多个syncronized锁定多个代码片段
     * (2)并且指定的锁对象都相同(此处默认都是this)
     */
    public synchronized void methodA() {
        try {
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + ":正在执行A方法");
            Thread.sleep(5000);
            System.out.println(t.getName() + ":执行A方法完毕");
        } catch (InterruptedException e) {
        }
    }


    //(1)不加锁两个线程会同时执行!
//    public void methodB(){

    //(2)加了锁则两个线程进行排队,执行完一个后再执行另一个。——————>即为互斥!
//    public synchronized void methodB(){

    //(3)换为同步块后,锁对象为this和methodA()的this是同一个,所以仍具有互斥性
//    public void methodB() {
//        synchronized (this) {

    //(4)在这里换为类对象后不具有互斥性!此处不合适
    //   原因:若main中Foo类有两个对象,即使不用synchronized锁定代码片段,两个对象执行程序互不干扰,还是并发执行的
    public void methodB(){
        synchronized (Foo.class){
            try {
                Thread t = Thread.currentThread();
                System.out.println(t.getName() + ":正在执行B方法");
                Thread.sleep(5000);
                System.out.println(t.getName() + ":执行B方法完毕");
            } catch (InterruptedException e) {
            }
        }
    }
}

7、死锁

  • 死锁的产生:
    • 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。
死锁:
以两个线程为例:
当两个线程各自持有一个上锁的对象的同时等待对方先释放锁时就会形成一种"僵持状态"
导致程序卡住且无法再继续后面的执行,这个现象称为"死锁"

01.死锁的写法:

02.解决死锁的写法:
   (1)尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
   (2)当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
      即:A线程在持有锁1的过程中去持有锁2,B线程也要以这样的持有顺序进行。


练习题:
package apiday.thread.thread_synchronized.deadlock;
/**
 * 死锁:
 * 以两个线程为例:
 * 当两个线程各自持有一个上锁的对象的同时等待对方先释放锁时就会形成一种"僵持状态"
 * 导致程序卡住且无法再继续后面的执行,这个现象称为"死锁"
 *
 * 01.死锁的写法:
 *
 * 02.解决死锁的写法:
 *    (1)尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
 *    (2)当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
 *       即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
 *
 */
public class DeadLock {
    //筷子:
    private static Object chopsticks = new Object();
    //勺:
    private static Object spoon = new Object();

    public static void main(String[] args) {
        /** 01.死锁的写法: */
//        //北方人:
//        Thread np = new Thread(){
//            public void run(){
//                try {
//                    System.out.println("北方人:开始吃饭");
//                    System.out.println("北方人去拿筷子...");
//                    synchronized (chopsticks) {
//                        System.out.println("北方人拿起了筷子,开始吃饭...");
//                        Thread.sleep(5000);
//                        System.out.println("北方人吃完了饭,去拿勺...");
//                        synchronized (spoon){
//                            System.out.println("北方人拿起了勺子,开始喝汤...");
//                            Thread.sleep(5000);
//                        }
//                        System.out.println("北方人喝完了汤,北方人放下了勺子");
//                    }
//                    System.out.println("北方人放下了筷子,吃饭完毕。");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        };
//        //南方人:
//        Thread sp = new Thread(){
//            public void run(){
//                try {
//                    System.out.println("南方人:开始吃饭");
//                    System.out.println("南方人去拿勺...");
//                    synchronized (spoon) {
//                        System.out.println("南方人拿起了勺,开始喝汤...");
//                        Thread.sleep(5000);
//                        System.out.println("南方人喝完了汤,去拿筷子...");
//                        synchronized (chopsticks){
//                            System.out.println("南方人拿起了筷子,开始吃饭...");
//                            Thread.sleep(5000);
//                        }
//                        System.out.println("南方人吃完了饭,南方人放下了筷子");
//                    }
//                    System.out.println("南方人放下了勺子,吃饭完毕。");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        };
//        np.start();
//        sp.start();


        /** 02.没有死锁的写法: */
        //北方人
        Thread np = new Thread(){
            public void run(){
                try {
                    System.out.println("北方人:开始吃饭");
                    System.out.println("北方人去拿筷子...");
                    synchronized (chopsticks) {
                        System.out.println("北方人拿起了筷子,开始吃饭...");
                        Thread.sleep(5000);
                    }
                    System.out.println("北方人吃完了饭,放下了筷子");
                    System.out.println("北方人去拿勺子...");
                    synchronized (spoon){
                        System.out.println("北方人拿起了勺子,开始喝汤...");
                        Thread.sleep(5000);
                    }
                    System.out.println("北方人喝完了汤,北方人放下了勺子");
                    System.out.println("吃饭完毕。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //南方人
        Thread sp = new Thread(){
            public void run(){
                try {
                    System.out.println("南方人:开始吃饭");
                    System.out.println("南方人去拿勺...");
                    synchronized (spoon) {
                        System.out.println("南方人拿起了勺,开始喝汤...");
                        Thread.sleep(5000);
                    }
                    System.out.println("南方人喝完了汤,放下勺子...");
                    System.out.println("南方人去拿筷子...");
                    synchronized (chopsticks){
                        System.out.println("南方人拿起了筷子,开始吃饭...");
                        Thread.sleep(5000);
                    }
                    System.out.println("南方人吃完了饭,南方人放下了筷子");
                    System.out.println("吃饭完毕。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        np.start();
        sp.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值