【Java】JUC并发(线程的方法、多线程的同步并发)

线程的方法

一、线程的插队:join()方法

1、作用

暂停当前线程的执行,直到调用join()的目标线程执行完毕,但不影响同一时刻的其他线程。

java 复制代码
// 使用join()
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            System.out.println("子线程 => begin");

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

            System.out.println("子线程 => end");
        });

        t.start();

        // t线程插队到主线程(当前线程)前面
        // t线程结束后,主线程才会继续执行
        // t.join() 相当于 t.join(0 )
        t.join();
        System.out.println("主线程 => end");
    }
}

子线程 => begin
子线程 => end
主线程 => end

java 复制代码
// 不使用join()
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            System.out.println("子线程 => begin");

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

            System.out.println("子线程 => end");
        });

        t.start();

        // t线程插队到主线程(当前线程)前面
        // t线程结束后,主线程才会继续执行
        // t.join() 相当于 t.join(0 )
//        t.join();
        System.out.println("主线程 => end");
    }
}

主线程 => end
子线程 => begin
子线程 => end

2、重载形式

  • join() : 无限等待,直到目标线程终止
  • join(long millis):等待指定毫秒,超时不再等待
  • join(long millis, int nanos) :精确到纳秒,超时不再等待

3、使用场景(多个线程要求按序执行)

java 复制代码
public class Test02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            for (int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName() + "->" + i);
            }
        },"数字线程");

        Thread t2 = new Thread(()->{
            try {
                // 等待t1线程完成
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            for (char i = 'A';i<'E';i++){
                System.out.println(Thread.currentThread().getName() + "->" + i);
            }
        },"字母线程");

        Thread t3 = new Thread(()->{
            try {
                // 等待t2线程完成
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            for (char i = 'A';i<'E';i++){
                System.out.println(Thread.currentThread().getName() + "->" + (char)(Math.random()*100));
            }
        },"符号线程");

        t1.start();
        t2.start();
        t3.start();
    }
}

数字线程->0
数字线程->1
数字线程->2
数字线程->3
数字线程->4
字母线程->A
字母线程->B
字母线程->C
字母线程->D
符号线程->I
符号线程->&
符号线程->
符号线程->

4、join()和sleep()的区别

  • join()执行过程中,会释放当前线程的锁;sleep()执行过程中,不会释放当前线程的锁。
  • join()是用过wait/notify机制思想;sleep()通过操作系统的定时器来实现。
  • join()主要实现线程任务编排;sleep()用与模仿耗时操作。

二、线程的中断:interrupt()方法

1、作用

将该线程的中断状态设置为true,线程的下一步动作取决于中断状态。

2、实现原理

支持中断的方法(sleep()、join()、wait()等方法)在执行过程中,会监视中断状态,一旦发现其状态为"true",就会抛出一个中断异常InterruptedException,并给等待状态发出一个中断信号,从而退出等待状态。注意:当线程中没有可以监听中断状态的方法时,interrupt()方法将不起作用。

java 复制代码
public class Test03 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            // 场景1 : 调用休眠方法(支持中断方法)
            System.out.println("子线程开始执行!");
            try {
                // 子线程休眠4秒
                Thread.sleep(1000*4);
            } catch (InterruptedException e) {
                System.out.println("子线程中断!");
                return;
            }
            System.out.println("子线程结束!");
        });
        t.start();

        try {
            // 主线程休眠3秒
            Thread.sleep(1000*3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        t.interrupt();
        System.out.println("主线程结束!");
    }
}

当子线程处于休眠状态时,而主线程对子线程进行中断则会子线程中断,不会继续向下执行。

子线程开始执行!
主线程结束!
子线程中断!

java 复制代码
public class Test03 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            // 场景2 : 判断线程的中断状态
            while (!isinterrupt){
                System.out.println("子线程开始执行!!");
            }

            System.out.println("子线程中断!");
        });
        t.start();
                try {
            Thread.sleep(1000*3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t.interrupt();
        System.out.println("主线程结束!");
    }
}

当没有使用中断方法时,子线程的中断状态一直时false,while的条件一
直为true会一直执行,当使用了中断方法后,中断状态更新为true,则会
退出while循环继续向下执行。

三、线程的让出:yield()方法

1、作用

让当前获取CPU的线程,主动让出CPU不执行。

java 复制代码
public class demo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run() {
                Thread.yield();
                for (int i = 0;i<5;i++){
                    System.out.println(i);
                }
            }
        };
        
        Thread t2 = new Thread(){
            public void run() {
                for (char i = 'A';i<'E';i++){
                    System.out.println(i);
                }
            }
        };
        
        t1.start();
        t2.start();
    }
}

四、守护线程(Daemon Thread)

1、用户线程和守护线程

用户线程:通常为创建的普通线程

