高并发程序设计入门

                       

说在前面

本文绝大部分参考《JAVA高并发程序设计》,类似读书笔记和扩展。

走入并行世界

概念

同步(synchronous)与异步(asynchronous)

同步和异步通常来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续执行任务。
异步方法更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的工作。异步方法通常会在另外的线程中“真实”的执行。整个过程不会阻碍调用者的工作。

并发(concurrency)和并行(parallelism)

链接:并发Concurrent与并行Parallel的区别

临界区

临界区表示一种公共资源或者说是共享资源,可以被多个线程使用。但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想得到这个资源就必须等待。
在并行程序中。临界区资源是保护对象。就比如大家公用的一台打印机,必然是一个人打完另一个人的才能打印,否则就会出乱子。

阻塞(blocking)与非阻塞(non-blocking)

阻塞和非阻塞通常来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程都需要在临界区中等待。等待会导致线程挂起,这种情况就是阻塞。此时如果占用这个资源的线程一直不愿释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。
反之就是非阻塞,它强调没有一个线程可以妨碍其他线程执行。所有线程都会尝试不断前向执行。

死锁(deadlock)、饥饿(starvation)和活锁(livelock)

这三种情况都属于线程活跃性问题。如果发现上述情况,那么相关线程可能就不再活跃,也就是说它可能很难再继续执行任务了。
1 死锁
应该是最糟糕的情况之一。它们彼此相互都占用着其他线程的资源,都不愿释放,那么这种状态将永远维持下去。
死锁是一个很严重的问题,应该避免和小心。就如4辆小汽车,互相都占用对方的车道,无法正常行驶。
这里写图片描述

2 饥饿
是指一个或多个线程因为种种原因一直无法得到所需要的资源,导致一直无法执行,比如它的线程优先级太低,高优先级的线程一直抢占它所需要的资源。另一种可能是某一个线程一直占用着关键资源不放,导致其他需要这个资源的线程一直无法得到这个资源,无法正常执行。与死锁相比,饥饿还是可能在一段时间内解决的,比如高优先级的线程执行完任务后,不在抢占资源,资源得到释放。

3 活锁
是非常有趣的情况,也是最难解决的情况。这就比如,大家在一个两人宽的桥上走路,双方都很有礼貌。都在第一时间礼让对方,一个往左一个往右,导致两人都无法正常通行。放到线程中,就体现为,两个线程都拿到资源后都主动释放给他人使用,那么就会出现资源不断的在两个线程中跳动,而没有一个线程可以拿到资源后正常执行,这个就是活锁。

并发级别

由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别进行分类,大致上可以分为阻塞、无饥饿、无障碍,无锁和无等待几种。

阻塞(blocking)

一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用synchronized关键字,或者重入锁时,我们得到的就是阻塞的线程。
无论是synchronized还是重入锁,都会在视图执行后续代码前得到临界区的锁,如果得不到,线程就会被挂起等待,直到占有了所需要的资源为止。

无饥饿

如果线程间是有优先级的,那么线程调用总是会倾向于满足高优先级的线程。也就是说对同一个资源的分配是不公平的。对于非公平的锁来说,系统允许高优先级的线程插队,这样有可能导致低优先级的线程产生饥饿。但如果锁是公平的,满足先来后到,那么饥饿就不会产生,不管新来的线程优先级多高,要想获得资源就必须排队。那么所有的线程都有机会执行。
这里写图片描述

无障碍(obstruction-Free)

无障碍是一种最弱的非阻塞调度。两个线程如果是无障碍的执行,那么他们不会因为临界区的问题导致一方被挂起。大家都可以大摇大摆进入临界区工作。那么如果大家都修改了共享数据怎么办呢?对于无障碍的线程来说,一旦出现这种情况,当前线程就会立即对修改的数据进行回滚,确保数据安全。但如果没有数据竞争发生,那么线程就可以顺利完成自己的工作,走出临界区。
如果阻塞控制的方式比喻成悲观策略。也就是说系统认为两个线程之间很有可能发生不幸的冲突,因此,保护共享数据为第一优先级。相对来说,非阻塞的调度就是一种乐观策略,他认为多线程之间很有可能不会发生冲突,或者说这种概率不大,但是一旦检测到冲突,就应该回滚。
从这个策略来看,无障碍的多线程程序不一定能顺利执行。因为当临界区的字眼存在严重的冲突时,所有线程可能都进行回滚操作,导致没有一个线程可以走出临界区。所以我们希望在这一堆线程中,至少可以有一个线程可以在有限时间内完成自己的操作,至少这可以保证系统不会再临界区进行无线等待。
一种可行的无障碍实现可以依赖一个“一致性标记”来实现。线程在操作之前,先读取并保持这个标记,在操作完后,再次读取,检查这个标记是否被修改过,如果前后一致,则说明资源访问没有冲突。如果不一致,则说明资源可能在操作过程中与其他写线程冲突,需要重试操作。任何对保护资源修改之前,都必须更新这个一致性标记,表示数据不安全。

无锁(lock-free)

