JavaSE高阶篇-多线程问题研究

一、进程与线程

1)进程

程序:静态的,就是存放在磁盘里的可执行文件,是一系列指令的集合

进程:进入到内存中执行的应用程序,是系统运行的基本单位,有独立的运行空间

具体点说:一个程序如果被CPU多次读取到内存中,变成多个独立的进程。将程序运行起来,就称之为进程。进程是执行程序的一次性过程,他是动态的概念。进程存在生命周期,也就是说程序随着程序的终止而销毁。进程之间是通过TCP/IP端口实现交互的。

一句话总结:

一个应用程序(一个进程就是一个软件),一个程序至少包含一个进程,一个进程中至少包含一条线程

 

 【进程的三个状态】

进程在运行的过程中不断的改变其运行状态。通常一个运行的进程必须有三个状态,
就绪态 运行态 阻塞态
就绪态:当进程获取出 CPU 外所有的资源后,只要再获得 CPU 就能执行程序,这时的状态叫做就绪态。在一个系统中处于就绪态的进程会有多个,通常把这些排成一个队列,这个就叫就绪队列。
运行态:当进程已获得 CPU 操作权限,正在运行,这个时间就是运行态。在单核系统中,同一个时间只能有一个运行态,多核系统中,会有多个运行态。
阻塞态:正在执行的进程,等待某个事件而无法继续运行时,便被系统剥夺了 CPU 的操作权限,这时就 是阻塞态。引起阻塞的原因有很多,比如:等待 I/O 操作、被更高的优先级的进程剥夺了 CPU 权限等。

 2)线程

        是系统分配资源的基本单位,一个进程可以包含多个线程,堆空间和方法区共享,但是每个线程的栈空间和程序计数器是独立的,同样的线程消耗的资源也是小于进程的。
        线程是CPU 调度和执行的最小单位。
也可以讲,就是说,是进程中的执行单元,在进程下进行,作用是负责当前进程中的程序运行,一个进程中是可以有多个线程的,这样的应用程序称之为多线程,很多线程都是模拟出来的,真正的多线程是 指有多个 CPU 即,多核,如服务器,如果是模拟出来的多线程,即一个 CPU 的情况下,在同一个时间 点, CPU 只能执行一个代码,因为切换很快,所以就有同时执行的错觉

 

 对于java程序来说,当在DOS命令窗口中输入:

java HelloWorld 回车之后。会先启动 JVM ,而 JVM 就是一个进程。
JVM 再启动一个主线程调用 main 方法( main 方法就是主线程)。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
注意 :使用多线程机制之后, main 方法结束只是主线程结束了,其他线程还没结束,但没有主线程也不能运行。最起码,现在的 java 程序中至少有两个线程并发,
        一个是 垃圾回收线程,一个是 执行main 方法的主线程

 总结:

1) 线程在进程下进行
2) 进程之间不会相互影响,主线程结束将会导致整个进程结束
3) 不同的进程数据很难共享,两个不同进程的内存也是不共享的,也是独立的
4) 同线程下的不同线程之间数据很容易共享
5) 进程使用内存地址可以限定使用量
6) 可以把进程看成是现实生活当中的公司,线程可以看作是公司当中的某个员工

 二、并发与并行与串行

1)并发

同一个时刻多个线程同时操作了同一个数据。
(这是一种假并行。即一个 CPU 的情况下,在同一个时间点, CPU 只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。
特点:同时安排若干个任务,这些任务可以彼此穿插着进行;有些任务可能是并行的,比如买菜、发邮 件和去洗脚的某些路是重叠的,这是确实是在做三件事;但进菜市场和发邮件和接娃三者是互斥的,每 个时刻只能完成一件(并发允许两个任务之间彼此干扰)

 2)并行

同一个时刻,多个线程同时执行了不同的程序
多个任务同时进行。并行必须有多核才能实现,否则只能是并发。
你(线程)做你的事,我(线程)做我的事,咱们互不干扰并同时进行。

 3)串行

一个程序处理完当前进程,按照顺序接着处理下一个进程,一个接着一个进行。

、CPU调度与主线程介绍

1)CPU调度

1 ):分时调度
        指的是,让所有的线程轮流获取CPU 的使用权,并且平均分配每个线程占用的 CPU 的时间片