守护线程:守护线程执行结束后,虚拟机不会自定退出;而非守护线程执行完毕后,虚拟机会自动退出

2、设置守护线程

在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程。

java 复制代码
Thread t1 = new Thread();
t1.setDaemon(true);
t1.start();

多线程同步并发

一、多线程的数据不一致

当多个线程同时运行时,线程的调度有操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令出被系统暂停,然后莫个时间段后继续执行。如果多个线程同时共享一个资源,则会出现数据不一致的问题。

java 复制代码
public class Test04 {
    public static void main(String[] args) {
        Thread add = new Thread(new AddThread());
        Thread dec = new Thread(new DecThread());

        add.start();
        dec.start();

        try {
            add.join();
            dec.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Count.count);
        
    }
}
class Count{
    public static int count = 0; //公共的累加值
    public static Object lock = new Object(); // 锁对象
}

// 累加线程
class AddThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i<10000;i++){
                Count.count += 1;
            }
        }
    }
}
// 累减线程
class DecThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i<10000;i++){
                Count.count -= 1;
            }
        }
    }
}


由于算数运算没有原子性所以会出现算数运算被覆盖的情况,从而导致结果不正确
-1234

二、 synchronized关键字

1、什么是synchronized

Synchronized关键字,也可以理解为一种同步锁。他可以对一段代码进行加锁和解锁,从而使其拥有原子性,从而确保代码的线程安全。

2、synchronized 的用法

  • 修饰实例方法:synchronized 修饰实例方法,则用到的锁,默认为 this 当前方法调用对象;
java 复制代码
public class Test05 {
    public static void main(String[] args) {
        Foo f1 = new Foo();
        Foo f2 = new Foo();
        Thread t1 = new Thread(()->{
            f1.add();
        });

        Thread t2 = new Thread(()->{
            f1.dec();
        });
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Foo.counter);
    }
}
class Foo{
    public static int counter = 0;

    public void add(){
        synchronized (this){
            for (int i = 0;i<10000;i++){
                counter++;
            }
        }
    }

    // 修饰实例方法,使用this关键字做锁
    public synchronized void dec(){
        for (int i=0;i<10000;i++){
            counter--;
        }
    }
}
  • 修饰静态方法:synchronized 修饰静态方法,则其所用的锁,默认为 Class 对象;
java 复制代码
public class Test05 {
    public static void main(String[] args) {
        Foo f1 = new Foo();
        Foo f2 = new Foo();
        Thread t1 = new Thread(()->{
            f1.add();
        });

        Thread t2 = new Thread(()->{
            f1.dec();
        });
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Foo.counter);
    }
}
class Foo{
    public static int counter = 0;

    public void add(){
        synchronized (this.getClass()){
            for (int i = 0;i<10000;i++){
                counter++;
            }
        }
    }

    public synchronized static void dec(){
        for (int i=0;i<10000;i++){
            counter--;
        }
    }
}
  • 修饰代码块:synchronized 修饰代码块,则其所用的锁,是某个指定 Java 对象
java 复制代码
public class Test04 {
    public static void main(String[] args) {
        Thread add = new Thread(new AddThread());
        Thread dec = new Thread(new DecThread());

        add.start();
        dec.start();

        try {
            add.join();
            dec.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Count.count);

    }
}

class Count{
    public static int count = 0; //公共的累加值
    public static Object lock = new Object(); // 锁对象
}

// 累加线程
class AddThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i<10000;i++){
            // synchronized 同步锁
            synchronized (Count.lock) {// 加锁
                // 临界区
                Count.count += 1;
            }// 释放锁
        }
    }
}

// 累减线程
class DecThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i<10000;i++){
            // synchronized 同步锁
            synchronized (Count.lock) {// 加锁
                // 临界区
                Count.count -= 1;
            }// 释放锁
        }
    }
}
相关推荐
艾莉丝努力练剑3 分钟前
【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)
c语言·开发语言·数据结构·学习·算法·链表
zyhomepage5 分钟前
科技的成就(六十九)
开发语言·网络·人工智能·科技·内容运营
珊瑚里的鱼11 分钟前
第十三讲 | map和set的使用
开发语言·c++·笔记·visualstudio·visual studio
逑之20 分钟前
C++笔记1:命名空间,缺省参数,引用等
开发语言·c++·笔记
songroom25 分钟前
【转】Rust: PhantomData,#may_dangle和Drop Check 真真假假
开发语言·后端·rust
LJianK131 分钟前
Java和JavaScript的&&和||
java·javascript·python
RealmElysia39 分钟前
java反射
java·开发语言
野蛮人6号1 小时前
黑马点评系列问题之p63unlock.lua不知道怎么整
java·redis·黑马点评
深度混淆1 小时前
C#,List<T> 与 Vector<T>
开发语言·c#·vector·list·simd
lixzest1 小时前
C++ 中两个类之间的通信方式
开发语言·c++