一、多线程常见注意事项

1:线程安全示例

在这里插入图片描述
线程1:a=5 执行 a – 此时 a=4 cpu切换线程执行
线程2: a=4 执行a-- 此时 a=3 打印a=3
线程1:打印,此时a=3
线程3:a=3 执行a-- 此时 a=2,打a=2
线程4:a=2执行a-- 此时 a=1,打a=1
线程5:a=1 执行a-- 此时 a=0,打a=0

可以看出线程1还未结束a的值已经被线程2减一等于3了。

可以在方法上加 synchronized 关键字 解决线程安全问题,如下
在这里插入图片描述

2、同步方法在执行中是否可以调用非同步方法?

package sync;
/**
 * 同步方法在执行中是否可以调用非同步方法?
 * 答案是可以的,线程2执行testMechod方法
 * 在线程1没有结束syncMethod方法时就执行完毕了
 * 而没有说去等到线程1执行syncMethod方法完毕后
 * 再去执行testMechod方法,这就说明同步方法中是可以调用非同步方法的

 * @author xzq
 */
public class Synchronize02 {
    public static synchronized void syncMethod() throws InterruptedException{
        String threadName=Thread.currentThread().getName();
        System.out.println(threadName+":开始执行syncMethod方法……");
        //休眠5秒,模拟处理业务逻辑
        Thread.sleep(5000);
        System.out.println(threadName+":执行syncMethod方法结束!");
    }

    public static void testMechod() throws InterruptedException{
        String threadName=Thread.currentThread().getName();
        System.out.println(threadName+":开始执行testMechod方法……");
        //休眠3秒,模拟处理业务逻辑
        Thread.sleep(3000);
        System.out.println(threadName+":执行testMechod方法结束!");
    }

