千家信息网

什么是线程安全与ThreadGroup

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章主要介绍"什么是线程安全与ThreadGroup",在日常操作中,相信很多人在什么是线程安全与ThreadGroup问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
千家信息网最后更新 2026年11月01日什么是线程安全与ThreadGroup

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

简介

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

  • synchronized提供了一种锁机制,能够确保共享变量的互斥访问,从而防止数据不一致的问题

  • synchronized包括monitor enter和monitor exit两个JVM指令,能保证在任何时候任何线程执行到monitor enter成功之前都必须从主存获取数据,而不是从缓存中,在monitor exit运行成功之后,共享变量被更新后的值必须刷入主内存而不是仅仅在缓存中

  • synchronized指令严格遵循Happens-Beofre规则,一个monitor exit指令之前必定要有一个monitor enter

基本用法

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 20l12: invokevirtual #13                 // Method java/util/concurrent/TimeUnit.sleep:(J)V15: goto          23                               // 正常退出,跳转到字节码偏移量23的地方18: astore_219: aload_220: invokevirtual #15                 // Method java/lang/InterruptedException.printStackTrace:()V23: aload_124: monitorexit                                              // monitor exit指令25: goto          3328: astore_329: aload_130: monitorexit31: aload_332: athrow33: return

关于monitorenter与monitorexit说明如下:

  • monitorenter:每一个对象与一个monitor相对应,一个线程尝试获取与对象关联的monitor的时候,如果monitor的计数器为0,会获得之后立即对计数器加1,如果一个已经拥有monitor所有权的线程重入,将导致计数器再次累加,而如果其他线程尝试获取时,会一直阻塞直到monitor的计数器变为0,才能再次尝试获取对monitor的所有权

  • monitorexit:释放对monitor的所有权,将monitor的计数器减1,如果计数器为0,意味着该线程不再拥有对monitor的所有权

注意事项

非空对象

与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还需要注意的是有可能造成死锁的问题,先来看一下造成死锁可能的原因。

死锁成因

  • 交叉锁导致程序死锁:比如线程A持有R1的锁等待R2的锁,线程B持有R2的锁等待R1的锁

  • 内存不足:比如两个线程T1和T2,T1已获取10MB内存,T2获取了15MB内存,T1和T2都需要获取30MB内存才能工作,但是剩余可用的内存为10MB,这样两个线程都在等待彼此释放内存资源

  • 一问一答式的数据交换:服务器开启某个端口,等待客户端访问,客户端发送请求后,服务器因某些原因错过了客户端请求,导致客户端等待服务器回应,而服务器等待客户端发送请求

  • 死循环引起的死锁:比较常见,使用jstack等工具看不到死锁,但是程序不工作,CPU占有率高,这种死锁也叫系统假死,难以排查和重现

例子

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查看:

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

两个特殊的monitor

这里介绍两个特殊的monitor:

  • this monitor

  • class 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可以发现:

一个线程处于休眠中,而另一个线程处于阻塞中。而如果将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分析也类似:

而如果将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)。

总结

  • this monitor:在成员方法上的synchronized,就是this monitor,等价于在方法中使用synchronized(this)

  • class monitor:在静态方法上的synchronized,就是class monitor,等价于在静态方法中使用synchronized(XXX.class)

ThreadGroup

简介

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

  • 如果新建线程没有指定ThreadGroup,默认就是main线程所在的ThreadGroup

  • 如果指定了ThreadGroup,那么就加入该ThreadGroup中

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

创建

创建ThreadGroup可以直接通过构造方法创建,构造方法有两个,一个是直接指定名字(ThreadGroup为main线程的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());}

输出结果:

truemain

enumerate()

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

  • public int enumerate(Thread [] list)

  • public int enumerate(Thread [] list, boolean recurse)

  • public int enumerate(ThreadGroup [] list)

  • public int enumerate(ThreadGroup [] list, boolean recurse)

上述方法会将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

  • activeCount():获取group中活跃的线程,估计值

  • activeGroupCount():获取group中活跃的子group,也是一个近似值,会递归获取所有的子group

  • getMaxPriority():用于获取group的优先级,默认情况下,group的优先级为10,且所有线程的优先级不得大于线程所在group的优先级

  • getName():获取group名字

  • getParent():获取父group,如果不存在返回null

  • list():一个输出方法,递归输出所有活跃线程信息到控制台

  • parentOf(ThreadGroup g):判断当前group是不是给定group的父group,如果给定的group是自己本身,也会返回true

  • setMaxPriority(int pri):指定group的最大优先级,设定后也会改变所有子group的最大优先级,另外,修改优先级后会出现线程优先级大于group优先级的情况,比如线程优先级为10,设置group优先级为5后,线程优先级就大于group优先级,但是新加入的线程优先级必须不能大于group优先级

  • interrupt():导致所有的活跃线程被中断,递归调用线程的interrupt()

  • destroy():如果没有任何活跃线程,调用后在父group中将自己移除

  • setDaemon(boolean daemon):设置为守护ThreadGroup后,如果该ThreadGroup没有任何活跃线程,自动被销毁

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

线程 方法 优先级 死锁 内存 两个 对象 输出 计数器 例子 客户 客户端 指令 递归 运行 安全 也就是 也就是说 字节 就是 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司起名 互联网金融监管科技数据 盈实互联网科技 互联网与新科技 九亚互联网科技 北京互联网络科技有限公司 亿人帮互联网科技 山东吉时兔互联网科技有限公司 青海省互联网新科技 蓝色互联网科技 天速互联网络科技有限公司 宁波青枫互联网科技有限公司 央行互联网金融科技公司 深圳市优智互联网科技有限公司 西安小鹰速贷互联网科技有限公司 长沙茂晟互联网科技有限公司 滨湖区互联网智能科技产品有哪些 珠海东科互联网科技有限公司 广西泛欧互联网科技有限公司 中国主要互联网科技公司 互联网科技海报 深圳科技互联网 互联网科技媒体 掌上梦互联网科技 互联网科技公司的市场部门 呼和浩特新华互联网科技学院 佛山市翊博互联网络科技有限公司 北京市互联网新科技 中国互联网有什么科技 现代科技的标志之一的互联网
0