2 ):抢占式调度
        指的是,多个线程轮流抢占CPU 的使用权,随机性比较高,哪个线程的优先级越高,先执行的几率就比较大,不是每次都是优先级高的线程先抢到 ------->Java 程序为抢占式

2)主线程介绍

/**专门运行main方法的线程叫做主线程**/
public class Demo01Thread {
    /**单线程程序**/
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println("我是一个for循环");
        }
        System.out.println(Math.abs(-1));
    }
}

 四、创建线程的方式

1)第一种方式_extends Thread

步骤:

​        1)创建一个类,继承Thread类

​        2)重写Thread中的run方法,设置线程任务(该线程要执行啥代码)

​        3)创建自定义的线程类对象

​        4)调用thread中的start方法(开启线程,JVM自动调用重写的run方法执行线程任务)

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread...执行了............."+i);
        }
    }
}
public class Demo02Thread {
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread = new MyThread();
        //调用Thread中的start方法,开启线程,jvm自动执行run方法
        myThread.start();
        myThread.start();//不能多次调用

        for (int i = 0; i < 10; i++) {
            System.out.println("main方法.............执行了"+i);
        }
    }
}

 2)Thread类中的方法

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

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

public void setName(线程名字):   给线程设置名字

public static Thread currentThread() :返回对当前正在执行的线程对象的引用。此方法在哪个线程中用获取的就是哪个线程对象

static void sleep(long millis) : 线程睡眠,设置的是毫秒值,如果超时,线程会自动醒来,继续执行 void start() 使该线程开始执行; Java 虚拟机调用该线程的 run 方法

 

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程开始执行了........"+i);
        }
    }
}
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.setName("曼曼");
        myThread.start();//开启时才抢占,
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000L);//降低for循环的执行速度,线程睡眠
            System.out.println(Thread.currentThread().getName()+"线程开始执行............"+i);
        }
    }
}

Thread.yield()方法:暂停当前正在执行的线程对象,把执行机会让給相同或者更高优先级的线程。

/**
 * 线程让步
 */
public class Text05 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 15; i++) {
                    Thread.yield();
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        });
        t.setName("t");
        t.start();

        // 主线程
        for (int i = 0; i < 15; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // 等待子线程执行完毕再输出
            if (i == 7) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
运行结果:让主线程走完了才运行
**/
sleep() yield() 的区别】
1.sleep() 使当前线程进入停滞状态,所以执行 sleep() 的线程在指定的时间内肯定不会被执行; yield() 只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执 行。
2.sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的, yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
3. 实际上, yield() 方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以 yield() 方法称为 退让 ,它把运 行机会让给了同等优先级的其他线程
4. 另外, sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优 先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先 级的线程运行结束,才有机会运行。

补充:

Thread.join() 方法:用于等待其他线程终止
    如果线程A中调用了线程B的join方法,那么线程A阻塞,直到线程B执行完后,线程A从阻塞状态转为就绪状态,等待获取CPU的使用权。join方法要在start方法调用后调用才有效,线程必须启动,再加入。

 

package com.thread;


public class Text06 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new JoinThread("t1"));
        Thread t2 = new Thread(new JoinThread("t2"));
        t1.start();
        t1.join();
        t2.start();
    }
}

class JoinThread implements Runnable {
    private String name;

    // 构造函数
    public JoinThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(name + "-->" + i);
        }
    }
}

 主线程一定会等子线程都结束了才结束

3)第二种方式_实现Runnable接口

1.创建一个类,实现Runnable接口
2.重写run方法,设置线程任务
3.创建线程类对象
4.创建Thread对象,将线程类对象封装到Thread中   ->  Thread(Runnable target)
5.调用Thread中的start方法

 

 4)两种实现多线程方式的区别

1. 继承 Thread, 有局限性 , 因为继承支持单继承
2. 实现 Runnable, 解决了单继承的局限性
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易的实现资源贡共享。

 【总结】:

 实现Runnable接口比继承Thread类具有的有优势,适合多个相同的程序代码的线程去处理统一资源,可以避免java中的单继承的限制增加程序的健壮性,代码可以被多个线程共享,代码和数据独立,线程池只能放入实现Runnable或callable类线程,不能直接放入继承Therad的类。

main方法其也是一个线程。在java中所有的线程都是同时启动的,至于什么时候、那哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程。

 5)匿名内部类创建多线程

