java语言--------javaSE之多线程

多线程

进程概述及多进程的意义

  • 线程和进程:要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。

  • 进程概述
    什么是进程呢?通过任务管理器我们就可以看到进程的存在。
    概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
    每一个进程都有它自己的内存空间和系统资源。

  • 多进程的意义
    单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
    所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
    对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
    因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
    所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。

线程概述及多线程的意义及并行和并发的区别

  • 什么是线程
    在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

  • 多线程有什么意义呢?
    多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
    那么怎么理解这个问题呢?
    我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
    CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
    中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
    中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.

  • 并行和并发。
    前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
    后者是物理上同时发生,指在某一个时间点同时运行多个程序。

  • 什么是并发 ?
    并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
    执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时…可以想象一下.

  • 什么是并行 ?
    并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

Java程序运行原理和JVM的启动是多线程的吗

  • Java程序运行原理
    Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
    该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
    所以 main方法运行在主线程中。

  • JVM的启动是多线程的吗:

    JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程程序实现的方式1

  • 如何实现多线程:
    如何实现呢?
    由于线程是依赖进程而存在的,所以我们应该先创建一个进程(JVM)出来。
    而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
    但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
    但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
    由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
    然后提供一些类供我们使用。我们就可以实现多线程程序了。
    参考 Thread类

  • 几个小问题:
    启动线程使用的是那个方法
    线程能不能多次启动
    run()和start()方法的区别

  • 我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
    Java 虚拟机调用该线程的 run 方法。

  • 为什么要重写run方法?

    这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
    我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
    那么也就是run方法中封装应该是必须被线程执行的代码.

  • run方法中的代码的书写原则: 一般是比较耗时的代码

public static void main(String[] args) {
    // 类 Thread 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
    //创建新执行线程有两种方法。
    //1.一种方法是将类声明为 Thread 的子类。
    // 2.该子类应重写 Thread 类的 run 方法。
    //3.接下来可以分配并启动该子类的实例。

    MyThread th= new MyThread();

    //th.run(); 你直接调用run方法,并没有什么线程创建出来,只是普通的new对象调用方法而已,这些代码还是运行在主线程。
    //正确开启线程的方法。
    //多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
    th.start();
    //th.start();

    MyThread th2 = new MyThread();
    th2.start();
}
public class MyThread extends Thread{

    //run 方法里面的代码,将来有线程来执行的,一般我们会在run方法里面执行一些耗时的代码。
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }

    public void show(){
        System.out.println("abc");
    }
}

获取和设置线程对象名称

Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称

public static void main(String[] args) {
    System.out.println("主线程执行了");
    //开启了一个子线程
    MyThread th = new MyThread();
    MyThread th2 = new MyThread();
    th2.setName("李冰冰");
    th.setName("范冰冰");
    th.start();
    th2.start();
}
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(this.getName()+"耗时的循环操作");
        }
    }
}

如何获取main方法所在的线程名称呢?
public static Thread currentThread()//获取当前执行的线程

public static void main(String[] args) {
    System.out.println("主线程的名称");
    //获取当前正在运行的线程对象
    Thread thread = Thread.currentThread();
    String name = thread.getName();
    System.out.println(name);
},

我们现在是想获取主线程的名称,那么我们可不可以先获取到主线程,
如果我们能获取到主线程,那么我们就可以调用getName方法获取对应的名称.
如何获取主线程呢? public static Thread currentThread()返回对当前正在执行的线程对象的引用。

线程调度及获取和设置线程优先级

  • 线程的执行
    假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
    线程只有得到 CPU时间片,也就是使用权,才可以执行指令。Java是如何对线程进行调用的呢?

  • 线程有两种调度模型:
    分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
    优先级高的线程获取的 CPU 时间片相对多一些。
    java使用的是抢占式调度模型。

  • 如何设置和获取线程优先级
    public final int getPriority() //获取线程的优先级
    public final void setPriority(int newPriority)//设置线程的优先级

public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.setName("线程A");
        th2.setName("线程B");
        //设置线程的优先级范围是 1---10  默认是5
        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MIN_PRIORITY);
        int priority = th1.getPriority();
        int priority1 = th2.getPriority();
        System.out.println(priority);
        System.out.println(priority1);
        th1.start();
        th2.start();
    }
}
public class MyThread extends Thread{

