当前位置: 主页 > JAVA语言

java线程定时器-java线程安全的类

发布时间:2023-04-02 10:08   浏览次数:次   作者:佚名

线程概述 1. 进程 和 线程

请添加图片描述

2. 进程 和 线程 的区别 3. 并行 和 并发 4. 为什么使用多线程 为了更好的利用cpu的资源,若只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待进程之间不能共享数据,线程可以线程比进程更轻量:创建线程比创建进程更快;销毁线程比销毁进程更快 ;调度线程比调度进程更快 5. Java 的线程 和 操作系统线程 的关系 线程的生命周期

一般在两种情况下进入阻塞状态:

当线程 A 运行过程中试图获取同步锁时,却被线程 B 获取,此时 JVM 会把当前线程 A 存到对象的锁池中,线程 A 进入阻塞状态当线程运行过程中,发出 I/O 请求时,该线程也会进入阻塞状态

处于等待状态中的线程不能立即争夺 CPU 使用权,必须等待其他线程执行特定的操作后java线程定时器,才有机会将等待状态转换为运行状态

例如:等待其他线程调用 notify() 或者 notifyAll() 方法唤醒当前等待中的线程;调用 jion() 方法而处于等待状态中的线程,必须等待其他加入的线程终止

处于定时等待状态中的线程也不能立即争夺 CPU 使用权,必须等待其他相关线程执行完特定的操作或者限制的时间后,才有机会将等待状态转换为运行状态

例如:通过其他线程调用 notify() 或者 notifyAll() 方法唤醒当前等待中的线程,或者等待限时时间结束后也可以进行状态转换

线程的创建 1. 继承 Thread 类,重写 run() 方法

步骤:

创建一个 Thread 线程类的子类(子线程),同时重写 Thread 类的 run() 方法

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
   }
}

创建该子类的实例对象,并通过调用 start() 方法启动线程

MyThread t = new MyThread();
t.start(); // 线程开始运行

代码实现:

class myThread extends Thread {
    public void run() {
        for (int x = 0; x < 10; x++) {
            //currentThread(): Thread 类的静态方法,用来获取当前线程对象
            //getName(): 用来获取线程名称  
            System.out.println(Thread.currentThread().getName()+ "运行");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        myThread t1 = new myThread();
        t1.start();
        myThread t2 = new myThread();
        t2.start();
        for(int x = 0; x < 10; x++){
            System.out.println("main:"+x);
        }
    }
}

请添加图片描述

main() 方法中有一条主线程在运行

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

Java 只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承 Thread 类来实现多继承,此时,可以通过实现 Runnable 接口来实现多线程

步骤:

创建一个 Runnable 接口的实现类,同时重写接口中的 run() 方法

class myThread implements Runnable{
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
        }
    }
}

创建 Runnable 接口的实现类对象,使用 Thread 有参构造方法创建线程实例,并将 Runnable 接口的实现类的实例对象作为参数传入

//创建 Runnable 接口的实现类对象
myThread1 t1 = new myThread1();
//使用 Thread(Runnable target,String name) 构造方法创建线程对象
Thread t2 = new Thread(t1,"t2");
//调用线程对象的 start() 方法启动线程
t2.start();

Thread 的常见构造方法:

方法说明

Thread()

创建线程对象

Thread(Runnable target)

使用 Runnable 对象创建线程对象

Thread(String name)

创建线程对象,并命名

Thread(Runnable target, String name)

使用 Runnable 对象创建线程对象,并命名

代码实现:

class myThread1 implements Runnable{
    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            //currentThread(): Thread 类的静态方法,用来获取当前线程对象
            //getName(): 用来获取线程名称
            System.out.println(Thread.currentThread().getName()+ " 运行");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        myThread1 t = new myThread1();
        Thread t2 = new Thread(t,"顺顺");
        t2.start();
        //创建并启动另一个线程
        Thread t3 = new Thread(t,"利利");
        t3.start();
    }
}

请添加图片描述

java线程解释器_java线程安全的类_java线程定时器

其他实现:

//1:将 Runnable 实例传给 Thread
    public static void main(String[] args) {
        Thread t = new Thread(new myThread1());
        t.start();
    }
//2:创建匿名内部类
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                for (int x = 0; x < 10; x++) {
                    System.out.println(Thread.currentThread().getName()+ "运行");
                }
            }
        };
        t.start();
    }   