1.匿名内部类回顾:
  a.匿名内部类代表的是子类对象或者实现类对象
  b.格式1:
    new 接口/抽象类(){
        重写方法
    }.重写的方法();

  c.格式2:
    接口/抽象类 对象名 = new 接口/抽象类(){
        重写方法
    }
    对象名.重写的方法();
java

Java

public class Test02 {
    /**
     * 匿名内部类创建多线程的方式
     **/
    public static void main(String[] args) {
        // 创建并启动第一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        // 线程休眠100毫秒
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 输出线程名和执行次数
                    System.out.println(Thread.currentThread().getName() + "...执行了" + i);
                }
            }
        }, "杨童").start();

        // 创建并启动第二个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        // 线程休眠100毫秒
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 输出线程名和执行次数
                    System.out.println(Thread.currentThread().getName() + "...执行了" + i);
                }
            }
        }, "曼曼").start();
    }
}

 五、线程的生命周期

1 、新建状态( new ):新建一个线程对象
2 、就绪状态( Runnable ):线程对象创建后,其他线程调用了该对象的 start ()方法。该状态的线程
位于可运行的线程池中,变得可运行,等待获取 CPU 的使用权
3 、运行状态( Running ):就绪状态的线程获取了 CPU ,执行程序代码
4 、阻塞状态( Blocked ):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进
入就绪状态,才有机会转到运行状态
阻塞的情况分为三种 :
等待阻塞:运行的线程执行 wait ()方法, JVM 会把该线程放入等待池中。( wait 方法会释放持有的锁)
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。
其他阻塞:运行的线程执行 sleep ()或 join ()方法,或者发出了 I/O 请求时, JVM 会把该线程程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪 状。( sleep 是不会释放持有的锁)
死亡状态( Dead ):线程执行完了或者因异常退出了 run ()方法,该线程结束生命周期。

 六、线程同步

1)概念

        即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多种。

2)为啥要创建多线程

        在一般情况下,创建一个线程是不能提高程序执行效率的,所以要创建多个线程。

3-为什么要线程同步

        多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。

4-什么叫线程同步

        同步就是协同步调,按预定的先后次序进行运行。如:你做完,我再做。

错误理解 :

        “同”字从字面上容易理解为一起动作,其实不是。“同”字应是指协同、协助、互相配合。

正确理解 :

        所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其他线程也不能调用这个方法。

线程同步的作用 :

        线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
        当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
        线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

5-类锁和对象锁

(1)基本概念:

对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的

                        —— 一个对象一把锁,100个对象100把锁。

类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象

                        —— 100个对象,也只是1把锁。

注意上述第三种方式synchronized同步代码块实现:

在静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁,这就叫类锁。

(2)释放锁的时机

        如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :

        线程执行完代码块,自动释放锁;

        程序报错,JVM让线程自动释放锁;

        如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :

        线程执行完代码块,自动释放锁;

        程序报错,JVM让线程自动释放锁;

public static void main(String[] args) {
		new Text07().myThread1(new Thread());
		new Text07().myThread2(new Thread());
	}

七、 线程安全问题

1)什么时候会发生线程安全问题

多个线程访问同一个资源(数据)时

2)问题

public class MyTicket implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket>0){//也有可能出现负数的情况
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"曼曼");
        Thread t2 = new Thread(myTicket,"童童");
        Thread t3 = new Thread(myTicket,"雪雪");
        t1.start();
        t2.start();
        t3.start();
    }
}

3)解决方案1(同步代码块)

同步互斥访问(synchronized)

基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称同步互斥访问。

在Java中一般采用synchronized和Lock来实现同步互斥访问。

Synchronized关键字

首先了解一下互斥锁 :就是能达到互斥访问目的的锁

        1)线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
        2)当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
        3) 线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

 synchronized同步代码块实现
1. 第一种:synchronized代码块方式(灵活)
2. 第二种:在实例方法上使用synchronized
3. 第三种 : 在静态方法上使用synchroniz

 1.

        1.关键字:synchronized
        2.格式:
             synchronized(任意对象){
                 可能出现线程不安全的代码
             }
      3.线程进了synchronized代码块相当于上锁了,其他线程只能在代码块外面等待,等到执行的线程执行完毕,出了synchronized,相当于将锁释放了,此时其他等待的线程才有机会拿到锁,上锁,进去执行
   4.注意:想要实现线程安全,线程同步,多个线程之间用的锁对象需要是同一个锁对象 

 