无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区的资源进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
在无锁的调度中,一个典型的特点是可能会包含一个无穷循环。在这个循环中线性不断尝试修改共享数据。如果没有冲突,修改成功,那么线程退出,否则尝试重新修改。但无论如何,无锁的并行总能保证有一个线程可以胜出,不至于全军覆没。至于临界区中竞争失败的线程,则不断重试。如果运气不好,总是不成功,则会出现类似饥饿的现象,线程会停止不前。

无等待(wait-free)

无锁是要求至少有一个线程在有限步内完成操作,而无等待则是在无锁的基础之上进一步扩展。他要求所有线程都必须在有限步内完成操作。这样就不会引起饥饿问题。如果限制这个步骤上限,还可以分为有界无等待和线程无关的无等待几种,它们之间的区别只是对循环次数的限制不同。
一种典型的无等待结构是RCU(read-copy-update)。它的基本思想是,对数据的读可以不加控制,因此所有读线程都是无等待的,它们既不会被锁定等待也不会引起任何冲突。但在写数据时,先取得原始数据的副本,接着只修改副本数据,修改完后,在合适的时机回写数据。

有关并行的两个重要定律

Amdahl定律

加速比定义:加速比= 优化前系统耗时/优化后系统耗时
根据Amdahl定律,使用多核CPU对系统进行优化,优化的效果取决于CPU的数量以及系统中串行程序的比重。CPU数量越多,串行化比重越低,则优化效果越好。仅提高CPU核数不降低系统串行程序比重,也无法提高系统性能。

Gustafson定律

根据Gustafson定律,我们更容易发现,如果串行化比例很小,并行化比例很大,那么加速比就是处理器的个数。只要不断增加CPU核数,就可以提高系统性能。

JAVA内存模型(JMM)

由于并发程序要比串行程序复杂的多,其中一个重要的原因是并发程序下数据访问的一致性和安全性将受到严重的挑战。因此我们需要在深入了解并行机制之前,再定义一种规则,保证多线程程序可以有效的,正确的协同工作。而JMM也就为此而生。JMM的关键技术点都是围绕多线程的原子性、可见性和有序性来建立的。

原子性(atomicity)

指一个操作是不可中断的。即使多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
比如对一个静态变量int i赋值,A线程赋值1,B线程赋值-1,那么这个变量i的结果可能是1或者-1,没有其他情况。这就是原子性。
但如果是给一个long型赋值的话就没那么幸运了。在32位系统下,long型数据的读写不是原子性的(因为long有64位)。
32位的java虚拟机上运行如下例子,就会出现非原子性的问题了。