//3:使用匿名类创建 Runnable 子类对象
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int x = 0; x < 10; x++) {
                    System.out.println(Thread.currentThread().getName()+ "运行");
                }
            }
        });
        t.start();
    } 
//4:lambda 表达式
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int x = 0; x < 10; x++) {
                System.out.println(Thread.currentThread().getName()+ "运行");
            }
        });
        t.start();
    }

3. 实现 Callable 接口,重写 call() 方法,并使用 Future 来获取 call() 方法的返回结果

Thread 类和 Runnable 接口实现多线程时需要重写 run() 方法,但 run() 方法没有返回值,因此无法从多个线程中获取返回结果,此时,可以使用 Callable 接口来满足既创建多线程又有返回值的需求

步骤:

创建一个 Callable 接口的实现类,同时重写 call() 方法创建 Callable 接口的实现类对象通过 FutureTask 线程结果处理类的有参构造方法封装 Callable 接口实现类对象使用参数为 FutureTask 类对象的 Thread 有参构造方法创建 Thread 线程实例调用线程实例的 Start() 方法启动线程

代码实现:

class myThread2 implements Callable<Object>{
    //重写 call() 方法
    @Override
    public Object call() throws Exception {
        int x = 0;
        for (; x < 10; x++) {
            System.out.println(Thread.currentThread().getName()+ "运行");
        }
        return x;
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建 Callable 接口的实现类对象
        myThread2 t1 = new myThread2();
        //通过 FutureTask 封装 Callable 接口实现类对象
        FutureTask<Object> f1 = new FutureTask<>(t1);
        //使用 Thread 有参构造方法创建线程对象
        Thread t11 = new Thread(f1,"ABC");
        t11.start();
        FutureTask<Object> f2 = new FutureTask<>(t1);
        Thread t22 = new Thread(f2,"DEF");
        t22.start();
        System.out.println("t11 返回结果:" + f1.get());
        System.out.println("t22 返回结果:" + f2.get());
    }
}

请添加图片描述

Callable 和 Runnable 相对, 都是描述一个 “任务”。 Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务Callable 通常需要搭配 FutureTask 来使用,FutureTask 用来保存 Callable 的返回结果。因为Callable 往往是在另一个线程中执行的, 什么时候执行完并不确定,FutureTask 就可以负责这个等待结果出来的工作 4. 三种方式对比

三种方式中,Runnable 接口和 Callable 接口 实现多线程的方式基本相同,主要区别是 Callable 接口方法中有返回值,并且可以声明抛异常

Thread 和 Runnable 的区别:

Runnable 接口(或 Callable 接口)比起 Thread 类 来说,更适合多个线程处理同一个共享资源的情况Runnable 接口(或 Callable 接口)可以避免 Java 单继承带来的局限性

class Ticket extends Thread{
    private int tickets = 20;
    @Override
    public void run() {
        while(true){
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张车票");
            }
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        t1.start();
        Ticket t2 = new Ticket();
        t2.start();
        Ticket t3 = new Ticket();
        t3.start();
        Ticket t4 = new Ticket();
        t4.start();
    }
}

请添加图片描述

20张车票,每张只能出售1次,但实际运行中每张车票被出售了4次。原因是创建的4个线程没有共享这20张车票,在这4个售票窗口中都有一个 tickets 变量,导致每个线程在执行时都会独立处理各自的资源

为了保证售票资源共享,在程序中只能创建一个售票对象,然后开启多个线程共享的去处理,此时需要通过 Runnable 接口来实现

class Ticket2 implements Runnable{
    private int tickets = 20;
    @Override
    public void run() {
        while(true){
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets-- + " 张车票");
            }
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        Ticket2 t = new Ticket2();
        Thread t1 = new Thread(t,"窗口1");
        t1.start();
        Thread t2 = new Thread(t,"窗口2");
        t2.start();
        Thread t3 = new Thread(t,"窗口3");
        t3.start();
        Thread t4 = new Thread(t,"窗口4");
        t4.start();
    }
}

请添加图片描述

5. 后台线程

对 Java 程序来说,只要还有一个前台程序在运行,这个程序就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束

前台进程和后台进程是相对的概念,新创建的线程默认都是前台线程,当某个线程对象在启动之前调用了 setDaemon(true) 语句,这个线程就变成了一个后台进程

关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行