    @Override
    public void run() {

        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

线程控制之休眠线程

  • 线程休眠: public static void sleep(long millis) 线程休眠
public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}
public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始进来执行了");
        //让当前线程线程休眠一下
        //Thread.sleep(3000);
        
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.start();
        //th2.start();
        
        System.out.println("下面的代码");
    }

线程控制之加入线程

  • 加入线程: public final void join()
    意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
    注意事项: 在线程启动之后,在调用方法
public static void main(String[] args) throws InterruptedException {
    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    MyThread th3 = new MyThread();
    //join() 可以让多个线程从并发执行,变成串行。
    th1.setName("刘备");
    th2.setName("关羽");
    th3.setName("张飞");
    th1.start();
    th1.join();
    th2.start();
    th2.join();
    th3.start();
    th3.join();

}
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

线程控制之礼让线程

  • 礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。

    这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.

这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

public class MyThread extends Thread{
    @Override
    public void run() {
        Thread.yield(); //线程礼让
        for (int i = 0; i < 100; i++) {

            System.out.println(this.getName()+"=="+i);
        }
    }
}

线程控制之守护线程

  • 守护线程: public final void setDaemon(boolean on):
    将该线程标记为守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
    该方法必须在启动线程前调用。

Java用户线程和守护线程

  • 用户线程和守护线程的区别
    用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
  • 用户线程和守护线程的适用场景
    由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
  • 创建守护线程
    调用线程对象的方法setDaemon(true),设置线程为守护线程。
    1)thread.setDaemon(true)必须在thread.start()之前设置。
    2)在Daemon线程中产生的新线程也是Daemon的。
    3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
    因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
  • java守护线程和Linux守护进程
    两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
    在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
public static void main(String[] args) throws InterruptedException {
    System.out.println("主线程开始执行了");
    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    th1.setName("张飞");
    th2.setName("关羽");
    th1.setDaemon(true);
    th2.setDaemon(true);
    th1.start();
    th2.start();

    System.out.println("主线程执行完毕");
}
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

线程控制之中断线程

  • 中断线程
    public final void stop(): 停止线程的运行

  • th1.stop(); //强制终止线程。过时方法
  • ​ public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞

public static void main(String[] args) throws InterruptedException {
    System.out.println("主线程开始执行了");
    MyThread th1 = new MyThread();
    th1.setName("张飞");
    th1.start();
    th1.interrupt();//清除线程的阻塞
    System.out.println("主线程执行完毕");

}
public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            //线程休眠,使线程处于了一种阻塞的状态。
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

多线程程序实现的方式2

  • 实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
    a:如何获取线程名称
    b:如何给线程设置名称
    c:实现接口方式的好处
    可以避免由于Java单继承带来的局限性。
public static void main(String[] args) {
   /* //创建线程的第二种方式
    创建线程的另一种方法是
    1.声明实现 Runnable 接口的类。
    2.该类然后实现 run 方法。
    3.然后可以分配该类的实例,
    4.在创建 Thread 时作为一个参数来传递并启动。*/

    // Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。

 /*   void run ()
    使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。*/

    //创建任务
    MyRunnable myRunnable = new MyRunnable();
    //把任务传递进来
    Thread th = new Thread(myRunnable,"刘亦菲");
    Thread th2 = new Thread(myRunnable,"范冰冰");
    th.start();
    th2.start();
}
public class MyRunnable implements Runnable {
        @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"==="+i);
        }
    }
}

多线程程序实现的方式3

实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。

执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类

实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程

public static void main(String[] args) {
    //创建线程的方式3
   /* C:
    实现步骤
    1. 创建一个类实现Callable 接口
    2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
    3. 创建Thread类, 将FutureTask对象作为参数传进去
    4. 开启线程*/
    MyCallable myCallable = new MyCallable();
    FutureTask<Integer> task = new FutureTask<>(myCallable);
    Thread thread = new Thread(task);
    thread.start();
}
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //call()将来是有线程来线程
        System.out.println("线程过来执行了");
        return null;
    }
}

继承Thread类的方式卖电影票案例

需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现

分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该–