/**
 * 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
 */
public class MyTicket implements Runnable {
    // 剩余票数
    int ticket = 100;
    // 创建对象锁
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                // 线程休眠
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 同步代码块
            synchronized (new Object()) {
                if (ticket > 0) {
                    // 打印当前买票信息
                    System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                    // 票数减一
                    ticket--;
                }
            }
        }
    }
}
/**

 * 测试类 Test01,用于测试多线程售票系统
   */
   public class Test01 {
   public static void main(String[] args) {
       // 创建 MyTicket 实例
       MyTicket myTicket = new MyTicket();
       // 创建线程并指定线程名
       Thread t1 = new Thread(myTicket, "曼曼");
       Thread t2 = new Thread(myTicket, "童童");
       Thread t3 = new Thread(myTicket, "雪雪");
       // 启动线程
       t1.start();
       t2.start();
       t3.start();
   }
   }

4)解决方案2(同步方法)

1.普通同步方法

1.格式:
  public synchronized 返回值类型 方法名(参数){
      可能出现线程不安全的代码
  }

2.默认锁:
  this

/**
 * 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
 */
public class MyTicket implements Runnable {
    // 剩余票数
    int ticket = 100;

    @Override
    public void run() {
        while (true) {
            try {
                // 线程休眠
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            method();
        }
    }
    /**
     * 同步方法
     */
    public synchronized void method() {//可能出现线程不安全的代码
        /*// 注释掉原有的同步方法
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
            ticket--;
        }*/
        if (ticket > 0) {
            // 打印当前买票信息
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
            // 票数减一
            ticket--;
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"曼曼");
        Thread t2 = new Thread(myTicket,"童童");
        Thread t3 = new Thread(myTicket,"雪雪");
        t1.start();
        t2.start();
        t3.start();
    }
}

2.静态同步方法

1.格式:
  public static synchronized 返回值类型 方法名(参数){
      可能出现线程不安全的代码
  }

2.默认锁:
  当前类.class

 

public class MyTicket implements Runnable {
    static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            method();
        }
    }
     /* public static synchronized void method() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
            ticket--;
        }
    }*/

    public static void method(){
        synchronized (MyTicket.class){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                ticket--;
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"曼曼");
        Thread t2 = new Thread(myTicket,"童童");
        Thread t3 = new Thread(myTicket,"雪雪");
        t1.start();
        t2.start();
        t3.start();
    }
}

八、死锁

1.死锁(锁嵌套就有可能产生死锁)

指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.

 

 根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行
而线程T2正在持有R2锁,但是T2线程需要再拿到R1锁,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中

 

 

public class LockA {
    public static LockA lockA = new LockA();
}

public class LockB {
    public static LockB lockB = new LockB();
}

/**
 * 循环死锁类 DieLock,实现了 Runnable 接口
 */
public class DieLock implements Runnable {
    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            // 如果 flag 为 true,获取 lockA 锁
            synchronized (LockA.lockA) {
                System.out.println("if...lockA");
                // 获取 lockB 锁
                synchronized (LockB.lockB) {
                    System.out.println("if...lockB");
                }
            }
        } else {
            // 如果 flag 为 false,获取 lockB 锁
            synchronized (LockB.lockB) {
                System.out.println("else...lockB");
                // 获取 lockA 锁
                synchronized (LockA.lockA) {
                    System.out.println("else...lockA");
                }
            }
        }
    }
}

public class Test01 {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);

        new Thread(dieLock1).start();
        new Thread(dieLock2).start();
    }
}

 直接卡死

九、线程状态

1)线程状态介绍

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
  这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。

 2)线程状态图

 可能不详细

再贴一张

 十、等待唤醒

练习1:

 