class demoThread implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName() + " 正在运行");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        System.out.println("main 线程是否为后台线程:" + Thread.currentThread().isDaemon());
        demoThread demoThread = new demoThread();
        Thread t = new Thread(demoThread,"后台线程");
        System.out.println("t 线程是否为后台线程:" + t.isDaemon());
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main: " + i);
        }
    }
}

请添加图片描述

setDaemon(true) 方法必须在 start() 方法之前调用,否则设置无效以上代码:当前台主线程序任务执行完,整个进程就会结束,此时 JVM 通知后台线程结束,但后台线程从接受指令到做出响应,需要一定时间,因此会打印多次 " 后台线程 正在运行 " Thread 类及常见方法 1. Thread 的常见构造方法: 方法说明

Thread()

创建线程对象

Thread(Runnable target)

使用 Runnable 对象创建线程对象

Thread(String name)

创建线程对象,并命名

Thread(Runnable target, String name)

使用 Runnable 对象创建线程对象,并命名

Thread(ThreadGroup group, Runnable target)

线程可以被用来分组管理,分好的组即为线程组

2. Thread 的几个常见属性: 属性获取方法

ID

java线程定时器_java线程解释器_java线程安全的类

getId()

名称

getName()

状态

getState()

优先级

getPriority()

是否后台线程

isDaemon()

是否存活

isAlive()

是否被中断

isInterrupted()

public class duoxiancheng {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 运行"+ i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 即将结束");
        });
        System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
    }

请添加图片描述

3. 中断线程 3.1 使用自定义的变量来作为标志位.

需要给标志位上加 volatile 关键字

class myTicket implements Runnable {
    public volatile boolean isQuit = false;
    @Override
    public void run() {
        while (!isQuit) {
            System.out.println(Thread.currentThread().getName() + ": 正在售票");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":暂停售票");
    }
 }
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        myTicket mt = new myTicket();
        Thread t = new Thread(mt, "窗口一");
        System.out.println(Thread.currentThread().getName() + ": 窗口一开始售票");
        t.start();
        Thread.sleep(5 * 1000);
        System.out.println(Thread.currentThread().getName() + ": 让窗口一停止售票");
        mt.isQuit = true;
    }
}

在这里插入图片描述

3.2 调用 interrupt() 方法来通知

使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记

方法说明

public void interrupt()

中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位

public static boolean interrupted()

判断当前线程的中断标志位是否设置,调用后清除标志位

public boolean isInterrupted()

判断对象关联的线程的标志位是否设置,调用后不清除标志位

class myTicket1 implements Runnable {
    @Override
    public void run() {
        while (!Thread.interrupted()) {// 两种方法均可以
            // while (!Thread.currentThread().isInterrupted()) {
             System.out.println(Thread.currentThread().getName() + ": 正在售票");
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
                 System.out.println(Thread.currentThread().getName() + ": 收到!");
                 // 注意此处的 break
                  break;
             }
        }
        System.out.println(Thread.currentThread().getName() + ": 暂停售票");
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        myTicket1 mt = new myTicket1();
        Thread t = new Thread(mt, "窗口一");
        System.out.println(Thread.currentThread().getName() + ": 窗口一开始售票");
        t.start();
        Thread.sleep(5 * 1000);
        System.out.println(Thread.currentThread().getName() + ": 让窗口一停止售票");
        t.interrupt();
    }
}

请添加图片描述

4. 等待线程 方法说明

public void join()

等待线程结束

public void join(long millis)

等待线程结束,最多等 millis 毫秒

public void join(long millis, int nanos)

同理,但可以更高精度

java线程安全的类_java线程定时器_java线程解释器

例如:排队买票时,先等前一个人买完,后一个再买

public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 正在买票");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            System.out.println(Thread.currentThread().getName() + ": 已买到");
        };
        Thread t1 = new Thread(target, "张三");
        Thread t2 = new Thread(target, "李四");
        System.out.println("张三开始买票");
        t1.start();
        t1.join();
        System.out.println("张三买完票了,李四开始买票");
        t2.start();
        t2.join();
        System.out.println("李四买完票了");
    }
}

请添加图片描述

线程的调度

程序中的多个线程是并发执行的,但并不是同一时刻执行,某个线程若想被执行必须要得到 CPU 的使用权,Java 虚拟机会按照特定的机制为程序中的每个线程分配 CPU 的使用权,这种机制被称作线程的调度