public class MyTest {
    public static void main(String[] args) {
        CellThread th1 = new CellThread();
        CellThread th2 = new CellThread();
        CellThread th3 = new CellThread();
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
public class CellThread extends Thread{
    //共享数据,被多个线程所共享。
    static int piao = 100;
    @Override
    public void run() {
        while (true){
            if(piao>0){
              System.out.println(this.getName()+"正在出售:"+(piao--)+" 张票");
            }
        }
    }
}

实现Runnable接口的方式卖电影票

  • 需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
    通过实现Runnable接口实现
public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable);
        Thread th2 = new Thread(cellRunnable);
        Thread th3 = new Thread(cellRunnable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
public class CellRunnable implements Runnable{
    //共享数据,被多个线程所共享。
    int piao = 100;
    @Override
    public void run() {
        while (true) {
            if (piao > 0) {
                System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
            }
        }
    }
}

买电影票出现了同票和负数票的原因分析

加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒

线程安全问题的产生原因分析

  • 首先想为什么出现问题?(也是我们判断是否有问题的标准)
    是否是多线程环境
    是否有共享数据
    是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    基本思想:让程序没有安全问题的环境。
    怎么实现呢?
    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
  • 判断一个多线程应用程序是否有问题的标准:

    a: 是否是多线程环境

    b: 是否存在共享数据

    c: 是否存在多条语句同时操作共享数据

  • 我们现在这个程序是存在问题的,因为它满足上面的标准,那么我们只要将这个标准打乱,那么我们就可以解决这个问题.

  • 而上面的标准中a , b是不能打乱的,因此我们只能对c做处理,关键是怎么处理? 如果我们把操作共享数据的多条语句看做

  • 成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程

  • 不能进行执行,那么怎么做到这个一点呢?

  • 需要使用同步代码块:

  • 格式:

  • synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果

  • 要被同步的代码 ;

  • }

  • 这个同步代码块保证数据的安全性的一个主要因素就是这个对象
    注意这个对象 要定义为静态成员变量 才能被所有线程共享

  • 需要这个对象被所有的线程对象所共享

  • 这个对象其实就是一把锁.

  • 这个对象习惯叫做监视器

同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端

同步代码块的格式
	格式:
	synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
	死循环
	需要同步的代码;
}
  • 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
    同步的好处: 同步的出现解决了多线程的安全问题。
    同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

    在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
    因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
    java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
    线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
    获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
    当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
    直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
    java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
    但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
    类锁是用于类的静态方法或者一个类的class对象上的。
    我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
    所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
    但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
    它只是用来帮助我们理解锁定实例方法和静态方法的区别的.

