什么是线程安全与ThreadGroup

发布时间:2021-10-11 17:23:37 作者:iii
来源:亿速云 阅读:116

这篇文章主要介绍“什么是线程安全与ThreadGroup”,在日常操作中,相信很多人在什么是线程安全与ThreadGroup问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是线程安全与ThreadGroup”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

简介

synchronized可以防止线程干扰和内存一致性错误,具体表现如下:

基本用法

synchronized的基本用法可以用于对代码块或方法进行修饰,比如:

private final Object MUTEX = new Object();
    
public void sync1(){
    synchronized (MUTEX){
    }
}

public synchronized void sync2(){
}

字节码简单分析

一个简单的例子如下:

public class Main {
    private static final Object MUTEX = new Object();

    public static void main(String[] args) throws InterruptedException {
        final Main m = new Main();
        for (int i = 0; i < 5; i++) {
            new Thread(m::access).start();
        }
    }

    public void access(){
        synchronized (MUTEX){
            try{
                TimeUnit.SECONDS.sleep(20);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

编译后查看字节码:

javap -v -c -s -l Main.class

access()字节码截取如下:

stack=3, locals=4, args_size=1
 0: getstatic     #9                  // Field MUTEX:Ljava/lang/Object;  获取MUTEX
 3: dup
 4: astore_1
 5: monitorenter					  // 执行monitor enter指令
 6: getstatic     #10                 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
 9: ldc2_w        #11                 // long 20l
12: invokevirtual #13                 // Method java/util/concurrent/TimeUnit.sleep:(J)V
15: goto          23				  // 正常退出,跳转到字节码偏移量23的地方
18: astore_2
19: aload_2
20: invokevirtual #15                 // Method java/lang/InterruptedException.printStackTrace:()V
23: aload_1
24: monitorexit						  // monitor exit指令
25: goto          33
28: astore_3
29: aload_1
30: monitorexit
31: aload_3
32: athrow
33: return

关于monitorentermonitorexit说明如下:

注意事项

非空对象

monitor关联的对象不能为空:

private Object MUTEX = null;
private void sync(){
    synchronized (MUTEX){

    }
}

会直接抛出空指针异常。

作用域不当

由于synchronized关键字存在排它性,作用域越大,往往意味着效率越低,甚至丧失并发优势,比如:

private synchronized void sync(){
    method1();
    syncMethod();
    method2();
}

其中只有第二个方法是并发操作,那么可以修改为

private Object MUTEX = new Object();
private void sync(){
    method1();
    synchronized (MUTEX){
        syncMethod();
    }
    method2();
}

使用不同的对象

因为一个对象与一个monitor相关联,如果使用不同的对象,这样就失去了同步的意义,例子如下:

public class Main {
    public static class Task implements Runnable{
        private final Object MUTEX = new Object();

        @Override
        public void run(){
            synchronized (MUTEX){
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new Thread(new Task()).start();
        }
    }
}

每一个线程争夺的monitor都是互相独立的,这样就失去了同步的意义,起不到互斥的作用。

死锁

另外,使用synchronized还需要注意的是有可能造成死锁的问题,先来看一下造成死锁可能的原因。

死锁成因

例子

public class Main {
    private final Object MUTEX_READ = new Object();
    private final Object MUTEX_WRITE = new Object();

    public void read(){
        synchronized (MUTEX_READ){
            synchronized (MUTEX_WRITE){
            }
        }
    }

    public void write(){
        synchronized (MUTEX_WRITE){
            synchronized (MUTEX_READ){
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();
        new Thread(()->{
            while (true){
                m.read();
            }
        }).start();
        new Thread(()->{
            while (true){
                m.write();
            }
        }).start();
    }
}

两个线程分别占有MUTEX_READ/MUTEX_WRITE,同时等待另一个线程释放MUTEX_WRITE/MUTEX_READ,这就是交叉锁造成的死锁。

排查

使用jps找到进程后,通过jstack查看:

什么是线程安全与ThreadGroup

可以看到明确的提示找到了1个死锁,Thread-0等待被Thread-1占有的monitor,而Thread-1等待被Thread-0占有的monitor

两个特殊的monitor

这里介绍两个特殊的monitor

this monitor

先上一段代码:

public class Main {
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName()+" method1");
        try{
            TimeUnit.MINUTES.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(Thread.currentThread().getName()+" method2");
        try{
            TimeUnit.MINUTES.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();
        new Thread(m::method1).start();
        new Thread(m::method2).start();
    }
}

运行之后可以发现,只有一行输出,也就是说,只是运行了其中一个方法,另一个方法根本没有执行,使用jstack可以发现:

什么是线程安全与ThreadGroup

一个线程处于休眠中,而另一个线程处于阻塞中。而如果将method2()修改如下:

public void method2(){
    synchronized (this) {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

效果是一样的。也就是说,在方法上使用synchronized,等价于synchronized(this)

class monitor

把上面的代码中的方法修改为静态方法:

public class Main {
    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " method1");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(Main::method1).start();
        new Thread(Main::method2).start();
    }
}

运行之后可以发现输出还是只有一行,也就是说只运行了其中一个方法,jstack分析也类似:

什么是线程安全与ThreadGroup

而如果将method2()修改如下:

public static void method2() {
    synchronized (Main.class) {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

可以发现输出还是一致,也就是说,在静态方法上的synchronized,等价于synchronized(XXX.class)

总结

ThreadGroup

简介

无论什么情况下,一个新创建的线程都会加入某个ThreadGroup中:

ThreadGroup中存在父子关系,一个ThreadGroup可以存在子ThreadGroup

创建

创建ThreadGroup可以直接通过构造方法创建,构造方法有两个,一个是直接指定名字(ThreadGroupmain线程的ThreadGroup),一个是带有父ThreadGroup与名字的构造方法:

ThreadGroup group1 = new ThreadGroup("name");
ThreadGroup group2 = new ThreadGroup(group1,"name2");

完整例子:

public static void main(String[] args) throws InterruptedException {
    ThreadGroup group1 = new ThreadGroup("name");
    ThreadGroup group2 = new ThreadGroup(group1,"name2");
    System.out.println(group2.getParent() == group1);
    System.out.println(group1.getParent().getName());
}

输出结果:

true
main

enumerate()

enumerate()可用于ThreadThreadGroup的复制,因为一个ThreadGroup可以加入若干个Thread以及若干个子ThreadGroup,使用该方法可以方便地进行复制。方法描述如下:

上述方法会将ThreadGroup中的活跃线程/ThreadGroup复制到Thread/ThreadGroup数组中,布尔参数表示是否开启递归复制。

例子如下:

public static void main(String[] args) throws InterruptedException {
    ThreadGroup myGroup = new ThreadGroup("MyGroup");
    Thread thread = new Thread(myGroup,()->{
        while (true){
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    },"MyThread");
    thread.start();
    TimeUnit.MILLISECONDS.sleep(1);
    ThreadGroup mainGroup = currentThread().getThreadGroup();
    Thread[] list = new Thread[mainGroup.activeCount()];
    int recurseSize = mainGroup.enumerate(list);
    System.out.println(recurseSize);
    recurseSize = mainGroup.enumerate(list,false);
    System.out.println(recurseSize);
}

后一个输出比前一个少1,因为不包含myGroup中的线程(递归设置为false)。需要注意的是,enumerate()获取的线程仅仅是一个预估值,并不能百分百地保证当前group的活跃线程,比如调用复制之后,某个线程结束了生命周期或者新的线程加入进来,都会导致数据不准确。另外,返回的int值相较起Thread[]的长度更为真实,因为enumerate仅仅将当前活跃的线程分别放进数组中,而返回值int代表的是真实的数量而不是数组的长度。

其他API

到此,关于“什么是线程安全与ThreadGroup”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. 线程安全与可重入函数
  2. java枚举是怎样保证线程安全的

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

threadgroup java

上一篇:python中怎么进行字符串处理

下一篇:Python中pyqt5网格布局QGridLayout是怎样的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》