线程的调度有两种模型:

分时调度模型:指让所有的线程轮流获得 CPU 的使用权,并且平均分配每个线程占用的 CPU 时间片抢占式调度模型:指让可运行池中所有就绪状态的线程争抢 CPU 的使用权,优先级高的线程获取 CPU 执行权的概率大于优先级低的线程

Java虚拟机默认采用抢占式调度机制 1. 线程的优先级

线程的优先级用 1~10 之间的整数来表示,数字越大优先级越高,除此之外还可以使用 Thread 类中提供的三个静态常量表示线程的优先级

Thread 类的静态常量功能描述

static int MIN_PRIORITY

表示线程最低优先级,相当于1

static int NORM_PRIORITY

表示线程普通优先级,相当于5

static int MAX_PRIORITY

表示线程最高优先级,相当于10

程序运行期间,处于就绪状态的每个线程都有自己的优先级,main 线程具有普通优先级

线程的优先级是可变的,通过 Thread 类的 setPriority(int newPriority) 方法可以对其进行设置

class DemoTest1 implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 3; x++) {
            System.out.println(Thread.currentThread().getName() + " : " + x);
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        DemoTest1 d = new DemoTest1();
        Thread t1 = new Thread(d,"A");
        Thread t2 = new Thread(d,"B");
        Thread t3 = new Thread(d,"C");
        //设置线程的优先级
        t3.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(1);
        t1.start();
        t2.start();
        t3.start();
    }
}

请添加图片描述

虽然Java中提供了10个线程优先级,但是这些优先级需要操作系统的支持,不同的操作系统对优先级的支持是不一样的,不能很好的和Java中线程优先级一一对应,因此,在设计多线程应用程序时,其功能的实现一定不能依赖于线程的优先级,而只能把线程优先级作为一种提高程序效率的手段

2. 线程休眠

如果人为控制线程执行顺序,使正在执行的线程暂停java线程定时器,将 CPU 的使用权让给其他线程,可以使用 sleep() 方法来实现,该方法会声明抛出 InterruptedException 异常,因此调用该方法时应捕获异常,或者声明抛出改异常

方法说明

public static void sleep(long millis) throws InterruptedException

休眠当前线程 millis 毫秒

public static void sleep(long millis, int nanos) throws InterruptedException

可以更高精度的休眠

因为线程的调度是不可控的,所以只能保证实际休眠时间是大于等于参数设置的休眠时间

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                if(i == 3){
                    try{
                        Thread.sleep(600);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int j = 5; j < 10; j++) {
                System.out.println(Thread.currentThread().getName() + " : " + j);
            }
        });
        t1.start();
        t2.start();
    }
}

请添加图片描述

当 t1 执行到 i==2 时,进入睡眠状态,t2 会获得 CPU 使用权,直到 t1 线程休眠时间消耗完才有机会获得 CPU 使用权

当其他程序都终止后并不代表当前休眠的线程就会立即执行,而是必须等休眠时间结束,线程才会转换到就绪状态

3. 线程让步

线程让步通过 yield() 方法来实现,该方法和 sleep() 类似,都可以让当前正在运行的线程暂停,但 yield() 方法不会阻塞该线程,只是将线程转换为就绪状态,让系统重新调度一次

class DemoTest2 implements Runnable{
    @Override
    public void run(){
        for(int i = 0 ; i < 5 ; i++){
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+" : "+i);
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        DemoTest2 d = new DemoTest2();
        Thread t1 = new Thread(d,"A");
        Thread t2 = new Thread(d,"B");
        t1.start();
        t2.start();
        for(int i = 0; i < 5 ; i++){
            System.out.println("main : "+i);
        }
    }
}

请添加图片描述

线程让步不会阻塞该线程,它只是将线程转换成就绪状态线程休眠,是让线程在一定时间内进入休眠等待状态,到达时间后线程再转换成就绪状态 4. 线程插队

java线程安全的类_java线程定时器_java线程解释器

当在某个线程中调用其他线程的 join() 方法时,调用的函数将被阻塞,直到被 join() 方法加入的线程执行完之后才会继续运行

class DemoTest3 implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getName() + " : " + x);
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        DemoTest3 d = new DemoTest3();
        Thread t1 = new Thread(d,"A");
        t1.start();
        for (int i = 5; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            if(i == 6){
                t1.join();
            }
        }
    }
}

请添加图片描述