public class BaoZiPu {
//    定义一个count 计数
//    生产包子 count++   消费包子:直接输出count
    private int count;
    private boolean flag;
//    定义一个boolean的flag
//   flag = true----->证明有包子    flag = false----->证明没有包子
    public BaoZiPu() {
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    /**
     * getCount专门给消费线程用
     */
    public void getCount() {
        System.out.println("消费了第......"+count+"个包子");
    }

    /**
     * setCount专门给生产线程生产包子用
     */
    public void setCount() {
        count++;
        System.out.println("生成了第..."+count+"个包子");
    }
    public boolean isFlag() {//看看是不是有包子
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

 

public class Product implements Runnable {
    private BaoZiPu baoZiPu;
    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (baoZiPu) {
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //1.判断flag如果是true,证明有包子,生产线程等待
                if (baoZiPu.isFlag() == true) {
                    try {
                        baoZiPu.wait();//必须要锁对象调用
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //2.如果flag为false,证明没有包子,需要生产包子
                baoZiPu.setCount();
                //3.改变flag状态,为true,证明有包子了
                baoZiPu.setFlag(true);
                //4.唤醒消费线程
                baoZiPu.notify();//必须要锁对象调用
            }
        }
    }
}
public class Consumer implements Runnable {
    private BaoZiPu baoZiPu;
    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (baoZiPu) {
                try {
                    // 线程休眠
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 1.判断标志 flag,如果为 false,表示没有包子,消费线程等待
                if (!baoZiPu.isFlag()) {
                    try {
                        baoZiPu.wait(); // 等待并释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 2.如果 flag 为 true,表示有包子,需要消费包子
                baoZiPu.getCount();

                // 3.改变标志 flag 状态为 false,表示没有包子了
                baoZiPu.setFlag(false);

                // 4.唤醒生产线程
                baoZiPu.notify(); // 唤醒在此对象监视器上等待的单个线程
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);
        new Thread(product).start();
        new Thread(consumer).start();
    }
}

同步方法改造写法

public class BaoZiPu {
    private int count;
    private boolean flag;

    public BaoZiPu() {
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    /**
     * getCount专门给消费线程用
     * @return
     */
    public synchronized void getCount() {
        //1.判断flag如果是false,证明没有包子,消费线程等待
        if (flag == false) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //2.如果flag为true,证明有包子,需要消费包子
        System.out.println("消费了第......" + count + "个包子");
        //3.改变flag状态,为false,证明没有包子了
        flag = false;
        //4.唤醒生产线程
        this.notify();
    }

    /**
     * setCount专门给生产线程生产包子用
     *
     * @param
     */
    public synchronized void setCount() {
        //1.判断flag如果是true,证明有包子,生产线程等待
        if (flag == true) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //2.如果flag为false,证明没有包子,需要生产包子
        count++;
        System.out.println("生成了第..." + count + "个包子");
        //3.改变flag状态,为true,证明有包子了
        flag = true;
        //4.唤醒消费线程
        this.notify();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
/**
 * 生产线程
 */
public class Product implements Runnable {
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            baoZiPu.setCount();
        }

    }
}
public class Consumer implements Runnable {
    private BaoZiPu baoZiPu;

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.getCount();
        }

    }
}
public class Test01 {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);
        new Thread(product).start();
        new Thread(consumer).start();
    }
}

多等待多唤醒

public class BaoZiPu {
    private int count;
    private boolean flag;

    public BaoZiPu() {
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    /**
     * getCount专门给消费线程用
     */
    public synchronized void getCount() {
        //1.判断flag如果是false,证明没有包子,消费线程等待
        while (flag == false) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //2.如果flag为true,证明有包子,需要消费包子
        System.out.println("消费了第......" + count + "个包子");
        //3.改变flag状态,为false,证明没有包子了
        flag = false;
        //4.唤醒生产线程
        this.notifyAll();
    }

    /**
     * setCount专门给生产线程生产包子用
     *
     * @param
     */
    public synchronized void setCount() {
        //1.判断flag如果是true,证明有包子,生产线程等待
        while (flag == true) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //2.如果flag为false,证明没有包子,需要生产包子
        count++;
        System.out.println("生成了第..." + count + "个包子");
        //3.改变flag状态,为true,证明有包子了
        flag = true;
        //4.唤醒消费线程
        this.notifyAll();
    }

    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class Consumer implements Runnable {
    private BaoZiPu baoZiPu;

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.getCount();
        }

    }
}

 

public class Product implements Runnable {
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true) {

            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            baoZiPu.setCount();
        }

    }
}
public class Test01 {
    public static void main(String[] args) {
        /**
         * 两个生产者,两个消费者
         * **/
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);
        new Thread(product).start();
        new Thread(product).start();
        /*********************************/
        new Thread(consumer).start();
        new Thread(consumer).start();
        /**
         * 会出现连续消费,连续生产的情况
         *notify 如果多条线程在等待唤醒,就会随机唤醒一个
         *假如第一个生产者线程抢到了锁,生产完第一个包子,然后随机唤醒一个锁,然后释放锁
         * 然后第二个生产线程抢到了锁,flag还是为true,所以生产线程等待,再次释放锁
         * 然后第一个消费者线程抢到锁,然后消费一个包子,同时改flag为false 随机唤醒一个生产者线程,再次释放拿到的锁
         *同时刚刚等待的第二个生产者线程再次拿到锁,生产一个包子,改变flag值,再次随机唤醒一个生产者线程,同时释放手里的锁
         *这时第一个生产线程也可能再次拿到锁,再次生产一个包子,前一个已经生产了,会造成同时生产2个包子的情况
         *反之,也可能出现同时消费2个包子的情况
         * 解决办法:
         *直接唤醒所有线程,但是需要保证第二个自己的线程能再次拿到锁,所以必须循环回去,
         * 将所有if改成while
         *这样就保证了第一次在生产完包子后,改了flag值,即使第二个生产者拿到锁,flag不符合也不会继续生产
         * **/
    }
}

