赞
踩
【1】join 让主线程等待子线程运行结束后再继续运行:join方法中如果传入参数,则表示这样的意思:如果线程A 中掉用线程B的 join(10),则表示线程A 会等待线程B 执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是 线程A等待线程B 0秒,而是线程A 等待线程B 无限时间,直到线程B 执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))
【2】利用并发包里的 Excutors的 newSingleThreadExecutor产生一个单线程的线程池,而这个线程池的底层原理就是一个先进先出(FIFO)的队列。代码中 executor.submit依次添加了123线程,按照 FIFO的特性,执行顺序也就是123的执行结果,从而保证了执行顺序。
【3】使用 CountDownLatch 控制多个线程执行顺序 cutDown()方法和 await()方法:可以通过调用CounDownLatch对象的cutDown()方法,来使计数减1;如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0,才可以继续执行。
Java语言中提供了两种锁机制的实现对某个共享资源的同步:Synchronized 和 Lock 。其中 Synchronized 使用 Object 类对象本身的 notify()、wait()、notifyAll() 调度机制,而 Lock 使用 Condition 包进行线程之间的调度,完成 Synchronized 实现的所有功能
【1】用法不一样:synchronized 既可以加在方法上,也可以加在特定的代码块中,括号中表示需要的锁对象。而 Lock 需要显式的指定起始位置和终止位置。synchronized 是托管给 JVM 执行的,而 Lock 的锁定是通过代码实现,它有比 synchronized 更精确的线程语义。
【2】性能不一样:在 JDK5 中增加了一个 Lock 接口的实现类 ReentrantLock。它不仅拥有和 synchronized 相同的并发性和内存语义、还多了锁投票、定时锁、等候锁和中断锁。它们的性能在不同的情况下会有所不同。在资源竞争不激烈的情况下,Synchronized 的性能要优于 RenntrantLock,但是资源竞争激烈的情况下,Synchronized 性能会下降的非常快【使用了重量级锁,通过操作系统的互斥锁实现的】,而ReentrantLock 的性能基本保持不变。
【3】锁机制不一样:Synchronized 获得锁和释放锁的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且自动解锁,而 Condition 中的 await()、signal()、signalAll() 能够指定要释放的锁。不会因为异常而导致锁没有被释放从而引发死锁的问题。而 Lock 则需要开发人员手动释放,并且必须放在 finally 块中释放,否则会引起死锁问题。此外,Lock 还提供了更强大的功能,他的 tryLock() 方法可以采用非阻塞的方式去获取锁。
虽然 synchronized 与 Lock 都可以实现多线程的同步,但是最好不要同时使用这两种同步机制给统一共享资源加锁(不起作用),因为 ReentrantLock 与 synchronized 所使用的机制不同,所以它们运行是独立的,相当于两个种类的锁,在使用的时候互不影响。
面试题:【1】当一个线程进入一个对象的 synchronized() 方法后,其他线程是否能够进入此对象的其他方法?
答案:其他线程可进入此对象的非 synchronized 修饰的方法。如果其他方法有 synchronized 修饰,都用的是同一对象锁,就不能访问。
【2】如果其它方法是静态方法,且被 synchronized 修饰,是否可以访问?
答案:可以的,因为 static 修饰的方法,它用的锁是当前类的字节码,而非静态方法使用的是 this,因此可以调用。
当使用多线程访问同一数据时,非常容易出现线程安全问题,因此采用同步机制解决。Java提供了三种方法:
【1】synchronized 关键字:在Java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程调用对象的 synchronize 代码时,需要先获取这个锁,然后再去执行相应的代码,执行结束后,释放锁。synchronize 关键字主要有两种用法(synchronize 方法和 synchronize 代码块)
1)、synchronized 方法:在方法的声明前加 synchronize 关键字: 将需要对同步资源的操作放入 test() 方法中,就能保证此资源在同一时刻只能被一个线程调用,从而保证资源的安全性。然而当此方法体规模非常大时,会影响系统的效率。
public synchronize void test();
2)、synchronized 代码块:既可以把任意的代码段声明为 synchronized,也可以指定上锁的对象,有非常高的灵活性。
- synchronized(syncObject){
- //访问syncObject的代码块
- }
【2】wait() 方法与 notify() 方法:当使用 synchronized 来修饰某个共享资源时,如果线程A1在执行 synchronized 代码,线程A2也要执行此 synchronize 的代码,线程A2将要等到线程A1执行完后执行,这种情况可以使用 wai() 和 notify()。必须是统一把锁,才生效。
- class NumberPrint implements Runnable{
- private int number;
- public byte res[];
- public static int count = 5;
- public NumberPrint(int number, byte a[]){
- this.number = number;
- res = a;
- }
- public void run(){
- synchronized (res){
- while(count-- > 0){
- try {
- res.notify();//唤醒等待res资源的线程,把锁交给线程(该同步锁执行完毕自动释放锁)
- System.out.println(" "+number);
- res.wait();//释放CPU控制权,释放res的锁,本线程阻塞,等待被唤醒。
- System.out.println("------线程"+Thread.currentThread().getName()+"获得锁,wait()后的代码继续运行:"+number);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }//end of while
- return;
- }//synchronized
-
- }
- }
- public class WaitNotify {
- public static void main(String args[]){
- final byte a[] = {0};//以该对象为共享资源
- new Thread(new NumberPrint((1),a),"1").start();
- new Thread(new NumberPrint((2),a),"2").start();
- }
- }
输出结果:
- 1
- 2
- ------线程1获得锁,wait()后的代码继续运行:1
- 1
- ------线程2获得锁,wait()后的代码继续运行:2
- 2
- ------线程1获得锁,wait()后的代码继续运行:1
- 1
- ------线程2获得锁,wait()后的代码继续运行:2
【3】Lock:JDK5 新增加 Lock 接口以及它的一个实现类 ReentrantLock(重入锁),也可以实现多线程的同步;
1)、lock():以阻塞的方式获取锁,也就是说,如果获取到了锁,就会执行,其他线程需要等待,unlock() 锁后别的线程才能执行,如果别的线程持有锁,当前线程等待,直到获取锁后返回。
- public int consume(){
- int m = 0;
- try {
- lock.lock();
- while(ProdLine.size() == 0){
- System.out.println("队列是空的,请稍候");
- empty.await();
- }
- m = ProdLine.removeFirst();
- full.signal();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally{
- lock.unlock();
- return m;
- }
- }
2)、tryLock():以非阻塞的方式获取锁。只是尝试性地去获取一下锁,如果获取到锁,立即返回true,否则,返回false
3)、tryLock(long timeout,TimeUnit unit):在给定的时间单元内,获取到了锁返回true,否则false
4)、lockInterruptibly():如果获取了锁,立即返回;如果没有锁,当前线程处于休眠状态,直到获取锁,或者当前线程被中断(会收到InterruptedException异常)。它与 lock() 方法最大的区别在于如果 lock() 方法获取不到锁,就会一直处于阻塞状态,且会忽略 Interrupt() 方法。
Java虚拟机允许应用程序并发地运行多个线程,在 Java语言中实现多线程的方法有三种,其中前两种为常用方法:
【1】继承 Thread类,重写 run()方法:Thread本质上也是实现了 Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过 Thread类的 start()方法,start() 方法是一个本地(native)方法,它将启动一个新的线程,并执行 run() 方法(执行的是自己重写了Thread类的 run()方法),同时调用 start() 方法并不是执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行多线程代码由操作系统决定。
- class MyThread extends Thread{//创建线程类
- public void run(){
- System.out.println("Thread Body");//线程的函数体
- }
- }
-
- public class Test{
- public static void main(String[] args){
- MyThread thread = new Thread
- thread.run();//开启线程
- }
- }
【2】实现 Runnable 接口,并实现该结构的 run() 方法: 1)自定义实现 Runnable 接口,实现 run() 方法。
2)创建 Thread 对象,用实现 Runnable 接口的对象作为参数实例化该 Thread 对象。
3)调用 Thread 的 start() 方法。
- class MyThread implements Runnable{
- pulic void run(){
- System.out.println("Thread Body");
- }
- }
-
- public class Test{
- public static void main(String[] args){
- MyThread myThread = new MyThread;
- Thread thread = new Thread(myThread);
- thread.start();//启动线程
- }
- }
其实,不管是哪种方法,最终都是通过 Thread 类的 API 来控制线程。
【3】实现 Callable 接口,重写 call() 方法:Callable 接口实际是属于 Executor 框架中的功能类,Callable 结构与 Runnable接口的功能类似,但提供了比 Runnable 更强大的功能,主要体现在如下三点:
1)、Callable 在任务结束后可以提供一个返回值,Runnable 无法提供该功能。
2)、Callable 中的 call() 方法可以抛出异常,而 Runnable 中的 run() 不能抛出异常。
3)、运行 Callable 可以拿到一个 Future 对象,Future 对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程输入异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用 Future 来监控目标线程来调用call() 方法的情况,当调用 Future 的 get() 方法以获取结果时,当前线程会阻塞,直到目标线程的 call() 方法结束返回结果。
- public class CallableAndFuture{
- //创建线程类
- public static class CallableTest implements Callable{
- public String call() throws Exception{
- return "Hello World!";
- }
- }
- public static void main(String[] args){
- ExecutorService threadPool = Executors.newSingleThreadExecutor();
- Future<String> future = threadPool.submit(new CallableTest());
- try{
- System.out.println("waiting thread to finish");
- System.out.println(future.get());
- }catch{Exception e}{
- e.printStackTrace
- }
- }
- }
输出结果如下:
- waiting thread to finish
- Hello World!
当需要实现多线程时,一般推荐使用 Runnable 接口方式,因为 Thread 类定义了多种方法可以被派生类使用或重写,但是只有 run() 方法必须被重写,在 run() 方法中实现这个线程的主要功能,这当然也是实现 Runnable 接口所需的方法。再者,我们很多时候继承一个类是为了去加强和修改这个类才去继承的。因此,如果我们没有必要重写 Thread 类中的其他方法,那么通过继承 Thread 类和实现 Runnable 接口的效果是相同的,这样的话最好还是使用 Runnable 接口来创建线程。
【引申】:一个类是否可以同时继承 Thread 类和实现 Runnable 接口?答案是可以的。
- public class Test Extend Thread implements Runnable{
- public static void main(String[] args){
- Thread thread = new Thread(new Test);
- thread.start();
- }
- }
如上,Test 实现了 Runnable 接口,但是并没有实现接口的 run() 方法,可能会认为有编译错误,但实际是可以编译通过的,因为 Test 从 Thread 中继承了 run() 方法,这个继承的 run() 方法被当做 Runnable 接口的实现,因此是可以编译通过的,当然也可以自己重写,重写后再调用 run() 方式时,那就是自己重写后的方法了。
通常,系统通过调用线程类的 start() 方法启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以被 JVM调用执行,执行的过程中,JVM通过调用目标类的 run() 方法来完成实际的操作,当 run() 方法结束后,线程也就会终止。
如果直接调用线程类的 run() 方法,就会被当做一个普通函数调用,程序中仍然只有一个主程序,也就是说 start() 方法能够异步调用 run() 方法,但是直接调用 run() 方法却是同步的,也就无法达到多线程的目的。
在多线程的环境中,通常会遇到数据共享问题,为了确保共享资源的正确性和安全性,就必须对共享数据进行同步处理(也就是锁机制)。对共享数据进行同步操作(增删改),就必须要获得每个线程对象的锁(this锁),这样可以保证同一时刻只有一个线程对其操作,其他线程要想对其操作需要排队等候并获取锁。当然在等候队列中优先级最高的线程才能获得该锁,从而进入共享代码区。
Java 语言在同步机制中提供了语言级的支持,可以通过使用 synchronize 关键字来实现同步,但该方法是以很大的系统开销作为代价的,有时候甚至可能造成死锁,所以,同步控制并不是越多越好,要避免所谓的同步控制。实现同步的方法有两种:①、同步方法(this锁)。②、同步代码块(this锁或者自定义锁)当使用 this 锁时,就与同步方法共享同一锁,只有当①释放,②才可以使用。同时,同步代码块的范围也小于同步方法,建议使用,相比之下能够提高性能。
【1】提高执行效率,减少程序的响应时间。因为单线程执行的过程只有一个有效的操作序列,如果某个操作很耗时(或等待网络响应),此时程序就不会响应鼠标和键盘等操作,如果使用多线程,就可以将耗时的线程分配到一个单独的线程上执行,从而使程序具备号更好的交互性。
【2】与进程相比,线程的创建和切换开销更小。因开启一个新的进程需要分配独立的地址空间,建立许多数据结构来维护代码块等信息,而运行于同一个进程内的线程共享代码段、数据段、线程的启动和切换的开销比进程要少很多。同时多线程在数据共享方面效率非常高。
【3】目前市场上服务器配置大多数都是多 CPU或多核计算机等,它们本身而言就具有执行多线程的能力,如果使用单个线程,就无法重复利用计算机资源,造成资源浪费。因此在多 CPU 计算机上使用多线程能提高 CPU 的利用率。
【4】利用多线程能简化程序程序的结构,是程序便于理解和维护。一个非常复杂的进程可以分成多个线程来执行。
sleep() 是使线程暂停执行一段时间的方法。wait() 也是一种使线程暂停执行的方法,直到被唤醒或等待时间超时
区别:1)、原理不同:sleep() 方法是 Thread 类的静态方法,是线程用来控制自身流程的,它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到时间一到,此线程会自动 “苏醒”。wait() 方法是 Object 类的方法,用于线程间通讯,这个方法会使当前线程拥有该对象锁的进程等待,直到其他线程调用 notify() 方法(或 notifyAll 方法)时才“醒”来,不过开发人员可可以给它指定一个时间,自动“醒”来。与 wait() 方法配套的方法还有 notify() 和 notifyAll() 方法。
2)、对锁的处理机制不同:由于 sleep() 方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通讯,因此,调用 sleep() 方法并不会释放锁。而 wait() 方法则不同,调用后会释放掉他所占用的锁,从而使线程所在对象中的其他 Synchronized 数据可被别的线程使用。
3)、使用区域不同:由于 wait() 的特殊意义,因此它必须放在同步控制方法或者同步代码块中使用,而 sleep() 则可以放在任何地方使用。
4)、sleep() 方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常:在 sleep 的过程中,有可能被其他对象调用它的 interrupt(),产生 InterruptedException 异常。
sleep 不会释放“锁标志”,容易导致死锁问题的发生,因此,一般情况下,不推荐使用 sleep() 方法。而推荐使用 wait() 方法
【1】sleep() 给其他线程运行机会时,不考虑线程的优先级,因此会给低优先级的线程以运行的机会,而 yield() 方法只会给相同优先级或更高优先级的线程以运行的机会。
【2】sleep() 方法会转入阻塞状态,所以,执行 sleep() 方法的线程在指定的时间内不会被执行,而 yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 方法的线程很可能在进入到可执行状态后马上又被执行。
【1】stop() 方法:它会释放已经锁定的所有监视资源,如果当前任何一个受监视资源保护的对象处于不一致的状态(执行了一部分),其他线程将会获取到修改了的部分值,这个时候就可能导致程序执行结果的不确定性,并且这种问题很难被定位。
【2】suspend() 方法:容易发生死锁。因为调用 suspend() 方法不会释放锁,这就会导致此线程挂起。
鉴于以上两种方法的不安全性,Java语言已经不建议使用以上两种方法来终止线程了。
【3】一般建议采用的方法是让线程自行结束进入 Dead 状态。一个线程进入 Dead 状态,既执行完 run() 方法,也就是说提供一种能够自动让 run() 方法结束的方式,在实际中,我们可以通过 flag 标志来控制循环是否执行,从而使线程离开run方法终止线程
- public class MyThread implements Runnable{
- private volatile Boolean flag;
- public void stop(){
- flag=false;
- }
- public void run(){
- while(flag);//do something
- }
- }
上述通过 stop() 方法虽然可以终止线程,但同样也存在问题;当线程处于阻塞状态时(sleep()被调用或wait()方法被调用或当被I/O阻塞时),上面介绍的方法就不可用了。此时使用 interrupt()方法来打破阻塞的情况,当 interrupt()方法被调用时,会跑出 interruptedException异常,可以通过在 run() 方法中捕获这个异常来让线程安全退出。
- public class MyThread implements Runnable{
- public static void main(String[] args){
- Thread thread = new Thread(new MyThread);
- public void run(){
- System.out.println("thread go to sleep");
- try{
- //用休眠来模拟线程被阻塞
- Thread.sleep(5000);
- System.out.println("thread finish");
- } catch (InterruptedException e){
- System.out.println("thread is interrupted!);
- }
- }
- thread.start();
- thread.interrupt();
- }
- }
程序运行结果:
- thread go to sleep
- thread is interrupted!
如果 I/O 停滞,进入非运行状态,基本上要等到 I/O 完成才能离开这个状态。或者通过抛发异常,使用 readLine() 方法在等待网络上的一个信息,此时线程处于阻塞状态,让程序离开 run() 就使用 close() 方法来关闭流,这个时候就会跑出 IOException 异常,通过捕获此异常就可以离开 run()。
进程是指一段正在执行的程序。而线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段、堆空间)及一些进程级的文件(列如:打开的文件),但是各个线程拥有自己的栈空间。在操作系统级别上,程序的执行都是以进程为单位的,而每个进程中通常都会有多个线程互不影响地并发执行。
Java提供了两种线程:守护线程和用户线程。守护线程又被称为“服务进程”、“精灵线程”、“后台线程”,是指在程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分,通俗点讲,每一个守护线程都是 JVM 中非守护线程的“保姆”。典型例子就是“垃圾回收器”。只要 JVM 启动,它始终在运行,实时监控和管理系统中可以被回收的资源。
用户线程和守护线程几乎一样,唯一的不同就在于如果用户线程已经全部退出运行,只剩下守护线程运行,JVM 也就退出了因为当所有非守护线程结束时,没有了守护者,守护线程就没有工作可做,也就没有继续运行程序的必要了,程序也就终止了,同时会“杀死”所有的守护线程。也就是说,只要有任何非守护线程运行,程序就不会终止。
Java语言中,守护线程优先级都较低,它并非只有 JVM 内部提供,用户也可以自己设置守护线程,方法就是在调用线程的 start()方法之前,设置 setDaemon(true) 方法,若将参数设置为 false,则表示用户进程模式。需要注意的是,守护线程中产生的其它线程都是守护线程,用户线程也是如此。
- class ThreadDemo extends Thread{
- public void run(){
- System.out.println(Thread.currentThread().getName()+":begin");
- try{
- Thread.sleep(1000);
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+":end");
- }
- }
定义一个守护线程:
- public class Test{
- public static void main(String[] args){
- System.out.println("test3:begin");
- Thread thread = new ThreadDemo();
- thread.setDaemon(true);
- thread.start();
- System.out.println("test3:end");
- }
- }
程序运行结果:
- test3:begin
- test3:end
- Thread-0:begin
从运行结果上可以发现,没有输出 Thread-0:end。之所以是这样的结果,是在启动线程前,将其设置成为了守护线程了,当程序中只有守护线程时,JVM 是可以退出的,也就是说,当 JVM 中只有守护线程运行时,JVM会自动关闭。因此,当 test3 方法调用结束后,mian 线程将退出,此时线程 thread 还处于休眠状态没有运行结果,但是由于此时只有这个守护线程在运行,JVM将会关闭,因此不会输出:“Thread-0:end”。
在 Java语言中,join() 方法的作用是让调用该方法的线程在执行完 run() 方法后,再执行 join() 方法后面的代码。简单点说就是将两个线程合并,并实现同步功能。具体而言,可以通过线程A的 join() 方法来等待线程A的结束,或者使用线程A的 join(2000) 方法来等待线程A的结束,但最多只等 2s。多线程示例如下:
- class ThreadImp implements Runnable{
- public void run(){
- try{
- System.out.println("Begin ThreadImp");
- Thread.sleep(5000);
- System.out.println("End ThreadImp");
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
主函数示例如下:
- public class JoinTest{
- public static void main(String[] args){
- Thread t = new Thread(new ThreadImp());
- t.start();
- try{
- t.join(1000);//主线程等待1s
- if(t.isAlive()){
- System.out.println("t has not finished");
- }else{
- System.out.println("t has finished");
- }
- System.out.println("joinFinish");
- }catch(InterruptedExcetion e){
- e.printStackTrace();
- }
- }
- }
运行结果:
- Begin ThreadImp
- t has not finished
- joinFinish
- End ThreadImp
Volatile 是 Java 虚拟机提供的轻量级同步机制。具有如下三种特性:
1)、保证可见性:被 Volatile 修改的变量会被存储在主内存中,而不是工作内存中。可以被其他线程共享访问。
2)、不保证原子性:既对修饰的变量的操作是可分割的(举例:i++ 十次得到的结果,不一定是10),也就是线程不安全的。
3)、禁止指令重排:计算机会对编译后的 java 语言进行编译。为了提高运行效率,会对指令进行重排。单线程重排后,是没有问题的,但是在多线程中重排就会影响执行结果。
Java 虚拟机中的同步(Synchronization)基于进入和退出 Monitor对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)还是隐式同步都是如此。在 Java语言中,同步用的最多的地方可能是被 synchronized 修饰的同步方法。同步方法并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法表结构的 ACC_SYNCHRONIZED 标志来隐式实现的。
同步代码块:monitorenter 指令插入到同步代码块的开始位置,monitorexit 指令插入到同步代码块的结束位置,JVM需要保证每一个 monitorenter都有一个 monitorexit与之相对应。任何对象都有一个 monitor与之相关联,当且一个 monitor被持有之后,他将处于锁定状态。线程执行到 monitorenter指令时,将会尝试获取对象所对应的 monitor所有权,即尝试获取对象的锁;
Java虚拟机对 synchronize的优化:锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级,关于重量级锁,前面我们已详细分析过,下面我们将介绍偏向锁和轻量级锁以及JVM的其他优化手段。
segment 分段锁使用场景最熟悉的应该属于 concurrentHashMap在 JDK1.7 的时候用来保证线程安全。那么主要看下其源码即可:链接
【博客链接】:链接
【博客链接】:链接
当需要阻塞或唤醒一个线程时,都会使用 LockSupport 工具类来完成响应工作。LockSupport 定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而 LockSupport 也成为构建同步组件的基本工具。LockSupport 定义了一组以 park 开头的方法用来阻塞当前线程,以及 unpark(Thread thread) 方法唤醒一个被阻塞的线程。
方法名称 | 描述 |
void park() | 阻塞当前线程,如果调用unpark(Thread thread)方法或者当前线程被中断interrupt(),才能从 park() 方法返回。 |
void parkNanos(long nanos) | 阻塞当前线程,最长不超过 nanos 纳秒,返回条件在 park() 基础上增加了超时返回。 |
void parkUntil(long deadline) | 阻塞当前线程,直到 deadline时间(从 1970年开始到 deadline 时间的毫秒数)。 |
void unpark(Thread thread) | 唤醒处于阻塞状态的线程 thread |
在 JDK 6中,LockSupport 增加了 park(Object blocker)、parkNanos(Object blocker,long nanos) 和 parkUntil(Object blocker,long deadline) 三个方法,用于实现阻塞当前线程的功能,其中参数 blocker 是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题排查和系统监控。有阻塞对象的 park 方法能够传递给开发人员更多的现场信息。这是由于 Java5 之前,当线程阻塞(使用 synchronized 关键字)在一个对象上时,通过线程 dump 能够查看到线程的阻塞对象,方便问题定位,而在 Java5 推出的 Lock 等并发工具时却遗漏了这一点,即使在线程 dump 时无法提供阻塞对象的信息。因此,在 Java6 中,LockSupport 新增了有阻塞对象的park() 方法。
【博客链接】:链接
【博客链接】:链接
ThreadLocal 也叫线程本地变量。每一个 ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。ThreadLocal中的变量是线程分离的,别的线程无法使用,保证了变量的安全性。
public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();
【ThreadLoacal 原理】:首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个 ThreadLocalMap类对应的 get()、set() 方法。set 方法源码如下:
- //设置当前线程的ThreadLocal变量的副本为指定的值
- public void set(T value) {
- Thread t = Thread.currentThread()
- //获取当前线程的 ThreadLocalMap 对象(每个线程都有一个 ThreadLocal.ThreadLocalMap对象)如果为空,
- //则创建一个 ThreadLocalMap 对象,否则设置值。
- ThreadLocalMap map = getMap(t);
- if(map !=null)
- map.set(this, value);
- else
- createMap(t, value);
- }
-
- //返回线程的threadLocals变量,它的类型是ThreadLocal.ThreadLocalMap
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-
- //创建一个 ThreadLocalMap 实例
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
-
- //ThreadLocalMap 底层:是一个数组,数组中元素类型是Entry类型
- ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
- table = new Entry[INITIAL_CAPACITY];
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- table[i] = new Entry(firstKey, firstValue);
- size = 1;
- setThreshold(INITIAL_CAPACITY);
- }
也就是说,每个线程都维护了一个 ThreadLocal.ThreadLocalMap类型的对象,而 set操作其实就是以 ThreadLocal变量为 key,以我们指定的值为 value,最后将这个键值对封装成 Entry对象放到该线程的 ThreadLocal.ThreadLocalMap对象中。每个ThreadLocal 变量在该线程中都是 ThreadLocal.ThreadLocalMap 对象中的一个 Entry。既然每个 ThreadLocal变量都对应ThreadLocal.ThreadLocalMap 中的一个元素,那么就可以对这些元素进行读写删除操作。
get 方法源码如下:就是从当前线程的 ThreadLocal.ThreadLocalMap对象中取出对应的 ThreadLocal变量所对应的值。
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();
- }
【注意事项】:实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。所以在 finally 中都需要通过 remove()方法。如果线程关闭,Thread类会进行一些清理工作,包括清理ThreadLocalMap。但是如果使用线程池,由于线程可能并不是真正的关闭(比如newFixedThreadPool会保持线程一只存活)。因此,如果将一些大对象存放到 ThreadLocalMap中,可能会造成内存泄漏。因为线程没有关闭,无法回收,但是这些对象不会再被使用了。如果希望及时回收对象,则可以使用Thread.remove()方法将变量移除。
- public void remove() {
- ThreadLocalMap m = getMap(Thread.currentThread());
- if (m != null)
- m.remove(this);
- }
【ThreadLocal使用场景】 最常见的 ThreadLocal使用场景为用来解决数据库连接、Session管理等。
【1】设置最大线程数:对于特定硬件上的负载,最大线程数设置为多少最好呢?这个问题回答起来并不简单:它取决于负载特性以及底层硬件。特别是,最优线程数还与每个任务阻塞的频率有关。假设 JVM有4个 CPU可用,很明显最大线程数至少要设置为4。的确,除了处理这些任务,JVM还有些线程要做其他的事,但是它们几乎从来不会占用一个完整的CPU,至于这个数值是否要大于4,则需要进行大量充分的测试。有以下两点需要注意:①、一旦服务器成为瓶颈,向服务器增加负载是非常有害的;②、对于 CPU密集型或 IO密集型的机器增加线程数实际会降低整体的吞吐量;
【2】设置最小线程数:一旦确定了线程池的最大线程数,就该确定所需的最小线程数了。大部分情况下,开发者会直截了当的将他们设置成同一个值。将最小线程数设置为其他某个值(比如1),出发点是为了防止系统创建太多线程,以节省系统资源。指定一个最小线程数的负面影响相当小。如果第一次就有很多任务要执行,会有负面影响:这是线程池需要创建一个新线程。创建线程对性能不利,这也是为什么起初需要线程池的原因。一般而言,对于线程数为最小值的线程池,一个新线程一旦创建出来,至少应该保留几分钟,以处理任何负载飙升。空闲时间应该以分钟计,而且至少在10分钟到30分钟之间,这样可以防止频繁创建线程。
【3】线程池任务大小:等待线程池来执行的任务会被保存到某个队列或列表中;当池中有线程可以执行任务时,就从队列中拉出一个。这会导致不均衡:队列中任务的数量可能变得非常大。如果队列太大,其中的任务就必须等待很长时间,直到前面的任务执行完毕。对于任务队列,线程池通常会限制其大小。但是这个值应该如何调优,并没有一个通用的规则。若要确定哪个值能带来我们需要的性能,测量我们的真实应用是唯一的途径。不管是哪种情况,如果达到了队列限制,再添加任务就会失败。ThreadPoolExecutor有一个rejectedExecution方法,用于处理这种情况,默认会抛出RejectedExecutionExecption。应用服务器会向用户返回某个错误:或者是HTTP状态码500,或者是Web服务器捕获异常错误,并向用户给出合理的解释消息,其中后者是最理想的。
【4】设置 ThreadPoolExecutor的大小:线程池的一般行为是这样的:创建时准备最小数目的线程,如果来了一个任务,而此时所有的线程都在忙碌,则启动一个新线程(一直到达到最大线程数),任务就会立即执行。否则,任务被加入到等待队列,如果队列中已经无法加入新任务,则拒接之。根据所选任务队列的类型,ThreadPoolExecutor会决定何时会启动一个新线程。有以下三种可能:
■ SynchronousQueue:如果 ThreadPoolExecutor搭配的是 SynchronousQueue,则线程池的行为和我们预期的一样,它会考虑线程数:如果所有的线程都在忙碌,而且池中的线程数尚未达到最大,则会为新任务启动一个新线程。然而这个队列没办法保存等待的任务:如果来了一个任务,创建的线程数已经达到最大值,而且所有的线程都在忙碌,则新的任务都会被拒绝,所以如果是管理少量的任务,这是个不错的选择,对于其他的情况就不适合了。
■ 无界队列:如果 ThreadPoolExecutor搭配的是无界队列,如LinkedBlockingQueue,则不会拒绝任何任务(因为队列大小没有限制)。这种情况下,ThreadPoolExecutor最多仅会按照最小线程数创建线程,也就是说最大线程池大小被忽略了。如果最大线程数和最小线程数相同,则这种选择和配置了固定线程数的传统线程池运行机制最为接近。
■ 有界队列:搭配了有界队列,如 ArrayBlockingQueue的 ThreadPoolExecutor会采用一个非常负责的算法。比如假定线程池的最小线程数为4,最大为8所用的 ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即最小线程数)。即使队列完全填满,也就是说有10个处于等待状态的任务,ThreadPoolExecutor也只会利用4个线程。如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。这个算法背后的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;这时第二个节流阀—最大线程数就起作用了。
对于上面提到的每一种选择,都能找到很多支持或反对的依据,但是在尝试获得最好的性能时,可以应用 KISS原则"Keep it simple,stupid"。可以将最小线程数和最大线程数设置为相同,在保存任务方面,如果适合无界队列,则选择LinkedBlockingQueue;如果适合有界队列,则选择 ArrayBlockingQueue。
【博客链接】:链接
【博客链接】:链接
【博客链接】:链接
【1】使用循环CAS+volatile,实现 i++原子操作;
【2】使用 synchronized,实现 i++原子操作;
【3】使用 Lock锁机制,实现i++原子操作;
【4】Semaphore构造方法中传入的参数是1的时候,此时线程并发数最多是1个,即是线程安全的,这种方式也可以做到现场互斥。
【5】使用 AtomicInteger:由硬件提供原子操作指令实现的;
【博客链接】:链接
AQS(AbstractQueuedSynchronizer):这个类在 java.util.concurrent.locks包,AbstractQuenedSynchronizer 抽象的队列同步器。是除了 Java自带的 synchronized 关键字之外的锁机制。
AQS的核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS是用 CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。注意:AQS 是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。AQS 是将每一条请求共享资源的线程封装成一个 CLH锁队列的一个结点(Node),来实现锁的分配。
【博客链接】:链接
Semaphore 可以维护当前访问自身的线程个数,并提供了同步机制。使用 Semaphore可以控制同时访问资源的线程个数(即允许n个任务同时访问这个资源),例如,实现一个文件允许的并发访问数。单个信号量的 Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。Java 实现互斥线程同步有三种方式synchronized、lock 、单Semaphore。
- public class SemaphoreTest {
- public static void main(String[] args) {
- ExecutorService service = Executors.newCachedThreadPool();
- //第二个参数表示后续的线程是随机获得优先机会,或者按照先来后到的顺序获得机会,
- //这取决于构造Semaphore对象时传入的参数选项。
- final Semaphore semaphore = new Semaphore(1,true);
- for(int i=0;i<10;i++){
- Runnable runnable = new Runnable(){
- public void run(){
- try {
- sp.acquire();
- } catch (InterruptedException e1) {
- e1.printStackTrace();
- }
- System.out.println("线程" + Thread.currentThread().getName() +
- "进入,当前已有" + (3-sp.availablePermits()) + "个并发");
- try {
- Thread.sleep((long)(Math.random()*10000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("线程" + Thread.currentThread().getName() +
- "即将离开");
- sp.release();
- //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
- System.out.println("线程" + Thread.currentThread().getName() +
- "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");
- }
- };
- service.execute(runnable);
- }
- }
- }
【1】管道:看一条 Linux 的语句 netstat -anp | grep 8080 学过 Linux 命名的估计都懂这条语句的含义,其中”|“是管道的意思,它的作用就是把前一条命令的输出作为后一条命令的输入。在这里就是把 netstat -anp 的输出结果作为 grep 8080 这条命令的输入。如果两个进程要进行通信的话,就可以用这种管道来进行通信了。并且这种通信方式是单向的,只能把第一个命令的输出作为第二个命令的输入,如果进程之间想要互相通信的话,那么需要创建两个管道。
【2】中间件:我们可以用消息队列的通信模式来解决这个问题,例如 a 进程要给 b 进程发送消息,只需要把消息放在对应的消息队列里就行了,b 进程需要的时候再去对应的消息队列里取出来。同理,b 进程要个 a 进程发送消息也是一样。这种通信方式也类似于缓存吧。缺点是,如果 a 进程发送的数据占的内存比较大,并且两个进程之间的通信特别频繁的话,消息队列模型就不大适合了。因为 a 发送的数据很大的话,意味发送消息(拷贝)这个过程需要花很多时间来读内存。
【3】共享内存:共享内存这个通信方式就可以很好着解决拷贝所消耗的时间了。我们都知道,系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块虚拟地址空间来,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但有一部分却是映射到相同的物理内存,这就完成了内存共享机制了。
【4】信号量:共享内存最大的问题就是多进程竞争内存的问题,就像类似于我们平时说的线程安全问题。如何解决这个问题?这个时候我们的信号量就上场了。信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
【5】Socket:上面我们说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。
线程是指程序在运行的过程中,能够执行程序代码的一个执行单元。Java语言中,线程有四种状态:运行、就绪、挂起、结束
【1】如果线程被 Object.wait、Thread.join 和 Thread.sleep 三种方法之一阻塞或者1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态。此时调用该线程的 interrupt()方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt() 将不起作用,直到执行到 wait()、sleep()、join()时,才马上会抛出 InterruptedException。
当一个线程运行时,另一个线程可以调用对应的 Thread对象的 interrupt() 方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。中断的结果线程是死亡还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像 stop方法那样会中断一个正在运行的线程。
- public class Test {
- public static void main(String[] args) throws InterruptedException {
- RunTest rt = new RunTest();
- Thread t = new Thread(rt);
- t.start();
- t.interrupt();
- }
- }
-
- class RunTest implements Runnable {
- @Override
- public void run() {
- try {
- for (long i = 0; i < 10000000L; i++) {
- System.out.println("I'm doing a time consuming task");
- }
- // TimeUnit.SECONDS.sleep(2); 这条语句会被中断,抛出java.lang.InterruptedException异常
- System.out.println("Thread finished.");
- } catch (Exception e) {
- e.printStackTrace();
- System.out.println("Thread execption.");
- }
- }
- }
RunTest 线程是在循环一个非常大的数字,但它没有阻塞,所以 interrupt()方法对它无效,RunTest线程会被正常执行完,打印Thread finished。
【2】MyThreadA.isInterrupted():判断 Thread 线程是否设置了中断标记,如果该线程已经中断,则返回 true;否则返回 false。需要注意的是,如果线程被中断且已经抛出了异常,它会清除中断标志。
【3】Thread.interrupted():这个是静态方法,直接Thread.interrupted()就好了,它会判断当前线程是否被设置了中断标志,并且清除中断标志。重点:静态方法,当前线程,清除中断标志。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
- public static boolean interrupted() {
- return currentThread().isInterrupted(true);
- }
interrupt 相关的三个方法就写完了,那么对于线程执行时间太长,但又不是阻塞的情况,该怎么办呢(例子中很大的 for循环)?ThreadPoolExecutor.shutdown()、ThreadPoolExecutor.shutdownNow() 以及 ThreadPoolExecutor.awaitTermination(long timeout, TimeUnit unit),Future.get() 方法都试过了,都不能解决。最后在 shutdownNow() 方法的注释找到了结果。
用博主自己的意思润色翻译为:shutdownNow()方法只能尽可能的尝试去停止正在运行的线程任务,但并不保证一定能成功。比如,此方法的典型实现是通过给线程任务添加一个中断标记 interrupt。但是呢,如果正在运行的线程任务对 interrupt()方法不感冒的话(不属于Object.wait, Thread.join和Thread.sleep阻塞),那么这部分的线程任务是不会停止的。
synchronized 在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized 功能相似的 reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么 reentrantLock.lock() 方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个 InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly() 方法,它就相当于一个超时设为无限的 tryLock方法。
【4】 interrupt() 方法的使用:在阅读线程池源码的时候,我们发现关闭线程池的时候就是使用的 interrupt()方法,我们来看一下线程池关闭这块的源码。我们看到很多文章都说,我们在关闭线程池的时候不要直接调用 shutdownNow()方法,而是需要先调用shutdown() 方法接着调用 awaitTermination(long timeout, TimeUnit unit)方法,那到底为什么这样说呢,我们通过源码来分析一下。shutdownNow() 方法的执行逻辑就是将线程池状态修改为 STOP,然后调用线程里的所有线程的 interrupt()方法。
- public List<Runnable> shutdownNow() {
- List<Runnable> tasks;
- final ReentrantLock mainLock = this.mainLock;
- mainLock.lock();
- try {
- checkShutdownAccess();
- //原子性的修改线程池的状态为STOP
- advanceRunState(STOP);
- //遍历线程里的所有工作限次,然后调用线程的interrupt方法。
- interruptWorkers();
- //将队列中还没有执行的任务放到列表中,返回给调用方
- tasks = drainQueue();
- } finally {
- mainLock.unlock();
- }
- /*在以下情况将线程池变为TERMINATED终止状态
- * shutdown 且 正在运行的worker 和 workQueue队列 都empty
- * stop 且 没有正在运行的worker
- *
- * 这个方法必须在任何可能导致线程池终止的情况下被调用,如:
- * 减少worker数量
- * shutdown时从queue中移除任务
- *
- * 这个方法不是私有的,所以允许子类ScheduledThreadPoolExecutor调用
- */
- tryTerminate();
- return tasks;
- }
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
【Executors 返回线程池对象的弊端如下】:FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool: 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。正是因为这个特性,当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞。
当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到所有的线程释放锁。通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞。
读写锁适合于对数据结构的读次数比写次数多得多的情况。因为,读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁又叫共享-独占锁。
ReadWriteLock 同 Lock一样也是一个接口,提供了 readLock和 writeLock两种锁的操作机制,一个是只读的锁,一个是写锁
- // 实际实现类--ReentrantReadWriteLock,默认非公平模式
- private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-
- //读
- public void get(){
- //使用读锁
- readWriteLock.readLock().lock();
- try {
- System.out.println(Thread.currentThread().getName()+" : "+number);
- }finally {
- readWriteLock.readLock().unlock();
- }
- }
- //写
- public void set(int number){
- readWriteLock.writeLock().lock();
- try {
- this.number = number;
- System.out.println(Thread.currentThread().getName()+" : "+number);
- }finally {
- readWriteLock.writeLock().unlock();
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。