public class CellRunnable implements Runnable{
    //共享数据,被多个线程所共享。
    static int piao = 100;
    static Object obj=new Object();
    int i=0;
    @Override
    public void run() {
        while (true) {
            if(i%2==0){
                //就是最后一张。piao=1;
                synchronized (CellRunnable.class) {
                    //加锁
                    try {
                        //模拟真实情况中,网络延迟的现象。
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (piao > 0) {
                        System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
                    }
                }
                //释放锁
            }else{
                maiPiao2();
                //释放锁
            }
            i++;
        }
    }
    //方法上加上关键字synchronized 同步方法
    //同步方法的默认锁对象是this
    public  synchronized void maiPiao(){

        //就是最后一张。piao=1;

            //加锁
            try {
                //模拟真实情况中,网络延迟的现象。
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (piao > 0) {
                System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
            }

    }
    //静态同步方法,用的锁对象是当前类的字节码文件对象
    public static synchronized void maiPiao2() {
        //就是最后一张。piao=1;
        //加锁
        try {
            //模拟真实情况中,网络延迟的现象。
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (piao > 0) {
            System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable);
        Thread th2 = new Thread(cellRunnable);
        Thread th3 = new Thread(cellRunnable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
        //++  --  i++  i--  不是一个原子性的操作。经过读 改 写。
        /*
        当我们模拟了一下真实售票,网络延迟的一个情况,会出现一些不合理的数据,也就是说出现了线程安全方面的问题。
           1.出现重复票的问题。是由于原子性所导致的,原子性,就是不可在分割。++ --
           2.出现负数票的问题,由于线程的随机性所导致的。
        * */

       /* 同步代码块的锁对象:
        任意一个对象
        同步方法的锁对象:是this
        静态同步方法的锁对象:就是当前类对应的字节码文件对象

        */
    }
}

死锁问题概述和使用

​ 如果出现了同步嵌套,就容易产生死锁问题
​ 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
​ 同步代码块的嵌套案例
​ 死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
​ 举例: 中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
​ 中国人获取到了美国人的刀
​ 美国人获取到了中国人的一根筷子

public interface ObjectUtils {
    //创建 两把锁对象
    Object objA=new Object();
    Object objB = new Object();
}
public class MyThread extends Thread{
    boolean flag;
    public MyThread(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
       // 两个或者两个以上的线程, 在抢占CPU的执行权的时候, 都处于等待状态
       if(flag){
           synchronized (ObjectUtils.objA){
               System.out.println("true 线程进来了 持有objA锁");
               synchronized (ObjectUtils.objB){
                   System.out.println("true 线程进来了 持有objB锁");
               }
           }//释放锁
       }else{
           synchronized (ObjectUtils.objB) {
               System.out.println("false 线程进来了 持有objB锁");
               synchronized (ObjectUtils.objA) {
                   System.out.println("false 线程进来了 持有objA锁");
               }
           }//释放锁
       }
    }
}
public class MyTest {
    public static void main(String[] args) {
        //死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
       /* 举例:
        中国人和美国人一起吃饭
                中国人使用的筷子
        美国人使用的刀和叉
                中国人获取到了美国人的刀
        美国人获取到了中国人的一根筷子*/
        MyThread th1 = new MyThread(true);
        MyThread th2= new MyThread(false);
        th1.start();
        th2.start();

    }
}

线程间的等待唤醒机制

Object 类中
void wait () 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
void wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。

void notify () 唤醒在此对象监视器上等待的单个线程。
void notifyAll () 唤醒在此对象监视器上等待的所有线程。

内存可见性问题 volatile

  • Java内存模型

    想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。
    Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,
    线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
    线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
    不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

  • Java中的可见性

    对于可见性,Java提供了volatile关键字来保证可见性。
    当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
    而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
    当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
    另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
    并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

  • volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
    相较于 synchronized 是一种较为轻量级的同步策略。
    volatile 变量,用来确保将变量的更新操作通知到其他线程。
    可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
     对于多线程,不是一种互斥关系
     不能保证变量状态的“原子性操作”

线程的状态转换图及常见执行情况

新建 , 就绪 , 运行 , 冻结 , 死亡
新建:线程被创建出来
就绪:具有CPU的执行资格,但是不具有CPU的执行权
运行:具有CPU的执行资格,也具有CPU的执行权
阻塞:不具有CPU的执行资格,也不具有CPU的执行权
死亡:不具有CPU的执行资格,也不具有CPU的执行权

线程池的概述和使用

  • 线程池概述
    程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
    而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
    线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
    在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • 内置线程池的使用概述
    JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
    public static ExecutorService newCachedThreadPool(): 根据任务的数量来创建线程对应的线程个数
    public static ExecutorService newFixedThreadPool(int nThreads): 固定初始化几个线程
    public static ExecutorService newSingleThreadExecutor(): 初始化一个线程的线程池
  • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
    Future<?> submit(Runnable task)
    Future submit(Callable task)
  • 使用步骤:
    创建线程池对象
    创建Runnable实例
    提交Runnable实例
    关闭线程池

单例模式之懒汉式

饿汉式和懒汉式的区别
单例设计模式之懒汉式

	开发中		饿汉式
	面试中		懒汉式

	面试就是想面试你们的两种思想:

		a: 线程安全思想
		b: 延迟加载思想
public class MyTest {
    public static void main(String[] args) {
        //单列模式:要保证内存中只有该类的一个对象。
        //单列模式的写法:1.懒汉式 2.饿汉式
        //1.私有构造
        Student student = Student.getStudent();
        Student student1 = Student.getStudent();
        System.out.println(student==student1);
    }
}
public class Student {
    //懒汉式
    private static Student student=null;
    //1.私有构造
    private Student() {
    }
    //2.提供一个公共的静态方法,来返回我该类的一个实例让外界来用。
    //设计为同步方法,保证多线程环境下也是单例的。
    public synchronized static Student getStudent(){
        if(student==null){
            student=new Student();
        }
        return student;
    }
}
public class MyTest {
    public static void main(String[] args) {
        Teacher teacher = Teacher.getTeacher();
        Teacher teacher1 = Teacher.getTeacher();
        System.out.println(teacher==teacher1);
        //开发中我们可以选用饿汉式
    }
}
public class Teacher {
    //饿汉式
    private static Teacher teacher = new Teacher();

    private Teacher() {
    }

    public static Teacher getTeacher() {
        return teacher;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值