public class E1 {    public static long t=0;    public static class ChangT implements Runnable{        private long to;        public ChangT(long to) {            this.to = to;        }        @Override        public void run() {            while (true){                E1.t = to;                Thread.yield();            }        }    }    public static class ReadT implements Runnable{        @Override        public void run() {            while (true){                long tmp = E1.t;                if (tmp != 111L && tmp != -999L && tmp != 333L && tmp != -444L)                    System.out.println(tmp);                Thread.yield();            }        }    }    public  static void main(String[] a){        new Thread(new ChangT(111L)).start();        new Thread(new ChangT(-999L)).start();        new Thread(new ChangT(333L)).start();        new Thread(new ChangT(-444L)).start();        new Thread(new ReadT()).start();    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

理想的结果可能是什么都不输出,但是,一旦运行,就会有大量的输出一下信息

...-42949669634294966852-4294966963...
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以看到读取线程居然读取到不可能存在的数据。因为32为系统中的long型数据的读和写不是原子性的,多线程之间互相干扰了。
如果我们给出结果中几个数值的2进制,大家就会更清晰的认识了。

-999        = 1111111111111111111111111111111111111111111111111111110000011001-444        = 1111111111111111111111111111111111111111111111111111111001000100111         = 0000000000000000000000000000000000000000000000000000000001101111333         = 00000000000000000000000000000000000000000000000000000001010011014294966852  = 0000000000000000000000000000000011111111111111111111111001000100-4294967185 = 1111111111111111111111111111111100000000000000000000000001101111
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面这几个数值的补码形式,也是在计算机内真实存储的内容。不难发现4294966852其实是111或333的前32为夹杂着-444的后32位的数据。而-4294967185其实是-999或-444夹杂111后32位的数据。换句话说,由于并行的关系数字被写乱了。或者读的时候读串位了。
通过这个例子,大家应该对原子性应该有基本的认识。

可见性(visibility)

可见性是指当一个线程修改了一个共享变量。其他线程是否可以立即知道这个修改。对于串行程序来说这个问题是不存在的。但这个问题在并行程序中就很有可能出现。如果一个线程修改了某一个全局变量。其他线程未必可以马上知道这个修改。如果CPU1和CPU2上各运行了一个线程,它们共享变量t。由于编译器优化或者硬件优化缘故。在CPU1上的线程将变量t进行了优化,将其缓存在cache中或者寄存器里。这种情况下如果CPU2上的某个线程修改了t的实际值,那么CPU1上的线程可能就无法意识到这个改动,依旧会读取cache或者寄存器中的旧值。因此就产生了可见性的问题。可见性问题在并行程序中也是需要重点关注的问题之一。
这里写图片描述
可见性问题是一个综合性问题,处理上述提到的缓存优化和硬件优化会导致可见性问题外,指令重排以及编译器的优化,都有可能导致这个问题。
附两个例子便于理解可见性问题。
这里写图片描述

有序性(ordering)

有序性是三个问题中最难理解的,对于一个线程的执行代码而言,我们总是习惯性的认为代码的执行是从先往后的,依次执行的。这么理解也不能完全说是错误的。在一个线程的情况下确实是从先往后。但是在并发时,程序的执行就可能出现乱序,写在前面的代码可能会后执行。
有序性的问题的原因是因为程序在执行的时候,可能发生指令重排,重排后的指令和原指令的顺序未必一致。
指令重排有一个基本的前提是,保证串行语义的一致性。指令重排不会使串行的语义逻辑发生问题。因此在串行代码中不必担心这个问题。而在多线程间就无法保证了。
so,问题来了。为什么会指令重排呢?
这完全是基于性能考虑。
我们知道一条指令的执行是可以分很多步骤的。简单的说可以分如下几步:
- 取指 IF
- 译码和取寄存器操作数 ID
- 执行或者有效地址计算 EX
- 存储器访问 MEM
- 回写 WB
我们的汇编指令也不是一步就执行完了。在CPU的实际工作中,还是要分几步去执行的。当然,每个步骤涉及的硬件也可能不同。比如,取指会用到PC寄存器和存储器,译码会用到指令寄存器组,执行会使用ALU(算术逻辑单元(arithmetic and logic unit) 是能实现多组算术运算和逻辑运算的组合逻辑电路,简称ALU。主要功能是二进制算数运算),写回时需要寄存器组。
由于一个步骤可能使用不同的硬件完成,因此,就发明了流水线技术来执行指令。
- 指令1 IF ID EX MEM WB
- 指令2    IF ID EX  MEM WB
可以看到,到两条指令执行时,第一条指令其实还未执行完,这样的好处是,假设每一步需要1毫秒,那么第2条指令需要等待5毫秒才能执行。而通过流水线技术,指令2就只需等待1毫秒。这样有了流水线就可以让CPU高效的执行。但是,流水线总是害怕被中断。流水线满载的时候性能确实相当不错,但是一旦中断,所有硬件设备都会进入停顿期,再次满载又需要几个周期,因此性能损失会比较大,所以我们就需要想办法来不让流水线中断。
之所以需要指令重排就是避免流水线中断,或尽量少的中断流水线。当然指令重排只是减少中断的一种技术,实际上CPU设计中,我们还有更多的软硬件技术来防止中断。具体大家就自己探究吧。
通过例子我们加深下理解。
示例 1 :
A = B + C执行过程。
左边是汇编指令,LW表示load,其中LW R1,B表示把B的值加载到R1寄存器中。ADD就是加法,把R1,R2的值想加放到R3中。SW表示store,就是将R3寄存器的值保存到变量A中。

//A = B + C 执行过程LW  R1,B        IF ID EX MEM WBLW  R2,C           IF ID EX  MEM WBADD R3,R1,R2          IF ID  X   EX  MEM WBSW  A,R3                 IF  X   ID  EX  MEM WB
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

左边是指令由上到下执行,右边是流水线情况。在ADD上的大叉表示一个中断。因为R2中的数据还没准备好,所以ADD操作必须进行一次等待。由于ADD的延迟,后面的指令都要慢一拍。
示例 2 :

a = b + c ;d = e - f ;
   
   
  • 1
  • 2

执行过程如下
这里写图片描述
其实就是将中断的时间去做别的事情,如load数据。这样时间就可以规划衔接好。有点儿像项目管理中优化关键路径。由此可见,指令重排对于提高CPU处理性能是十分必要的,虽然确实带来了乱序的问题,但这点儿牺牲完全值得的。

 

JMM 参考资料
  深入理解JVM—JVM内存模型
  Java内存模型
  深入理解Java内存模型之系列篇
  程晓明-深入理解Java内存模型

哪些指令不能重排:

虽然java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的。
- 原则基本包括以下:
1 程序顺序原则:一个线程内保证语义的串行性

Eg:a=1;b=a+1;第二条语句依赖于第一条执行结果。所以不允许指令重排。
   
   
  • 1
  • 2
  • 3
  • 4

2 volatile规则:volatile变量的写,先发生与读,这保证了volatile变量的可见性。
3 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

Eg:锁规则强调,unlock操作必然发生在后续的对同一个锁的lock之前,也就是说,如果对一个锁解锁后,在加锁,那么加锁的动作绝对不能重排到解锁动作之前。很显然,如果这么做,加锁行为是无法获得这把锁的。
   
   
  • 1
  • 2
  • 3
  • 4

4 传递性:A先于B,B先于C,那么A必然先于C
5 线程的start()方法先于它的每一个动作
6 线程的所有操作先于线程的终结(Thread.join())
7  线程的中断(interrupt())先于被中断线程的代码
8  对象的构造函数执行、结束先于finalize()方法

基础

线程生命周期

这里写图片描述
线程所有的状态都在Thread.State枚举类中定义

public enum State {    /**    * 表示刚刚创建的线程,这种线程还没开始执行。    **/    NEW,    /**    * 调用start()方法后,线程开始执行,处于RUNNABLE状态,    * 表示线程所需要的一切资源以及准备好。    **/    RUNNABLE,    /**    * 当线程遇到synchronized同步块,就进入了BLOCKED阻塞状态。    * 这时线程会暂停执行,直到获得请求的锁。    **/    BLOCKED,    /**    * WAITING和TIMED_WAITING都表示等待状态,他们是区别是WAITING表示进入一个无时间限制的等待    * TIMED_WAITING会进入一个有时间限制的等待。    * WAITING的状态正是在等待特殊的事件,如notify()方法。而通过join()方法等待的线程,则是等待目标线程的终止。    * 一旦等到期望的时间,线程就会继续执行,进入RUNNABLE状态。    * 当线程执行完后进入TERMINATED状态,表示线程执行结束。    **/    WAITING,    TIMED_WAITING,    TERMINATED;}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

线程的基本操作

启动初始化及基本方法

参考  多线程基础

终止线程

一个线程执行完后会结束,无须手动关闭,但是如一些系统性服务的线程基本都是一个大的循环,一般情况不会终止。
如何才能正常关闭线程呢?JDK提供了一个Thread.stop方法就可以立即关闭一个线程。但是这个方法太暴力,基本不会使用。并且stop()方法也是标记要废弃的方法。stop()强行的将执行中的线程关闭,可能会造成数据不一致问题。
看图说话:
这里写图片描述
举个栗子:

public class ThreadStopExample {    public static User u = new User();    public static void main(String[] a){        /**         * 开启读取线程         */        new Thread(new readObj(),"读--线程").start();        while (true){            Thread t = new Thread(new changeObj(),"写--线程");            t.start();            try {                /**                 * 主线程sleep 150毫秒,处理业务                 */                Thread.sleep(150);            } catch (InterruptedException e) {                e.printStackTrace();            }            /**             * 将写线程停止             */            t.stop();        }        /**         * 执行结果:         * 观察这些值,name属性永远比id小,是因为它永远是上一次的值,就是因为stop(),无法完整的完成id和name赋值.         *          * 为什么会不一致呢?         * 因为 User 通过 changeObj()方法不断改变,当changeObj方法设置id后,需要处理其他花费100毫秒的业务.完成后设置name的值.         * 在这100毫秒中,调用changeObj()的主线程恰好执行了stop()方法,         * 虽然已经设置了User的id属性值,但User的name属性依然是上次循环的值.没来得及赋值就stop()了.         * 所以这就是为什么stop()会产生不一致问题.         *          * User{id=1455613327, name='1455613326'}         * User{id=1455613329, name='1455613328'}         * User{id=1455613331, name='1455613330'}         * User{id=1455613331, name='1455613330'}         * User{id=1455613331, name='1455613330'}         * .......         */    }    /**     * 修改操作     */    public static class changeObj implements Runnable{        @Override        public void run() {            while (true){                synchronized(u){                    int v = (int) (System.currentTimeMillis()/1000);                    u.setId(v);                    try {                        /**                         * sleep 100毫秒,处理业务                         */                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    u.setName(String.valueOf(v));                }                Thread.yield();            }        }    }    /**     * 读取操作     */    public static class readObj implements Runnable{        @Override        public void run() {            while (true) {                synchronized (u) {                    /**                     * 当ID 不等于 name时,打印.                     *                     */                    if (u.getId() != Integer.parseInt(u.getName())){                        System.out.println(u);                    }                }                Thread.yield();            }        }    }    public static class User{        private int id ;        private String name ;        //getter setter        public User() {            this.id = 0;            this.name = "0";        }        @Override        public String toString() {            return "User{" +                    "id=" + id +                    ", name='" + name + '\'' +                    '}';        }    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118

如何正确的stop,如何不写坏对象,请看修改后的代码如下,我们采用自己的方式去达到线程stop,当然还有其他更好的方案。

public static class changeObj implements Runnable{        //定义一个stop标识来实现我们自己的关闭方法        volatile static boolean stopMe = false;        @Override        public void run() {            while (true){                //增加if块                if (stopMe){                    System.out.println("exit by stopMe...");                    break;                }                    synchronized(u){                        ...                    }                ...            }        }    }public static void main(String[] a){        while (true){            ...            //t.stop();            changeObj.stopMe = true;        }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

线程中断

线程中断是重要的线程协作机制,中断就是让线程停止执行,但这个停止执行非stop()的暴力方式。JDK提供了更安全的支持,就是线程中断。
线程中断并不会使线程立即停止,而是给线程发送一个通知,告诉目标线程有人希望你退出。至于目标线程接到通知后什么时候停止,完全由目标线程自行决定。这点很重要,如果线程接到通知后立即退出,我们就又会遇到类似stop()方法的老问题。
与线程有关的三个方法,
1、中断线程
public void Thread.interrupt() 
说明:Thread.interrupt() 是一个实例方法,他通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断了。
2、判断是否被中断
public boolean Thread.isInterrupted()
说明:Thread.isInterrupted()  也是实例方法,他判断当前线程是否被中断(通过检查中断标志位)
3、判断是否被中断,并清除当前中断状态
public static boolean Thread.interrupted()
说明:Thread.interrupted()  是静态方法,判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。
实例1
看起来和stopMe的手法一样,但是中断功能更为强劲,比如遇到sleep()或wait()这样的操作时,就只能用中断标识了。

public class InterruptExample {    public static void main(String [] a){        Thread t1 = new Thread("线程小哥 - 1 "){            @Override            public void run() {                while (true){                    /**                     * 必须得判断是否接受到中断通知,如果不写退出方法,也无法将当前线程退出.                     */                    if (Thread.currentThread().isInterrupted()){                        System.out.println(Thread.currentThread().getName() + " Interrupted ... ");                        break;                    }                    Thread.yield();                }            }        };        t1.start();        try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        /**         * 给目标线程发送中断通知         * 目标线程中必须有处理中断通知的代码         * 否则,就算发送了通知,目标线程也无法停止.         */        t1.interrupt();    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

实例2

public class InterruptExample {    public static void main(String [] a){        Thread t1 = new Thread("线程小哥 - 1 "){            @Override            public void run() {                while (true){                    /**                     * 必须得判断是否接受到中断通知,如果不写退出方法,也无法将当前线程退出.                     */                    if (Thread.currentThread().isInterrupted()){                        System.out.println(Thread.currentThread().getName() + " Interrupted ... ");                        break;                    }                    try {                        /**                         * 处理业务逻辑花费10秒.                         * 而在这时,主线程发送了中断通知,当线程在sleep的时候如果收到中断                         * 则会抛出InterruptedException,如果在异常中不处理,则线程不会中断.                         *                         */                        Thread.sleep(10000);                    } catch (InterruptedException e) {                        System.out.println("我错了....");                        /**                         * 在sleep过程中,收到中断通知,抛出异常.可以直接退出线程.                         * 但如果还需要处理其他业务,则需要重新中断自己.设置中断标记位.                         * 这样在下次循环的时候 线程发现中断通知,才能正确的退出.                         */                        Thread.currentThread().interrupt();                    }                    Thread.yield();                }            }        };        t1.start();        try {            /**             * 处理业务500毫秒             * 然后发送中断通知,此时t1线程还在sleep中.             */            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        /**         * 给目标线程发送中断通知         * 目标线程中必须有处理中断通知的代码         * 否则,就算发送了通知,目标线程也无法停止.         */        t1.interrupt();    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

等待(wait)和通知(notify)

为了支持多线程之间的协作,JDK提供了两个非常重要的等待方法wait()和nofity()方法。这两个方法并不是Thread类中的,而是Object类,这意味着任何对象都可以调用这两个方法。
比如线程A调用了obj.wait()方法,那么线程A就会停止执行而转为等待状态,进入obj对象的等待队列。这个等待队列可能有多个线程,因为系统运行多个线程同时等待同一个对象。其他线程调用obj.notify()方法时,它就会从等待队列中随机选择一个线程并将其唤醒。注意着个选择是不公平的,是随机的。
obj.wait()方法并不是可以随便调用。他必须包含在对应的synchronized语句中。无论是wait还是notify都必须首先获得目标对象的一个监视器。而正确执行wait方法后,会释放这个监视器,这样其他等待obj上的线程才能获得这个监视器,不至于全部无法执行。
在调用obj.notify()前,同样也必须获得obj的监视器,索性wait方法已经释放了监视器。唤醒某个线程后(假设唤醒了A),A线程要做的第一件事并不是执行后续的代码,而是要尝试重新获得obj监视器。而这个监视器也正是A执行wait方法前所只有的那个obj监视器。如果暂时无法获得。A还必须要等待这个监视器。当A获得监视器后,才能真正意义上的继续执行。
这里写图片描述
注意:wait方法和sleep方法都可以让线程等待若干时间,处理wait方法可以唤醒之外,另外一个主要区别是wait方法会释放目标对象的锁,而sleep方法不会释放。
例子:

public class WaitNotifyExample {    public static void main (String [] a){        Thread a1 = new A();        Thread b1 = new B();        a1.start();        b1.start();        /**         * 执行结果:        * A start ...        * A wait for obj ...        * B start ... notify one Thread...        * B end         * 这里间隔2秒        * A end        * */    }    final static Object obj = new Object();    public static class A extends Thread{        @Override        public void run() {            synchronized (obj){                System.out.println("A start ... ");                try {                    System.out.println("A wait for obj ... ");                    obj.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("A end");            }        }    }    public static class B extends Thread{        @Override        public void run() {            synchronized (obj){                System.out.println("B start ... notify one Thread...");                obj.notify();                System.out.println("B end");                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

挂起(suspend)和继续执行(resume)线程

这两个方法虽然已经不推荐使用了。但是这里再提一下,不推荐使用suspend挂起线程是因为suspend挂起线程后不释放锁资源,导致其他线程想要访问这个锁资源时都会被等待。无法正常运行。而suspend挂起的线程居然还是RUNNABLE状态,这也严重影响了我们队系统当前状态的判断。
这里写图片描述
示例

public class SuspendExample {    public static  Object u = new Object();    static ChangeObj c1 = new ChangeObj("T1");    static ChangeObj c2 = new ChangeObj("T2");    public static class ChangeObj extends Thread{        public ChangeObj(String name) {            super(name);        }        @Override        public void run() {            synchronized (u) {                System.out.println("Thread in : " + getName());                /* //注释1                try {                    System.out.println("sleep 500ms : " + getName());                    Thread.sleep(500);                } catch (InterruptedException e) {                    e.printStackTrace();                }*/                //进入的线程挂起,且不释放资源                Thread.currentThread().suspend();                System.out.println("resume by : " + getName());            }        }    }    public static void  main(String[] a) throws InterruptedException {        //启动c1线程        c1.start();        /**         * 主线程工作100毫秒,(非常关键)         * 这里的意思是         * 第一:为了演示,保证c1能抢占到资源,让主线程sleep后再启动c2         * 第二:保证c1能在执行resume的时候执行完成.这样才能保证c1本身可以有效释放资源.         *      假设c1中执行业务耗时500毫秒后 才执行suspend.(将注释[1]放开).而主线程仅仅sleep100毫秒后执行了c1.resume().         *      这样就导致c1无法释放锁,结果打印的是         *          Thread in : T1         *          sleep 500ms : T1         *      无法再继续走下去.         */        Thread.sleep(100);        //启动c2线程,但在c1不释放资源的情况下,c2只能等待.        c2.start();        //c1 释放锁,此时c1应该已经执行了suspend挂起状态,resume继续执行        c1.resume();        /**         * 解决c2挂起无法继续的方法:         * 1 将主线程sleep1000毫秒,保证c1在1000毫秒内执行完成,         *   但是这不是最好的方法,因为c1有可能在1000毫秒内执行不完         *     Thread.sleep(1000);         * 2 将c2.resume() 放到c1.join后面.         */        //c2 继续执行,其实这里提前执行了resume.导致c2在挂起后无法resume.        //因为c1.join导致c2必须在c1执行完后才能执行.        c2.resume();        //c1 用join将主线程挂起,自己先执行完再执行主线程.也就是保证自己必须先执行完成        //System.out.println("c1 将要执行 join");        c1.join();        System.out.println(Thread.currentThread().getName() + " 结束工作...after c1");        //c2 执行完        c2.join();        System.out.println(Thread.currentThread().getName() + " 结束工作...after c2");    }    /**     *  错误的 结果是:     *  Thread in : T1     *  resume by : T1     *  Thread in : T2     *  main 结束工作...after c1     *  并且程序一直挂起,无法结束.     *  打印线程信息可以发现     *  "T2@431" prio=5 tid=0xd nid=NA runnable     *  java.lang.Thread.State: RUNNABLE     *  at java.lang.Thread.suspend0(Thread.java:-1)     *  at java.lang.Thread.suspend(Thread.java:1029)     *  at com.iboray.javacore.Thread.T2.SuspendExample$ChangeObj.run(SuspendExample.java:31)     *  - locked <0x1b3> (a java.lang.Object)     *     *  "main@1" prio=5 tid=0x1 nid=NA waiting     *  java.lang.Thread.State: WAITING     *  at java.lang.Object.wait(Object.java:-1)     *  at java.lang.Thread.join(Thread.java:1245)     *  at java.lang.Thread.join(Thread.java:1319)     *  at com.iboray.javacore.Thread.T2.SuspendExample.main(SuspendExample.java:75)     *     *  正确的 结果是:     *  Thread in : T1     *  resume by : T1     *  Thread in : T2     *  main 结束工作...after c1     *  resume by : T2     *  main 结束工作...after c2     */}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

示例2
通过wait和notify方式实现suspend和resume效果。这种方式类似于我们自己实现stop那样

public class Suspend1Example {    public static Object u = new Object();    public static void main(String[] a) throws InterruptedException {        ChangeObj c = new ChangeObj();        ReadObj r = new ReadObj();        c.start();        r.start();        Thread.sleep(1000);        c.suspendMe();        System.out.println(" suspend ChangeObj 3s... ");        Thread.sleep(3000);        c.resumeMe();        /**         * 执行结果         * 刚开始ChangeObj与ReadObj交叉执行             in ChangeObj...             in ChangeObj...             in ReadObj...             in ChangeObj...             in ReadObj...             suspend ChangeObj 3s...   主线程执行c.suspendMe()             in ChangeObj...            ...             in ChangeObj...suspend     ChangeObj进入WAIT状态             in ReadObj...              ReadObj独自执行             in ReadObj...              ...             in ReadObj...              ...             in ReadObj...              ...             in ReadObj...              ...             in ReadObj...              ...             in ChangeObj...resume      ChangeObj进入RUNNABLE状态             in ChangeObj...            随后ChangeObj与ReadObj又开始交叉执行             in ChangeObj...             in ReadObj...             in ChangeObj...             in ReadObj...         */    }    public static class ChangeObj extends Thread{        //自定义挂起标识        volatile boolean suspend = false;        //设置挂起标识        public void suspendMe(){            this.suspend = true;        }        //模拟继续执行        public void resumeMe(){            //设置挂起标识为 false 不挂起.            this.suspend = false;            //拿到当前对象锁            synchronized (this){                //唤醒this对象等待队列中的某一个线程.这里单指当前这个                this.notify();            }        }        @Override        public void run() {            while (true){                //拿到当前对象的锁,为什么这里同步的锁一个是this一个是u呢?                //因为this锁的作用是当前类                synchronized (this){                    //如果设置了挂起为true                    if (suspend){                        try {                            //让当前对象加入this对象的等待队列.同时可以释放当前对象的锁.                            System.out.println(" in ChangeObj...suspend  ");                            this.wait();                            System.out.println(" in ChangeObj...resume  ");                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }                //拿到指定实例对象的锁                synchronized (u){                    //执行业务                    try {                        Thread.sleep(200);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(" in ChangeObj...  ");                }                Thread.yield();            }        }    }    public static class ReadObj extends Thread{        @Override        public void run() {            while (true){                try {                    Thread.sleep(200);                } catch (InterruptedException e) {                    e.printStackTrace();                }                //拿到指定实例对象的锁                synchronized (u){                    //执行业务                    System.out.println(" in ReadObj... ");                }                Thread.yield();            }        }    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

等待线程结束(join)和谦让(yield)

一个线程的输入可能依赖于另一或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行,JDK提供了join操作来实现这个功能。方法签名:

//无限等待,它会一直阻塞当前线程,知道目标线程执行完毕public final void join() throws InterruptedException//包含最大等待时机,如果超过给定时间目标线程还未执行完成,当前线程会跳出阻塞 继续执行public final synchronized void join(long mills) throws InterruptedException
   
   
  • 1
  • 2
  • 3
  • 4

join的本质是让调用线程wait()在当前线程对象实例上。当执行完成后,被等待的线程会在退出前调用notifyAll()通知所有的等待线程继续执行。因此,需要注意,不要在应用程序中,在Thread上使用类似wait()或者notify()等方法,因为这很有可能影响系统API的工作,或者被系统API锁影响
yield是一个静态方法,一旦执行,它会使当前线程让出CPU,然后继续加入争抢CPU的线程中。

volatile与JMM

当我们使用volatile来修饰变量,就等于告诉虚拟机这个变量极有可能被某些程序或者线程修改。为了确保这个变量修改后,应用程序范围内的所有线程都能够看到。虚拟机就必须采用一些特殊的手段保证这个变量的可见性。这样就可以解决之前咱们在32位虚拟机上用多线程修改long 的值了。
volatile并不代表锁,他无法保证一些符合操作的原子性。他只能保证一个线程修改了数据后,其他线程能够看到这个改动,但当多个线程同时修改某一个数据时,却依然会产生冲突。他只能保证单个变量的完整性和可见性。保证原子性还的靠类似synchronized方式去解决。

线程组

如果线程数量很多,而且功能分配比较明确,就可以将相同的线程放置在一个线程组里面。

public class ThreadGroupName implements Runnable{    public  static void main(String[] a){        ThreadGroup threadGroupName = new ThreadGroup("printGroup");        Thread t1 = new Thread(threadGroupName,new ThreadGroupName(),"T1");        Thread t2 = new Thread(threadGroupName,new ThreadGroupName(),"T2");        t1.start();        t2.start();        //由于线程是动态的,activeCount()是一个估计值        System.out.println("printGroup线程组  活动线程数 : " + threadGroupName.activeCount());        //list()可以打印这个线程组中所有线程的信息        threadGroupName.list();        //threadGroupName.stop(); 不推荐使用,和单个线程stop暴露的问题是一样的。        /**         * printGroup线程组  活动线程数 : 2         * This is  printGroup : T1         * java.lang.ThreadGroup[name=printGroup,maxpri=10]         * Thread[T1,5,printGroup]         * Thread[T2,5,printGroup]         * */    }    @Override    public void run() {        String groupName =                Thread.currentThread().getThreadGroup().getName() + " : "                        + Thread.currentThread().getName();        while (true){            System.out.println(" This is  " + groupName);            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            Thread.yield();        }    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

守护线程(daemon)

守护线程是一个特殊的线程,他在后台完成系统性的服务,比如垃圾回收等等。用户线程可以理解为系统工作线程,他们会完成业务操作。当用户线程全部结束后,系统就无事可做了。守护线程守护的对象也不存在了。因此当一个程序中就只有守护线程时,java虚拟机就会自然退出。

public class DaemonExample extends Thread{    @Override    public void run() {        while (true){            System.out.println("I am a Daemon Thread .. ");            try {                Thread.sleep(500);                System.out.println("I am a Daemon Thread ..after sleep ");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] a) throws InterruptedException {        Thread t = new DaemonExample();        t.setDaemon(true);        t.start();        //main线程相当于用户线程,当用户线程休眠2秒后,整个程序也随之结束        //如果t不设置为守护线程,t 会一直输出.        //结果是主线程休眠2秒后 守护线程也退出循环        Thread.sleep(2000);    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

线程优先级

优先级高的线程在竞争资源的时候回更加有优势,更可能抢占到资源,当然这只是一个概率问题,高优先级的线程可能也会抢占失败。他和线程优先级调度以及底层操作系统有密切关系。在各平台上表现不一。

线程安全与synchronized

非线程安全写入例子
这里写图片描述
就算是volatile修饰的变量,也无法保证正确写入,要从根本上解决这个问题,我们就必须保证多个线程之间是完全同步的。也就是Thread1在写入时,Thread2既不能读也不能写。这时我们就得通过synchronized关键字来解决了。它的工作是对同步代码加锁,使得每一次,只能有一个线程进入同步块。从而保证线程间的安全性,说白了就是让并行程序串行执行。

synchronized用法

1、指定加锁对象:对给定对象加锁,进入同步代码块要获得给定对象的锁。

public class SynchronizedExample implements Runnable{    static final Object o = new Object();    static int a = 0;    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){        //将锁加到指定static修饰的对象上            synchronized (o){                a++;            }        }    }    public static void main(String[] ac ) throws InterruptedException {        Thread t1 = new Thread(new SynchronizedExample());        Thread t2 = new Thread(new SynchronizedExample());        Thread t3 = new Thread(new SynchronizedExample());        t1.start();        t2.start();        t3.start();        t1.join();        t2.join();        t3.join();        System.out.println("add result : " + a);    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2、直接作用于实例方法:相当于对当前实例加锁,进入同步代码块要获得当前实例的锁。

public class SynchronizedExample implements Runnable{    static  int a = 0;    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){            //System.out.println(Thread.currentThread().getName() + " coming..");            synchronized (this){                a++;            }        }    }    public static void main(String[] ac ) throws InterruptedException {        /* 错误的Thread方法,因为这里synchronized是对实例上锁,而new创建了多个实例,所以锁无意义。        Thread t1 = new Thread(new SynchronizedExample());        Thread t2 = new Thread(new SynchronizedExample());        Thread t3 = new Thread(new SynchronizedExample());        */        //正确的创建Thread方法如下。所有的Thread都是通过同样的实例创建。所以同步是有用的。        SynchronizedExample aca = new SynchronizedExample();        Thread t1 = new Thread(aca);        Thread t2 = new Thread(aca);        Thread t3 = new Thread(aca);        t1.start();        t2.start();        t3.start();        t1.join();        t2.join();        t3.join();        System.out.println("add result : " + a);    }}/*** 结果是 :*  add result : 30000*/
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3 、 直接作用于静态方法:相当于对当前类加锁,进入同步块要获得当前类的锁。
    说明: 这个锁的影响范围更广,只要是调用这个类的方法,都必须拿到这个类的锁。
3.1 、 加到静态方法上

public class SynchronizedExample implements Runnable{    static  int a = 0;    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){                add();        }    }    public static synchronized void add(){        a++;    }    public static void main(String[] ac ) throws InterruptedException {        //将锁加到静态方法上,就相当于把锁加到类上,就可以将这个类所有的实例创建的线程进行同步。        Thread t1 = new Thread(new SynchronizedExample());        Thread t2 = new Thread(new SynchronizedExample());        Thread t3 = new Thread(new SynchronizedExample());        t1.start();        t2.start();        t3.start();        t1.join();        t2.join();        t3.join();        System.out.println("add result : " + a);    }}   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3.2 、 加到类上

public class SynchronizedExample implements Runnable{    static  int a = 0;    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){            //System.out.println(Thread.currentThread().getName() + " coming..");            synchronized (SynchronizedExample.class){                a++;            }        }    }    public static void main(String[] ac ) throws InterruptedException {        ....    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

实例1

public class SynchronizedExample implements Runnable{    static SynchronizedExample obj = new SynchronizedExample();    static  int a = 0;    /* //方法1    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){            //作用在给定的对象上.因此每次当线程进入synchronized时,            //线程都会请求obj实例的锁.            synchronized (obj){                a ++ ;            }        }    }*/    //方法2    @Override    public void run() {        for (int i = 0;i < 10000 ; i ++){            add();        }    }    public synchronized void add(){        a ++ ;    }    public static void main(String[] ac ) throws InterruptedException {        /**         * 注意Thread的创建方式,这里使用Runnable创建两个线程,并且这两个线程         * 都指向同一个Runnable接口实例(obj对象),这样才能保证两个线程在工作时         * 能够关注到同一个对象的锁上去.从而保证线程安全         *         * 而以下同步方法是错误的,因为创建的线程不是关注在同一个对象锁上.解决方法看示例2         * Thread t1 = new Thread(new SynchronizedExample());         * Thread t1 = new Thread(new SynchronizedExample());         */        Thread t1 = new Thread(obj);        Thread t2 = new Thread(obj);        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println("add result : " + a);    }}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

猜你喜欢

转载自blog.csdn.net/qq_43679720/article/details/86513784