线程插队,可以让插队的线程先执行完,然后本线程才开始执行使用线程插队 join() 方法时,需要抛出 InterruptedException 异常 线程安全

当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的

下面这个代码就不是线程安全的:

class Counter {
    public int count = 0;
    void increase() {
        count++;
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

请添加图片描述

原因:

上面的线程不安全的代码中,涉及到多个线程针对 counter.count 变量进行修改,此时这个 counter.count 是一个多个线程都能访问到的 " 共享数据 "

1. 线程不安全 修改共享数据:上一个代码中,counter.count 这个变量就是在堆上, 因此可以被多个线程共享访问原子性:多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到代码顺序性:如果是在单线程情况下,JVM、CPU指令集会对代码进行优化,这种叫做指令重排序,但是在多线程环境下就没那么容易了

保证线程安全的思路

使用没有共享资源的模型适用共享资源只读,不写的模型 直面线程安全 2. 怎么保证线程安全 1. synchronized 关键字

对这个代码进行小修改,就能保证线程安全

class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
    }
}

1.1synchronized 的特性 互斥:synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待

进入 synchronized 修饰的代码块, 相当于 加锁

退出 synchronized 修饰的代码块, 相当于 解锁 阻塞等待:

针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试进行加锁就会失败, 进入阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的线程, 再来获取到这个锁理解:

假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则 刷新内存:

synchronized 的工作过程:

获得互斥锁从主内存拷贝变量的最新副本到工作的内存执行代码将更改后的共享变量的值刷新到主内存释放互斥锁

所以 synchronized 也能保证内存可见性

可重入:synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题 把自己锁死:一个线程没有释放锁,然后又尝试再次加锁在可重入锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息

如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.

解锁的时候计数器递减为 0 的时候, 才是真正释放锁 1.2 synchronized 的使用 直接修饰普通方法

class SynchronizedDemo {
    public synchronized void methond() {
    
   }
}