    public static void main(String[] args) {
        new Thread("线程1"){
            @Override
            public void run() {
                try {
                    syncMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();


        new Thread("线程2"){
            @Override
            public void run() {
                try {
                    testMechod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

}

在这里插入图片描述

  • 答案是可以的,线程2执行testMechod方法
  • 在线程1没有结束syncMethod方法时就执行完毕了
  • 而没有说去等到线程1执行syncMethod方法完毕后
  • 再去执行testMechod方法,这就说明同步方法中是可以调用非同步方法的

3、一个同步方法是否可以调用另一个同步方法?

package sync;
/**
 * 一个同步方法是否可以调用另一个同步方法?
 * 这个要分情况讨论:
 * 1、如果在同一个线程的同一把锁,那么就是可以的。
 * 2、如果在同一个线程的不是同一把锁那么就需要看锁是否
 * 被其它线程持有了。
 * 3、不同线程的同一把锁,那么就会产生排队现象(同步)
 * @author xzq
 */
public class Synchronize03 {


    //锁的是Synchronize03.class对象锁
    public static synchronized void syncMethod01() throws InterruptedException{
        String threadName=Thread.currentThread().getName();
        System.out.println(threadName+":开始执行syncMethod01方法……");
        //休眠3秒,模拟处理业务逻辑
        Thread.sleep(3000);
        //调用同一个锁对象的同步方法
        syncMethod02();
        System.out.println(threadName+":执行syncMethod01方法结束!");
    }

    //锁的也是Synchronize03.class对象锁 所以锁的是同一个锁
    // 因为是同一个锁,所以就可以直接进入需要要在外等着排队  可重入锁
    public static synchronized void syncMethod02() throws InterruptedException{
        String threadName=Thread.currentThread().getName();
        System.out.println(threadName+":开始执行syncMethod02方法……");
        //休眠1秒,模拟处理业务逻辑
        Thread.sleep(1000);
        System.out.println(threadName+":执行syncMethod02方法结束!");
    }

    public static void main(String[] args) {
        new Thread("线程1"){
            @Override
            public void run() {
                try {
                    syncMethod01();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();


    }

}

在这里插入图片描述
这个要分情况讨论:

  • 1、如果在同一个线程的同一把锁,那么就是可以的。
  • 2、如果在同一个线程的不是同一把锁那么就需要看锁是否被其它线程持有了。
  • 3、不同线程的同一把锁,那么就会产生排队现象(同步)

案例中的synchronized 是同一把锁 锁的都是Synchronize03.class对象

4、线程在执行过程中,出现异常,锁会被释放么?

package sync;

/**
 * 线程在执行过程中,出现异常,锁会被释放么?
 * 答:synchronized默认抛出异常,锁是会被释放的,
 * 下面代码中,run方法为同步方法,线程1中的run在死循环中
 * 抛出了异常,如果锁不被释放,线程2是无法进入run方法继续执行的。
 *
 * 所以,在多线程并发执行过程中,对出现的异常一定要做妥善处理,
 * 否则就可能会导致数据不一致的情况;
 * 下面的代码中,线程2拿到的a的值就是从3开始的,这和预期的从0开始
 * 是不一致的,因为异常了数据应该回滚 a应该回归到a=0
 * 如果此时如果对a的操作处在一个事务中,那么就应该在出现异常的时候将
 * a的值进行重置(做事务的回滚)
 * @author xzq
 */
public class Synchronize05 {

    public static void main(String[] args) throws InterruptedException {

        Runnable r = new Runnable() {
            private int a = 0;
            @Override
            public synchronized void run() {
                try {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + "开始执行线程的run方法……");

                    while (true) {
                        a++;
                        System.out.println(threadName + ":a的值为:" + a);
                        // 模拟业务逻辑执行,休息1秒
                        Thread.sleep(1000);
                        if (a == 3) {
                            throw new RuntimeException();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };

        new Thread(r,"线程1").start();
        // 2秒后又新建一个线程进行执行同样的逻辑
        Thread.sleep(2000);

        new Thread(r,"线程2").start();
    }
}

在这里插入图片描述
答:synchronized默认抛出异常,锁是会被释放的。

  • 下面代码中,run方法为同步方法,线程1中的run在死循环 抛出了异常,如果锁不被释放,线程2是无法进入run方法继续执行的。

  • 所以,在多线程并发执行过程中,对出现的异常一定要做妥善处理, 否则就可能会导致数据不一致的情况;

  • 下面的代码中,线程2拿到的a的值就是从3开始的,这和预期的从0开始是不一致的,因为异常了数据应该回滚 a应该回归到a=0

  • 如果此时如果对a的操作处在一个事务中,那么就应该在出现异常的时候将 a的值进行重置(做事务的回滚)

5、从synchronized思考合理地设置锁的粒度

package sync;

/**
 * 从synchronized思考合理地设置锁的粒度
 * 尽量使synchronized的代码区域小,减小锁的粒度
 * @author xzq
 */
public class Synchronize06 {
    public static void main(String[] args)  {
        //创建服装店对象
        final Test clothingStore=new Test();

        //创建第一个线程:模拟第一个人进入服装店
        new Thread("线程1"){

            @Override
            public void run() {
                try {
                    clothingStore.fineGrain();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }.start();

        //创建第二个线程:模拟第二个人进入服装店
        new Thread("线程2"){

            @Override
            public void run() {
                try {
                    clothingStore.fineGrain();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }.start();



    }

    static class Test{
        public void coarseGrain(){
            synchronized (this) {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName+":1、进入服装店……");
                System.out.println(threadName+":2、挑选衣服……");
                System.out.println(threadName+":3、进入试衣间换衣服……");
                System.out.println(threadName+":4、离开试衣间并付款……");
                System.out.println(threadName+":5、离开服装店……");
            }
        }
        public void fineGrain() throws InterruptedException {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+":1、进入服装店……");
            System.out.println(threadName+":2、挑选衣服……");
            synchronized (this) {
                System.out.println(threadName+":3、进入试衣间换衣服……");
                Thread.sleep(5000);
                System.out.println(threadName+":4、离开试衣间并付款……");
            }
            System.out.println(threadName+":5、离开服装店……");
        }
    }
}

在这里插入图片描述
锁试衣间而不是锁服装店

6、锁定一个对象f,如果这个对象的属性发生改变,会不会影响到锁的使用呢

package sync;

/**
 *  锁定一个对象f,如果这个对象的属性发生改变,
 * 会不会影响到锁的使用呢?
 *
 * 答:是不影响这个锁对象的正常使用的,但是如果这个对象f的
 * 引用发生改变去指向了另一个对象了,那么这个锁的对象
 * 会变成新指向的那个对象了。
 * 所以应该杜绝将锁定对象的引用去指向另外的对象,才能达到
 * 我们软件想要的结果。
 *

 * @author xzq
 */
public class Synchronize07 {

    static class Test_1{

        public Test_2 test_2=new Test_2();
        public void testMethod() throws InterruptedException{
            synchronized (test_2) {
                String threadName = Thread.currentThread().getName();
                while(true){
                    Thread.sleep(1000);
                    test_2.a++;
                    System.out.println(threadName+":a="+test_2.a);
                   // System.out.println(threadName+":你好,我是复读机,我每1秒中复读一次!");
                }
            }
        }
    }
    static class Test_2{
        public   int a=0;
    }

    public static void main(String[] args) throws InterruptedException {

        final Test_1 test=new Test_1();

        //创建第一个线程并启动
        new  Thread("线程1"){
            @Override
            public void run() {
                try {
                    test.testMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        Thread.sleep(3000);

        //创建第二个线程并启动
        Thread t2 = new  Thread("线程2"){

            @Override
            public void run() {
                try {
                    test.testMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        };

        /*
         * 这里是将lock这个引用去指向了另一个新创建出来的对象
         * 所以两个线程持有的不是同一个锁对象,所以不能形成互斥现象
         * 和去掉锁效果一样 两个线程共享一个对象
         */
        test.test_2=new Test_2();
        /*如果修改属性 是不影响锁的性质的  线程2无法进入
         */
//      test.lock.a=2;
        t2.start();

    }

}

在这里插入图片描述
如果是改变对象的属性不影响锁的性质,如下测试线程2无法进入
在这里插入图片描述
在这里插入图片描述

7、不要以字符串常量或者其它常量作为锁定对象

package sync;

/**
 * 不要以字符串常量或者其它常量作为锁定对象
 *
 * 这样做会产生几个问题:
 * 1、导致引入我们项目作为jar包的上游方,如果在
 * 同步的时候锁定的是同一个我们已锁定的常量,那么
 * 就会产生死锁现象。
 *
 * 2、如果是我们引入下游方的jar包,而下游方在逻辑中
 * 有对某个常量进行加锁,但是我们由于读不到其源码,
 * 然后我们在加锁的时候也去用到了这个常量,那么也会
 * 产生死锁现象。
 *
 * 结论:这样的做法会导致排错极为困难,会造成软件的
 * 可维护性和可靠性极差。

 * @author xzq
 */
public class Synchronize08 {


    static class Test{

        /**
         * //存入常量池  str_1和str_2是同一个对象
         * new String();不会存入常量池中
         */
        private String str_1="字符串";

        private String str_2="字符串";

        private Integer itg_1=50;

        private Integer itg_2=50;//127以下的数字都会被存入常量池中

        private Integer itg_3=128;//不存在常量池,不会互斥

        private Integer itg_4=128;

        public void testMethod_1() throws InterruptedException{
            synchronized (itg_1) {
                String threadName = Thread.currentThread().getName();
                while(true){
                    Thread.sleep(1000);
                    System.out.println(threadName+":我是"+itg_1+"存在常量池中,所以互斥");
                }
            }
        }


        public void testMethod_2() throws InterruptedException{
            synchronized (itg_2) {
                String threadName = Thread.currentThread().getName();
                while(true){
                    Thread.sleep(1000);
                    System.out.println(threadName+":我是"+itg_2+"存在常量池中,所以互斥");
                }
            }
        }

    }


    public static void main(String[] args) throws InterruptedException {

        final Test test=new Test();

        //创建第一个线程并启动
        new  Thread("线程1"){
            @Override
            public void run() {
                try {
                    test.testMethod_1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        Thread.sleep(3000);
        //创建第二个线程并启动
        Thread t2 = new  Thread("线程2"){
            @Override
            public void run() {
                try {
                    test.testMethod_2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t2.start();
    }
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/2020123116545610.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1haUV9TVFVE,size_16,color_FFFFFF,t_70

private String str_1=“字符串”;
private String str_2=“字符串”;
str_1和str_2会存入常量池,所以二者为同一个对象,锁定会导致互斥

127以下的数字包括127都会被存入常量池中,127以上的不会放入常量池
所以锁定127一下的数字会导致互斥包括127,127以上的不会
下面案例锁定数字 128
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用 CMake 构建多线程项目时,有一些注意事项可以帮助你确保正确配置和使用线程。 1. 寻找并链接线程库:在 CMakeLists.txt 文件中,你可以使用 `find_package(Threads REQUIRED)` 来寻找线程库,并使用 `target_link_libraries(your_target_name PRIVATE Threads::Threads)` 来链接线程库。 2. 设置编译选项:在 CMakeLists.txt 文件中,可以通过 `target_compile_options(your_target_name PRIVATE -pthread)` 来设置编译选项,确保在编译时包含线程相关的标志。 3. 线程安全性:多线程编程需要注意线程安全性。确保你的代码在多个线程之间正确同步和共享数据,避免出现竞态条件和数据竞争问题。 4. 调试和测试:多线程程序的调试和测试可能比单线程程序更具挑战性。使用适当的调试工具和技术,例如断点、日志输出、线程安全的数据结构等,可以帮助你定位和解决问题。 5. 并发控制:根据你的应用需求,选择适当的并发控制机制,例如互斥锁、条件变量、原子操作等,来保护共享资源的访问。 6. 线程池:如果你的应用需要频繁地创建和销毁线程,考虑使用线程池来提高性能和效率,避免频繁的线程创建和销毁开销。 这些是一些常见注意事项,当然还要根据你的具体项目和需求进行相应的配置和处理。通过合理的设计和编码实践,可以帮助你开发出高效、稳定的多线程应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值