Java多线程同步 比多线程复杂

在本文中,我们将重点关注线程同步这个主题。这比多线程复杂。不重视就“掉坑”了。而且与单线程程序不同的是,每次是否出现多线程错误并不是固定的,这给调试带来了很大的挑战。

在本文中,我们首先解释什么是同步,什么是不同步的问题,然后讨论可以采取什么措施来控制同步。接下来,我们将在服务器端构建一个“线程池”,就像我们在审查网络通信时所做的那样。JDK为我们提供了一个大型并发工具包,最后我们将探索其中的内容。

为什么要线程同步?
说到线程同步,大多数情况下我们讨论的是“单对象多线程”的情况,一般分为两部分,一部分是关于“共享变量”,一部分是关于“执行步骤”。

共享变量
当我们在一个Runnable对象中定义一个全局变量,run方法修改该变量时,如果多个线程同时使用该线程对象,那么全局变量的值也会同时被修改,从而产生错误。让我们看看下面的代码:

共享变量会导致同步问题
在这个例子中,用线程计算1到100的和,我们知道正确的结果是5050(好像高斯小时候玩过这个?),但是上面的程序返回的结果是10100,因为两个线程同时对sum进行操作。

执行步骤
当我们运行多线程时,可能需要将一些操作组合成“原子操作”,也就是说,这些操作可以看作是“单线程”。例如,我们可能希望输出结果如下所示:

1读取1:步骤1
2线程1:步骤2
3线程1:步骤3
4线程2:步骤1
5线程2:步骤2
6线程2:步骤3

如果同步控制不好,可能如下所示:

线程1:步骤1
线程2:步骤1
线程1:步骤2
线程2:步骤2
线程1:步骤3
线程2:步骤3

这里我们也给出一个示例代码:
执行步骤混乱带来的同步问题

1 class MyNonSyncRunner implements Runnable
2 {
3 public void run() {
4 System.out.println(Thread.currentThread().getName() + " Start.");
5 for(int i = 1; i <= 5; i++)
6 {
7 System.out.println(Thread.currentThread().getName() + " Running step " + i);
8 try
9 {
10 Thread.sleep(50);
11 }
12 catch(InterruptedException ex)
13 {
14 ex.printStackTrace();
15 }
16 }
17 System.out.println(Thread.currentThread().getName() + " End.");
18 }
19 }
20
21
22 private static void syncTest() throws InterruptedException
23 {
24 MyNonSyncRunner runner = new MyNonSyncRunner();
25 Thread thread1 = new Thread(runner);
26 Thread thread2 = new Thread(runner);
27 thread1.setDaemon(true);
28 thread2.setDaemon(true);
29 thread1.start();
30 thread2.start();
31 thread1.join();
32 thread2.join();
33 }

如何控制线程同步
既然线程同步有上述问题,应该如何解决?对于不同原因引起的同步问题,我们可以采取不同的策略。

控制共享变量
有三种方法可以控制共享变量。

将“单对象多线程”修改为“多对象多线程”
如上所述,同步问题一般发生在“单对象多线程”的场景中,所以最简单的处理方法是将运行模型修改成“多对象多线程”的样子。对于上述示例中的同步问题,修改后的代码如下:

解决共享变量问题的方案1
我们可以看到,上面代码中的两个线程使用了两个不同的Runnable实例,它们在运行过程中不会访问同一个全局变量。

将“全局变量”降级为“局部变量”
既然问题是由共享变量引起的,我们可以把共享变量改成“不共享”,也就是修改成局部变量。这样也能解决问题。同样对于上述示例,该解决方案的代码如下:

解决共享变量问题的方案2
我们可以看到sum变量在run方法内部已经从全局变量变成了局部变量。

使用线程本地机制
ThreadLocal是JDK引入的一种机制,用来解决线程间共享变量的问题。即使ThreadLocal声明的变量是线程中的全局变量,这些变量对于每个线程都是独立的。

我们可以这样修改上面的代码,如下所示:

解决共享变量问题的方案3
总结三种方案,第一种方案会降低多线程执行的效率,所以推荐使用第二种或者第三种方案。

控制执行步骤
说到执行步骤,我们可以用synchronized关键字来解决。

执行问题解决步骤
关于线程同步这个话题,synchronized是一个很重要的关键词。其原理类似于数据库中的事务锁。在使用过程中,要尽量减少同步覆盖的范围,原因有二:1)它覆盖的范围是串行的,效率低;2)容易出现死锁。让我们看看下面的例子:

同步示例
要把需要同步的内容集中在一起,尽量不要包含其他不相关的消耗大量资源的操作。显然,例子中线程休眠的操作不应该包括在内。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值