class Ticket4 implements Runnable{
    private int tickets = 10;
    @Override
    public void run() {
        while(true){
            sale();
        }
    }
    private synchronized void sale(){
        if(tickets > 0){
            try{
                Thread.sleep(200);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets-- + " 张车票");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        Ticket4 t = new Ticket4();
        Thread t1 = new Thread(t,"窗口一");
        Thread t2 = new Thread(t,"窗口二");
        Thread t3 = new Thread(t,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

修饰静态方法

class SynchronizedDemo {
    public synchronized static void method() {
   }
}

修饰代码块: 明确指定锁哪个对象

class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            
       }
   }
}

class Ticket3 implements Runnable{
    private int tickets = 10;
    Object lock = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (lock){
                if(tickets > 0){
                    try{
                        Thread.sleep(200);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets-- + " 张车票");
                }
            }
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        Ticket3 t = new Ticket3();
        Thread t1 = new Thread(t,"窗口一");
        Thread t2 = new Thread(t,"窗口二");
        Thread t3 = new Thread(t,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

两个线程竞争同一把锁,才会产生阻塞等待

两个线程分别尝试获取两把不同的锁,不会产生竞争

锁对象:

同步方法的锁就是当前调用该方法的对象,也就是 this 指向的对象同步静态方法的锁是该方法所在类的 class 对象,该对象可以直接用 " 类名.class " 的方式获取同步代码块的锁是自己定义的任意类型的对象

加锁的特点:

java线程解释器_java线程安全的类_java线程定时器

解决了共享数据时的线程安全问题在线程执行同步代码时每次都要判断锁的状态,非常消耗资源,效率较低 2. Lock锁

Lock 锁可以让某个线程在持续获取同步锁失败后返回,不再继续等待

ReentrantLock 的用法:

class lockT implements Runnable{
    private int tickets = 10;
    private final Lock l = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            l.lock();//加锁
            if(tickets > 0){
                try{
                    Thread.sleep(200);
                    System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets-- + " 张车票");
                }catch(InterruptedException e){
                    e.printStackTrace();
                }finally {
                    l.unlock();//解锁
                }
            }else {
                l.unlock();
                break;
            }
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) {
        lockT t = new lockT();
        new Thread(t,"窗口一").start();
        new Thread(t,"窗口二").start();
        new Thread(t,"窗口三").start();
    }
}

3. ReentrantLock 和 synchronized 的区别: synchronized 是一个关键字, 是 JVM 内部实现的

ReentrantLock 是标准库的一个类, 在 JVM 外实现的synchronized 使用时不需要手动释放锁

ReentrantLock 使用时需要手动释放,使用起来更灵活,但是也容易遗漏 unlocksynchronized 在申请锁失败时会死等

ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃synchronized 是非公平锁

ReentrantLock 默认是非公平锁,可以通过构造方法传入一个 true 开启公平锁模式更强大的唤醒机制

synchronized 是通过 Object 的 wait / notify 实现等待-唤醒,每次唤醒的是一个随机等待的线程

ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程 4. 锁的选择 锁竞争不激烈的时候, 使用 synchronized,效率更高,自动释放更方便锁竞争激烈的时候,使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为,而不是死等如果需要使用公平锁,使用 ReentrantLock volatile 关键字

volatile 修饰的变量, 能够保证 “内存可见性”

代码在写入 volatile 修饰的变量的时候:

代码在读取 volatile 修饰的变量的时候:

volatile 和 synchronized 有着本质的区别,synchronized 能够保证原子性,volatile 保证的是内存可见性

public class duoxiancheng {
    static class Counter1 {
        public volatile int flag = 0;
    }
    public static void main(String[] args) {
        Counter1 c = new Counter1();
        Thread t1 = new Thread(() -> {
            while (c.flag == 0) {
                // do nothing
            }
            System.out.println("循环结束!");
        });
        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            c.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

wait 和 notify

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知, 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序

完成这个协调工作, 主要涉及到三个方法

1. wait()方法

wait():

wait 要搭配 synchronized 来使用,脱离 synchronized 使用 wait 会直接抛出异常

wait 结束等待的条件:

2. notify()方法

notify 方法是唤醒等待的线程

class WaitTask implements Runnable {
    private Object lock;
    public WaitTask(Object locker) {
        this.lock = locker;
    }
    @Override
    public void run() {
        synchronized (lock) {
            while (true) {
                try {
                    System.out.println("wait 开始");
                    lock.wait();
                    System.out.println("wait 结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class NotifyTask implements Runnable {
    private Object lock;
    public NotifyTask(Object locker) {
        this.lock = locker;
    }
    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("notify 开始");
            lock.notify();
            System.out.println("notify 结束");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

3. notifyAll()方法

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程

class WaitTask implements Runnable {
    private Object lock;
    public WaitTask(Object locker) {
        this.lock = locker;
    }
    @Override
    public void run() {
        synchronized (lock) {
            while (true) {
                try {
                    System.out.println(Thread.currentThread().getName() + "wait 开始");
                    lock.wait();
                    System.out.println(Thread.currentThread().getName() + "wait 结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class NotifyTask implements Runnable {
    private Object lock;
    public NotifyTask(Object locker) {
        this.lock = locker;
    }
    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + "notify 开始");
            lock.notifyAll();
            System.out.println(Thread.currentThread().getName() + "notify 结束");
        }
    }
}
public class duoxiancheng {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new WaitTask(locker));
        Thread t3 = new Thread(new WaitTask(locker));
        Thread t4 = new Thread(new NotifyTask(locker));
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000);
        //虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁
        //所以并不是同时执行, 而仍然是有先有后的执行
        t4.start();
    }
}

请添加图片描述

4. notify 和 notifyAll notify 只唤醒等待队列中的一个线程. 其他线程还是等着notifyAll 一下全都唤醒, 需要这些线程重新竞争锁 5. wait 和 sleep wait 需要搭配 synchronized 使用,sleep 不需要wait 是 Object 的方法 ,sleep 是 Thread 的静态方法 死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止

死锁产生的四个必要条件:

互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁。死锁的情况下如果打破上述任何一个条件,便可让死锁消失

怎么避免死锁

最容易破坏的是:循环等待

最常用的一种死锁阻止技术就是锁排序,假设有 N 个线程尝试获取 M 把锁,就可以针对 M 把锁进行编号(1, 2, 3…M),N 个线程尝试获取锁的时候,都按照固定编号由小到大顺序来获取锁,这样就可以避免环路等待

public class duoxiancheng {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (lock1) {
                    synchronized (lock2) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        };
        //约定好先获取 lock1, 再获取 lock2 , 就不会环路等待
        Thread t2 = new Thread() {
            @Override
            public void run() {
                synchronized (lock1) {
                    synchronized (lock2) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }
}