十一、lock锁

1)简单实现案例:

1.概述:是一个接口,作为锁对象使用
2.实现类:ReentrantLock
3.方法:
  void lock() :获取锁
  void unlock() :释放锁 

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
 */
public class MyTicket implements Runnable {
    int ticket = 100;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 获取锁
            lock.lock();
            try {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                    ticket--;
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"曼曼");
        Thread t2 = new Thread(myTicket,"童童");
        Thread t3 = new Thread(myTicket,"雪雪");
        t1.start();
        t2.start();
        t3.start();
    }
}

 2)lock与synchronized区别

1. 在java.util.concurrent.atomic 包下有很多"原子类",这些类都是对某一个值进行修改,进行操作的,底层实现原理都是"乐观锁"
    AtomicXXX

比如:AtomicInteger -> 以原子形式,修改int的值
2.构造:
  a.AtomicInteger()创建具有初始值 0 的新 AtomicInteger -> int i = 0
  b.AtomicInteger(int initialValue)创建具有给定初始值的新 AtomicInteger -> int i = 10
      
3.方法:
  int addAndGet(int delta)  以原子方式将给定值与当前值相加
  int getAndIncrement()-> 加1
  int getAndDecrement()-> 减1    

public class Test02 {
    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(10);
        System.out.println(i);
        int sum = i.addAndGet(10);
        System.out.println("sum = " + sum);
    }
}

 

十二、线程池

1.问题描述:
  我们将来在操作多线程的时候,我们为了执行线程任务会频繁创建线程对象,销毁线程对象,这样的话比较耗费内存资源
      
  所以,我们想,能不能创建几个线程对象,让这几个线程对象循环利用

1.概述:Executors线程池对象
2.创建:
  static ExecutorService newFixedThreadPool(int nThreads) -> 创建线程池,指定最多创建多少条线程对象
      
3.ExecutorService:管理线程的
  Future<?> submit(Runnable task) -> 提交线程任务
  void shutdown()  -> 启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
4.Future接口:
  用来接收执行run方法后的返回值的值,但是run方法没有返回值,所以不需要Future接收

 

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行了");
    }
}

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

public class Test01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<String> future = es.submit(new MyCallable());
        System.out.println("future = " + future.get());
    }
}

十三、Callable接口

1)基本概念

1.概述:Callable<V>接口,类似于Runnable
2.方法:V call()
3.<V>:叫做泛型
  a.泛型作用:规定类型的,统一数据类型的
  b.泛型只能传递引用数据类型,如果操作基本类型数据,需要传递包装类,如果不写泛型,默认类型为Object类型
4.call方法 ->设置线程任务的,类似于run方法
5.call方法和run方法区别
  run:设置线程任务,没有返回值,不能throws异常
  call:设置线程任务,有返回值,能直接throws异常,
       返回值类型是啥类型?
           实现Callable时泛型写什么类型,重写的call方法返回值类型就是啥类型
6.提交线程任务:ExecutorService中的方法:
  Future<T> submit(Callable<T> task)
  返回值:Future->用于接收call方法的返回值  
7.获取call的返回值:
  需要用到Future接口中的方法:V get()  

public class Test01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<String> future = es.submit(new MyCallable());
        System.out.println("future = " + future.get());
    }
}

 练习1

需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串

public class MyString implements Callable<String> {
    @Override
    public String call() throws Exception {
        int[] arr = {1,2,3,4,5};
        String str = "[";
        for (int i = 0; i < arr.length; i++) {
            if (i== arr.length-1){
                str+=arr[i]+"]";
            }else {
                str+=arr[i]+", ";
            }
        }
        return str;
    }
}
public class MySum implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}
public class Test01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个固定大小为 2 的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);

        // 提交 MyString 任务,并获取其执行结果
        Future<String> f1 = es.submit(new MyString());

        // 提交 MySum 任务,并获取其执行结果
        Future<Integer> f2 = es.submit(new MySum());

        // 输出 MyString 任务的执行结果
        System.out.println(f1.get());

        // 输出 MySum 任务的执行结果
        System.out.println(f2.get());

        // 关闭线程池
        es.shutdown();
    }
}

 

十四、定时器

1.作用:用来定时执行线程任务的
2.构造:Timer()
3.方法:
 void schedule(TimerTask task, Date firstTime, long period)  
                task:设置线程任务
                firstTime:从什么时间开始
                period:每隔多长时间执行一次线程任务

 

public class Test01 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("柳岩对涛哥说:涛哥,该起床了!");
            }
        },new Date(),2000L);//当前系统时间开始算,每隔两秒
    }
}

十五、枚举

1)枚举介绍

1.引用数据类型:
  类 数组 接口 枚举 注解
      
2.所有的枚举类的父类->Enum   
  public enum 枚举类类名{
      
  }  

3.枚举中的成员:
  a.所有成员默认都是static final修饰的常量,但是不用写static final,被static final修饰的变量名习惯上大写
      
  b.每一个枚举成员都是所在枚举的对象
  c.问题:枚举类中的成员都是什么类型? -> 本类类型
      
4.枚举的使用场景:一般都是表示状态的   
5.注意:枚举类中的构造都是private的,不写private,默认也是private 

 

package com.atguigu.enumtest;

public enum Status {
    //WEIFUKUAN,// Status WEIFAHUO = new Status()
    //YIFUKUAN,// Status YIFUKUAN = new Status()
    //WEIFAHUO,// Status WEIFAHUO = new Status()
    //YIFAHUO;// Status YIFAHUO = new Status()
    WEIFUKUAI("未付款"),// Status WEIFAHUO = new Status("未付款")
    YIFUKUAN("已付款"), //Status YIFUKUAN = new Status("已付款")
    WEIFAHUO("未发货"), //Status WEIFAHUO = new Status("未发货")
    YIFAHUO("已发货");//Status YIFAHUO = new Status("已发货")

    //枚举类中的构造要求是private的,不写也是private

    String name;
    Status(){

    }

    Status(String name){
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Test01 {
    public static void main(String[] args) {
        Status weifahuo = Status.WEIFAHUO;
        System.out.println("weifahuo = " + weifahuo);//默认调用toString方法
        System.out.println("weifahuo = " + weifahuo.toString());//默认调用toString方法

        Status weifahuo1 = Status.WEIFAHUO;
        System.out.println(weifahuo1.getName());
    }
}

2)枚举的方法

public enum Status {
    //WEIFUKUAN,// Status WEIFAHUO = new Status()
    //YIFUKUAN,// Status YIFUKUAN = new Status()
    //WEIFAHUO,// Status WEIFAHUO = new Status()
    //YIFAHUO;// Status YIFAHUO = new Status()

    WEIFUKUAI("未付款"),// Status WEIFAHUO = new Status("未付款")
    YIFUKUAN("已付款"), //Status YIFUKUAN = new Status("已付款")
    WEIFAHUO("未发货"), //Status WEIFAHUO = new Status("未发货")
    YIFAHUO("已发货");//Status YIFAHUO = new Status("已发货")

    //枚举类中的构造要求是private的,不写也是private

    String name;
    Status(){

    }

    Status(String name){
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Test02 {
    public static void main(String[] args) {
        Status weifahuo = Status.WEIFAHUO;
        System.out.println(weifahuo);
        System.out.println(weifahuo.toString());

        System.out.println("=======================");

        Status[] values = Status.values();
        for (Status value : values) {
            System.out.println(value.getName());
        }

        System.out.println("=======================");

        Status yifahuo = Status.valueOf("YIFAHUO");
        System.out.println(yifahuo);
    }
}

写的不好,请你给我指出来,谢谢

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值