Error[8]: Undefined offset: 3081, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

Java 并发编程》共享模型之管程

1. 共享带来的问题 1.1 临界区

例如,下面代码中的临界区

static int counter = 0
static void increment() {
	// 临界区
	counter++;
}
static void decrement() {
	// 临界区
	counter--;
}
1.2 竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

2. synchronized 解决方案

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

这里使用阻塞式的解决方案:synchronized 来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻最多只有一个线程能持有【对象锁】,其他线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。

值得注意的是,虽然 Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

2.1 synchronized 语法
synchronized(对象) {   //线程1,线程2(blocked)
	临界区
}

案例代码

static int counter = 0; 
//创建一个公共对象,作为对象锁的对象
static final Object room = new Object();
 
public static void main(String[] args) throws InterruptedException {    
	Thread t1 = new Thread(() -> {        
    for (int i = 0; i < 5000; i++) {            
        synchronized (room) {     
        	counter++;            
       	 }       
 	   }    
    }, "t1");
 
    Thread t2 = new Thread(() -> {       
        for (int i = 0; i < 5000; i++) {         
            synchronized (room) {            
            	counter--;          
            }    
        } 
    }, "t2");
 
    t1.start();    
    t2.start(); 
    t1.join();   
    t2.join();    
    log.debug("{}",counter); 
}

可以做这样的类比:

2.2 synchronized 加在方法上
  1. 加在成员方法上
public class Test {
	//在方法上加上synchronized关键字
	public synchronized void test() {
	
	}
	//等价于
	public void test() {
		synchronized(this) {
		
		}
	}
}
  1. 加在静态方法上
public class Test {
	//在静态方法上加上synchronized关键字
	public synchronized static void test() {
	
	}
	//等价于
	public void test() {
		synchronized(Test.class) {
		
		}
	}
}
3. 变量的线程安全分析

成员变量和静态变量是否线程安全?

局部变量是否安全?

局部变量线程安全性分析

public static void test1() {
	int i = 10;
	i++;
}

每个线程调用 test1() 方法时,局部变量 i 会在每个线程的栈帧内存中被创建多份,因此不存在共享,是线程安全的。

然而,局部变量的引用却有所不同,先看一个成员变量的例子

public class ThreadUnsafe {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;

    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            //{临界区,会产生竞态条件
            method2();
            method3();
            //}
        }
    }
    private void method2() {
        list.add("1");
    }
    private void method3() {
        list.remove(0);
    }

    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            },"Thread" + i).start();
        }
    }
}

运行之后,可能有一种情况,method2 还未 add,method3 便开始 remove 就会报错:

Exception in thread "Thread0" Exception in thread "Thread1" java.lang.ArrayIndexOutOfBoundsException: -1
	at java.util.ArrayList.remove(ArrayList.java:507)
	at com.czh.concurrent.ThreadUnsafe.method3(ThreadUnsafe.java:26)
	at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:18)
	at com.czh.concurrent.ThreadUnsafe.lambda$main
  • 无论哪个线程中的 method2,引用的都是同一个对象中的 list 成员变量
  • (ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748) java.lang.ArrayIndexOutOfBoundsException: -1 at java.util.ArrayList.add(ArrayList.java:465) at com.czh.concurrent.ThreadUnsafe.method2(ThreadUnsafe.java:23) at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:17) at com.czh.concurrent.ThreadUnsafe.lambda$mainpublic(ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748)

    分析:

    将 list 修改为局部变量

    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}private
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        
  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
  • 分析:

    方法修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会带来线程安全?

    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}public
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        class
    ThreadSafeSubClass
    
    extends ThreadUnsafe @Override public {
        void
        method3 ( ArrayList<String)new> listThread {
            ( ().remove->{
                list(0);})
            ;}}
        
  • String
  • Integer
  • 从这个例子可以看出 private 或 final 提供【安全】的意义所在,体会开闭原则中的【闭】。

    常见线程安全类

    这里的线程安全是指,多个线程调用它们同一个实例的某个方法时,是线程安全的,也可以理解为:

    new table Hashtable ( );newThread
    ( ().put->{
    	table("key","") ;value1})
    .start();newThread
    
    ( ().put->{
    	table("key","value2") ;})
    .start();
  • 它们的每个方法是原子的
  • 但是它们多个方法的组合不是原子的,可能会出现线程安全问题
  • new table Hashtable ( );//线程1,线程2if
    (
    . gettable("key")==null ) .put {
    	table("key",); value}
  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
  • String 有 replace,substring 等方法【可以】改变值,那么这些方法又是如何保证线程安全的呢?
  • 不可变类线程安全性

    4. Monitor 概念


    当线程执行到临界区代码时,如果使用了 synchronized,会先查询 synchronized 中所指定的对象 (obj) 是否绑定了 Monitor.

    注意:

    5. synchronized 原理进阶

    Java 对象头格式

    64 位虚拟机 Mark Word 结构如下:

    5.1 轻量级锁(用于优化 Monitor 这类的重量级锁)

    轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。

    5.2 锁膨胀 5.3 自旋优化

    重量级锁竞争时,还可以使用自旋来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态。

    5.4 偏向锁(用于优化轻量级锁重入)

    轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象) *** 作仍需要 CAS 替换 *** 作,这样会导致性能降低。

    所以引入了偏向锁对性能进行优化:在第一次 CAS 时会将线程的 ID 写入对象的 Mark Word中。此后发现这个线程 ID 就是自己的,就表示没有竞争,就不需要再次 CAS ,以后只要不发生竞争,这个对象就归该线程所有。

    偏向状态

    以下几种情况会使对象的偏向锁失效

    5.5 批量重偏向 5.6 批量撤销

    当撤销偏向锁的阈值超过 40 以后,就会将整个类的对象都改为不可偏向的

    6. wait/notify 6.1 原理

    wait 和 notify 都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用这几个方法,示例代码如下:

    Test final static {
        Object = new obj Object ( );publicstatic
        
        void main ( String[])new argsThread {
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}}
                    System
                .
                .printlnout("其他代码...");})
            .start();newThread
    
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}System
                    .
                    .printlnout("其他代码...");}}
                )
            .start();//主线程两秒后执行sleep
    
            (
            2);System.
            .printlnout("唤醒 obj 上其他线程");synchronized(
            ) .objnotify {
                obj();//唤醒obj上一个线程//obj.notifyAll();  //唤醒obj上所有等待线程  }
                }
            }
        
  • sleep 是 Thread 类的静态方法,wait 是 Object 的方法,Object 又是所有类的父类,所以所有类都有 wait 方法
  • sleep 在阻塞(睡眠)的时候不会释放锁,而 wait 在阻塞的时候会释放锁
  • 6.2 使用 wait/notify 的正确姿势

    wait 和 sleep 的区别:

    wait 与 sleep 的相同点:

    什么时候适合使用 wait

    使用 wait/notify 的注意点

    ) whileLOCK( {
    	.wait//不满足条件,一直等待,避免虚假唤醒) {
    		LOCK();}//满足条件后再运行
    	}
    	synchronized
    (
    ) //唤醒所有等待线程LOCK. {
    	notifyAll
    	LOCK();}
  • 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个 Guarded Object
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者-消费者)
  • 7. 模式之保护性暂停

    定义

    保护性暂停(Guarded Suspension)用在一个线程等待另一个线程的执行结果。
    要点:

    案例代码如下

    Test public static {
    	void main ( String[])String args= {
    		"hello thread!" hello ; Guarded=
    		new guarded Guarded ( );newThread
    		( ()System.->{
    			.printlnout("想要得到结果");synchronized(
    			) Systemguarded. {
    				.printlnout("结果是:"+.getResponseguarded());}System
    			.
    			.printlnout("得到结果");})
    		.start();newThread
    
    		( ()System.->{
    			.printlnout("设置结果");synchronized(
    			) .guardedsetResponse {
    				guarded();hello}}
    			)
    		.start();}}
    	class
    Guarded
    
    /**
    	 * 要返回的结果
    	 */ private {
    	Object
    	; //优雅地使用 wait/notify responsepublic
    	
        Object
    	getResponse ( )//如果返回结果为空就一直等待,避免虚假唤醒while {
    		(
    		==nullresponse ) synchronized( {
    			this )trythis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				}
    			return
    		;
    		} responsepublic
    	void
    
    	setResponse ( Object)this response. {
    		=;response synchronized response(
    		this )//唤醒休眠的线程this {
    			.
    			notifyAll();}}
    		@Override
    	public
    
    	String
    	toString ( )return"Guarded{" {
    		+ "response=" +
    				+ '}' response ;
    				}}
    	public
    Object
    

    带超时判断的暂停

    getResponse ( long)synchronized time( {
    	this )//获取开始时间long {
    		=
    		System currentTime . currentTimeMillis();//用于保存已经等待了的时间long
    		=
    		0 passedTime ; while(
    		==nullresponse ) //看经过的时间-开始时间是否超过了指定时间long {
    			=
    			- waitTime ; time if passedTime(
    			<=0waitTime ) break; {
    				}try
    			//等待剩余时间
    			this {
                      	.
    				wait();waitTime}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}//获取当前时间
    			=
    			System
    			passedTime . currentTimeMillis()-}}currentTime		
               return
    	;
    	} responsepublic
    final
    

    join 源码——使用保护性暂停模式

    synchronized void join ( long)throws millisInterruptedException
        long = {
            System base . currentTimeMillis();long=
            0 now ; if(
    
            < 0millis ) thrownew {
                IllegalArgumentException ( "timeout value is negative");}if
            (
    
            == 0millis ) while( {
                isAlive ())wait( {
                    0);}}
                else
            while ( {
                isAlive ())long= {
                    - delay ; millis if now(
                    <= 0delay ) break; {
                        }wait
                    (
                    );delay=System
                    now . currentTimeMillis()-; } base}
                }
            //暂停线程运行
        LockSupport
    
    8. park & unpark 8.1 基本使用

    park/unpark 都是 LockSupport 类中的的方法

    .
    ;//恢复线程运行parkLockSupport
    
    .
    unpark();threadpublicstatic
    
    void main ( String[])throws argsInterruptedException Thread = {
    		new thread Thread ( ()System.-> {
    			.printlnout("park");//暂停线程运行LockSupport
                .
    			park();System.
    			.printlnout("resume");},
    		"t1") ;.start
    		thread();Thread.
    
    		sleep(1000);System.
    		.printlnout("unpark");//恢复线程运行LockSupport
        	.
    		unpark();thread}
  • wait/notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park&unpark 不必
  • park&unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】
  • 8.2 特点

    与 Object 的 wait/notify 相比

    8.3 原理

    每个线程都有一个自己的 park 对象,并且该对象由 _counter, _cond,__mutex 组成

    情况 1:先调用 park,再调用 unpark

    先调用 park

    然后再调用 unpark

    情况 2:先调用 unpark,再调用 park

    先调用 unpark

    再调用 park

    9. 线程状态转换


    假设有线程 Thread t

    情况一:NEW --> RUNNABLE

    情况二:RUNNABLE <–> WAITING

    情况三:RUNNABLE <–> WAITING

    情况四: RUNNABLE <–> WAITING

    情况五: RUNNABLE <–> TIMED_WAITING
    t 线程用 synchronized(obj) 获取了对象锁后

    情况六:RUNNABLE <–> TIMED_WAITING

    情况七:RUNNABLE <–> TIMED_WAITING

    情况八:RUNNABLE <–> TIMED_WAITING

    情况九:RUNNABLE <–> BLOCKED

    情况十: RUNNABLE <–> TERMINATED

    10. 多把锁

    将锁的粒度细分

    11. 活跃性

    定义:因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性。

    11.1 死锁

    有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。

    t1 线程获得 A 对象锁,接下来想获取 B 对象的锁, t2 线程获得 B 对象锁,接下来想获取 A 对象 的锁, 例:

    void main ( String[])final argsObject {
    
    	A = new Object ( );finalObject
    	B = new Object ( );newThread
    	
    	( ()synchronized(->{
    		A )tryThread {
    			. {
    				sleep(2000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			B )}} {
    			
    			}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		B )tryThread {
    			. {
    				sleep(1000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			A )}} {
    
    			}
    		)
    	.start();}
  • 互斥条件:在一段时间内,一种资源只能被一个进程所使用。
  • 请求和保持条件:进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源。
  • 发生死锁的四个必要条件

    定位死锁的方法:

    注意点:避免死锁要注意加锁顺序;另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 Linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程 id 来定位是哪个线程,最后再用 jstack 排查。

    哲学家就餐问题

    有 5 位哲学家,围坐在圆桌旁。他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
    如果筷子被身边的人拿着,自己就得等待。

    筷子类

    String ; {
        public nameChopstick
    
        ( String)this name. {
            =;name } name@Override
        public
    
        String
        toString ( )return"筷子{" {
            + +"}"name;}}
        class
    Philosopher
    

    哲学家类

    extends Thread Chopstick ; {
    
        Chopstick left;
        public rightPhilosopher
    
        ( String,Chopstick name, Chopstick left) super right( {
            );namethis.
            =;left this left.
            =;right } rightprivate
        void
    
        eat ( )System. {
            .printlnout("eating...");this.
            =;right } right@Override
        public
    
        void
        run ( )while( {
            true )//获得左手筷子synchronized {
                (
                ) //获得右手筷子leftsynchronized {
                    (
                    ) //吃饭righteat {
                        (
                        );}//放下右手筷子
                    }
                    //放下左手筷子
                }
                }
            }
        
  • 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。
  • 避免死锁的方法

    在线程使用锁对象时,顺序加锁即可避免死锁

    11.2 活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

    避免活锁的方法:在线程执行时,中途给予不同的间隔时间即可。

    死锁与活锁的区别

    11.3 饥饿

    某些线程因为优先级太低,导致一直无法获得资源的现象,在使用顺序加锁时,可能会出现饥饿现象。

    12. 可重入锁

    和 synchronized 相比具有的的特点

    基本语法

    ReentrantLock
    = new lock ReentrantLock ( );//加锁.
    lock
    lock();try//需要执行的代码
    } {
    	finally
    //释放锁. {
    	unlock
    	lock();}
  • 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
  • 可重入

    可打断

    void main ( String[])ReentrantLock args= {
    
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//加锁,可打断锁-> {
    		. {
    			lockInterruptibly
    			lock();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,返回,不再向下执行return
                   ;
    			}finally
    		//释放锁. {
    			unlock
    			lock();}}
    		)
    
    	;.lock
    
    	lock();try.
    	start {
    		t1();Thread.
    		sleep(1000);//打断.
    		interrupt
    		t1();}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 使用 lock.tryLock 方法会返回获取锁是否成功。如果成功则返回 true,反之则返回 false
  • 并且 tryLock 方法可以指定等待时间,参数为:tryLock(long timeout, TimeUnit unit),其中timeout 为最长等待时间,TimeUnit 为时间单位
  • 锁超时

    不设置等待时间,立刻失败

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()//未设置等待时间,一旦获取失败,直接返回falseif-> {
               (
    		!.tryLocklock())System. {
    			.printlnout("获取失败");//获取失败,不再向下执行,返回return
                   ;
    			}System
    		.
    		.printlnout("得到了锁");.unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	public
    static
    

    设置等待时间

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//判断获取锁是否成功,最多等待1秒-> {
    		if {
    			(
    			!.tryLocklock(1,TimeUnit. ))SECONDSSystem. {
    				.printlnout("获取失败");//获取失败,不再向下执行,直接返回return
    				;
    				}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,不再向下执行,直接返回return
    			;
    			}System
    		.
    		.printlnout("得到了锁");//释放锁.
    		unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();//打断等待.
    		interrupt
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的
  • //默认是不公平锁,需要在创建时指定为公平锁

    公平锁

    =
    new lock ReentrantLock ( true);
  • synchronized 中也有条件变量,就是 waitSet 等待队列 ,当条件不满足时进入waitSet 等待
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量,这就好比,synchronized 是那些不满足条件的线程都在一间休息室等消息,而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
  • 条件变量

    使用要点:

    = false judge ; publicstatic
    void main ( String[])throws argsInterruptedException ReentrantLock = {
    	new lock ReentrantLock ( );//获得条件变量Condition
    	=
    	. condition newCondition lock();newThread
    	( ().lock->{
    		lock();trywhile
    		({
    			!)Systemjudge. {
    				.printlnout("不满足条件,等待...");//等待.
    				await
    				condition();}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		System . {
    			.printlnout("执行完毕!");.unlock
    			lock();}}
    		)
    	.start();newThread
    
    	( ().lock->{
    		lock();tryThread
    		. {
    			sleep(1);=true
    			judge ; //释放.
    			signal
    			condition();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		. unlock {
    			lock();}}
    		)
    
    	.start();}static
    final
    
    13. 同步模式之顺序控制
    Object = new LOCK Object ( );//判断先执行的内容是否执行完毕static
    Boolean
    = false judge ; publicstatic
    void main ( String[])new argsThread {
    	( ()synchronized(->{
    		) whileLOCK( {
    			! )tryjudge. {
    				wait {
    					LOCK();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout("2");}}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		) SystemLOCK. {
    			.printlnout("1");=true
    			judge ; //执行完毕,唤醒所有等待线程.
                   notifyAll
    			LOCK();}}
    		)
    	.start();}public
    class
    

    交替输出(wait/notify 版本)

    Test static Symbol {
    	= new symbol Symbol ( );publicstatic
    	void main ( String[])new argsThread {
    		( ().run->{
    			symbol("a",1, 2) ;})
    		.start();newThread
    
    		( ().run->{
    			symbol("b",2, 3) ;})
    
    		.start();.run
    		symbol("c",3, 1) ;newThread
    		( ()})->{
    
    		.start();}}
    	class
    Symbol
    
    public synchronized {
    	void run ( String,int str, int flag) for nextFlag( {
    		int=0 i;<; i++loopNumber) iwhile( {
    			!=thisflag . )tryflagthis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout();str//设置下一个运行的线程标记this
    			.
    			=;flag //唤醒所有线程 nextFlagthis
    			.
    			notifyAll();}}
    		private
    	int
    
    	/**
    	 * 线程的执行标记, 1->a 2->b 3->c
    	 */
    	= 1 flag ; privateint
    	= 5 loopNumber ; publicint
    
    	getFlag ( )return; {
    		} flagpublic
    	void
    
    	setFlag ( int)this flag. {
    		=;flag } flagpublic
    	int
    
    	getLoopNumber ( )return; {
    		} loopNumberpublic
    	void
    
    	setLoopNumber ( int)this loopNumber. {
    		=;loopNumber } loopNumber}
    	public
    class
    
    14. ThreadLocal

    ThreadLocal 是 JDK 包提供的,它提供线程本地变量,也就是如果创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程 *** 作这个变量时,实际 *** 作的是自己本地内存里面的变量,从而避免了线程安全问题。

    使用

    ThreadLocalTest public static {
       void main ( String[])// 创建ThreadLocal变量 argsThreadLocal {
          <
          String=new> stringThreadLocal ThreadLocal < ()>;ThreadLocal<
          User=new> userThreadLocal ThreadLocal < ()>;// 创建两个线程,分别使用上面的两个ThreadLocal变量Thread
    
          =
          new thread1 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread1 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread1 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Cristiano",37) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());// 移除.
              
              remove
    		 userThreadLocal();System.
    		 .printlnout(.getuserThreadLocal());})
          ;Thread=
    
          new thread2 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread2 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread2 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Lionel",34) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());})
          ;// 启动线程.
    
          start
          thread1();.start
          thread2();}}
       class
    User
    
    String ; {
       int name;
       public ageUser
    
       ( String,int name) this age. {
          =;name this name.
          =;age } age@Override
       public
    
       String
       toString ( )return"User{" {
          + "name='" +
                + '\'' name + ", age=" +
                + '}' age ;
                }}
       thread1 stringThreadLocal second
    thread2 stringThreadLocal second
    User{name='Cristiano', age=37}
    User{name='Lionel', age=34}
    null
    
    
  • 每个线程中的 ThreadLocal 变量是每个线程私有的,而不是共享的
  • ThreadLocal 其实就相当于其泛型类型的一个变量,只不过是每个线程私有的,stringThreadLocal被赋值了两次,保存的是最后一次赋值的结果
  • 从运行结果可以看出

    原理

    Thread implements Runnable . . {
    	..=
        ThreadLocalnullThreadLocalMap threadLocals ; .=
    
        ThreadLocalnullThreadLocalMap inheritableThreadLocals ; ..
        .}static
    class
    
    ThreadLocalMap static class {
       Entry extends WeakReference < ThreadLocal<?/** The value associated with this ThreadLocal. */Object>> {
           ;
           Entry value(
    
           ThreadLocal<?,Object> k) super v( {
               );k=;
               value } v}
           }
       public
    void
    

    Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而 ThreadLocalMap 是一个定制化的 Hashmap。在默认情况下,每个线程中的这两个变量都为 null.

    ThreadLocal 中的方法

    set ( T)//获取当前线程 valueThread {
    	=
        Thread t . currentThread();//获得ThreadLocalMap对象, 返回Thread类中的threadLocalsThreadLocalMap
        =
        getMap map ( );tif(
        != nullmap ) //ThreadLocal自生的引用作为key,传入的值作为value.
        	set
            map(this,); valueelsecreateMap
        (
            ,)t; value}void
    createMap
    ( Thread,T t) // 创建的同时设置想放入的值 firstValue// threadLocal自生的引用作为key,传入的值作为value {
        .
        =
        tnewthreadLocals ThreadLocalMap ( this,); firstValue}public
    T
    
    get ( )Thread= {
        Thread t . currentThread();ThreadLocalMap=
        getMap map ( );tif(
        != nullmap ) .= {
            ThreadLocalMap.Entry e getEntry map(this);if(
            != nulle ) @SuppressWarnings( {
                "unchecked")T=
                ( result T ).;ereturnvalue;
                } result}
            return
        setInitialValue
        ( );}private
    T
    setInitialValue ( )T= {
         initialValue value ( );Thread=
         Thread t . currentThread();ThreadLocalMap=
         getMap map ( );tif(
         != nullmap ) .set
             map(this,); valueelsecreateMap
         (
             ,)t; valuereturn;
         } valuepublic
    void
    
    remove ( )ThreadLocalMap= {
        getMap m ( Thread.currentThread());if(
         != nullm ) .remove
             m(this);}
  • 在每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中
  • 只有当前线程第一次调用 ThreadLocal 的 set 或者 get 方法时才会创建 threadLocals(inheritableThreadLocals 也是一样)。其实每个线程的本地变量不是存放在 ThreadLocal 实例里面,而是存放在调用线程的 threadLocals 变量里面
  • 从 ThreadLocal 的源码可以看出,无论是 set、get、还是 remove,都是相对于当前线程 *** 作

    Thread t . currentThread();publicclass
    

    因此 ThreadLocal 无法从父线程传向子线程,所以 InheritableThreadLocal 出现了,它能够让父线程中 ThreadLocal 的值传给子线程。

    也就是从 main 所在的线程,传给 thread1 或 thread2

    Test public static {
       void main ( String[])ThreadLocal args< {
          String=new> stringThreadLocal ThreadLocal < ()>;InheritableThreadLocal<
          String=new> stringInheritable InheritableThreadLocal < ()>;// 主线程赋对上面两个变量进行赋值.
    
          set
          stringThreadLocal("this is threadLocal");.set
          stringInheritable("this is inheritableThreadLocal");// 创建线程Thread
    
          =
          new thread1 Thread ( ()// 获得ThreadLocal中存放的值System->{
             .
             .printlnout(.getstringThreadLocal());// 获得InheritableThreadLocal存放的值System
    
             .
             .printlnout(.getstringInheritable());})
          ;.start
    
          thread1();}}
       null
    this is inheritableThreadLocal
    
    public
    

    运行结果

    class

    InheritableThreadLocal 的值成功从主线程传入了子线程,而 ThreadLocal 没有。

    原理

    InheritableThreadLocal < TextendsThreadLocal> < T// 传入父线程中的一个值,然后直接返回protected> {
        T
        childValue ( T)return parentValue; {
            } parentValue// 返回传入线程的inheritableThreadLocals
        // Thread中有一个inheritableThreadLocals变量
    
      	// ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ThreadLocalMap
        getMap
        ( Thread)return t. {
           ; t}inheritableThreadLocals// 创建一个inheritableThreadLocals
        void
    
     	createMap
        ( Thread,T t) . firstValue= {
            tnewinheritableThreadLocals ThreadLocalMap ( this,); firstValue}}
        
  • InheritableThreadLocal 继承了 ThreadLocal,并重写了三个方法。InheritableThreadLocal 重写了createMap 方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals 变量的实例而不再是 threadLocals。当调用 getMap 方法获取当前线程内部的 map 变量时,获取的是 inheritableThreadLocals 而不再是 threadLocals
  • 当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面
  • )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    Error[8]: Undefined offset: 3082, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
    File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

    Java 并发编程》共享模型之管程

    1. 共享带来的问题 1.1 临界区

    例如,下面代码中的临界区

    static int counter = 0
    static void increment() {
    	// 临界区
    	counter++;
    }
    static void decrement() {
    	// 临界区
    	counter--;
    }
    
    1.2 竞态条件

    多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

    2. synchronized 解决方案

    为了避免临界区的竞态条件发生,有多种手段可以达到目的。

    这里使用阻塞式的解决方案:synchronized 来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻最多只有一个线程能持有【对象锁】,其他线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。

    值得注意的是,虽然 Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

    2.1 synchronized 语法
    synchronized(对象) {   //线程1,线程2(blocked)
    	临界区
    }
    

    案例代码

    static int counter = 0; 
    //创建一个公共对象,作为对象锁的对象
    static final Object room = new Object();
     
    public static void main(String[] args) throws InterruptedException {    
    	Thread t1 = new Thread(() -> {        
        for (int i = 0; i < 5000; i++) {            
            synchronized (room) {     
            	counter++;            
           	 }       
     	   }    
        }, "t1");
     
        Thread t2 = new Thread(() -> {       
            for (int i = 0; i < 5000; i++) {         
                synchronized (room) {            
                	counter--;          
                }    
            } 
        }, "t2");
     
        t1.start();    
        t2.start(); 
        t1.join();   
        t2.join();    
        log.debug("{}",counter); 
    }
    

    可以做这样的类比:

    2.2 synchronized 加在方法上
    1. 加在成员方法上
    public class Test {
    	//在方法上加上synchronized关键字
    	public synchronized void test() {
    	
    	}
    	//等价于
    	public void test() {
    		synchronized(this) {
    		
    		}
    	}
    }
    
    1. 加在静态方法上
    public class Test {
    	//在静态方法上加上synchronized关键字
    	public synchronized static void test() {
    	
    	}
    	//等价于
    	public void test() {
    		synchronized(Test.class) {
    		
    		}
    	}
    }
    
    3. 变量的线程安全分析

    成员变量和静态变量是否线程安全?

    局部变量是否安全?

    局部变量线程安全性分析

    public static void test1() {
    	int i = 10;
    	i++;
    }
    

    每个线程调用 test1() 方法时,局部变量 i 会在每个线程的栈帧内存中被创建多份,因此不存在共享,是线程安全的。

    然而,局部变量的引用却有所不同,先看一个成员变量的例子

    public class ThreadUnsafe {
        static final int THREAD_NUMBER = 2;
        static final int LOOP_NUMBER = 200;
    
        ArrayList<String> list = new ArrayList<>();
        public void method1(int loopNumber) {
            for (int i = 0; i < loopNumber; i++) {
                //{临界区,会产生竞态条件
                method2();
                method3();
                //}
            }
        }
        private void method2() {
            list.add("1");
        }
        private void method3() {
            list.remove(0);
        }
    
        public static void main(String[] args) {
            ThreadUnsafe test = new ThreadUnsafe();
            for (int i = 0; i < THREAD_NUMBER; i++) {
                new Thread(() -> {
                    test.method1(LOOP_NUMBER);
                },"Thread" + i).start();
            }
        }
    }
    

    运行之后,可能有一种情况,method2 还未 add,method3 便开始 remove 就会报错:

    Exception in thread "Thread0" Exception in thread "Thread1" java.lang.ArrayIndexOutOfBoundsException: -1
    	at java.util.ArrayList.remove(ArrayList.java:507)
    	at com.czh.concurrent.ThreadUnsafe.method3(ThreadUnsafe.java:26)
    	at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:18)
    	at com.czh.concurrent.ThreadUnsafe.lambda$main
  • 无论哪个线程中的 method2,引用的都是同一个对象中的 list 成员变量
  • (ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748) java.lang.ArrayIndexOutOfBoundsException: -1 at java.util.ArrayList.add(ArrayList.java:465) at com.czh.concurrent.ThreadUnsafe.method2(ThreadUnsafe.java:23) at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:17) at com.czh.concurrent.ThreadUnsafe.lambda$mainpublic(ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748)

    分析:

    将 list 修改为局部变量

    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}private
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        
  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
  • 分析:

    方法修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会带来线程安全?

    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}public
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        class
    ThreadSafeSubClass
    
    extends ThreadUnsafe @Override public {
        void
        method3 ( ArrayList<String)new> listThread {
            ( ().remove->{
                list(0);})
            ;}}
        
  • String
  • Integer
  • 从这个例子可以看出 private 或 final 提供【安全】的意义所在,体会开闭原则中的【闭】。

    常见线程安全类

    这里的线程安全是指,多个线程调用它们同一个实例的某个方法时,是线程安全的,也可以理解为:

    new table Hashtable ( );newThread
    ( ().put->{
    	table("key","") ;value1})
    .start();newThread
    
    ( ().put->{
    	table("key","value2") ;})
    .start();
  • 它们的每个方法是原子的
  • 但是它们多个方法的组合不是原子的,可能会出现线程安全问题
  • new table Hashtable ( );//线程1,线程2if
    (
    . gettable("key")==null ) .put {
    	table("key",); value}
  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
  • String 有 replace,substring 等方法【可以】改变值,那么这些方法又是如何保证线程安全的呢?
  • 不可变类线程安全性

    4. Monitor 概念


    当线程执行到临界区代码时,如果使用了 synchronized,会先查询 synchronized 中所指定的对象 (obj) 是否绑定了 Monitor.

    注意:

    5. synchronized 原理进阶

    Java 对象头格式

    64 位虚拟机 Mark Word 结构如下:

    5.1 轻量级锁(用于优化 Monitor 这类的重量级锁)

    轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。

    5.2 锁膨胀 5.3 自旋优化

    重量级锁竞争时,还可以使用自旋来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态。

    5.4 偏向锁(用于优化轻量级锁重入)

    轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象) *** 作仍需要 CAS 替换 *** 作,这样会导致性能降低。

    所以引入了偏向锁对性能进行优化:在第一次 CAS 时会将线程的 ID 写入对象的 Mark Word中。此后发现这个线程 ID 就是自己的,就表示没有竞争,就不需要再次 CAS ,以后只要不发生竞争,这个对象就归该线程所有。

    偏向状态

    以下几种情况会使对象的偏向锁失效

    5.5 批量重偏向 5.6 批量撤销

    当撤销偏向锁的阈值超过 40 以后,就会将整个类的对象都改为不可偏向的

    6. wait/notify 6.1 原理

    wait 和 notify 都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用这几个方法,示例代码如下:

    Test final static {
        Object = new obj Object ( );publicstatic
        
        void main ( String[])new argsThread {
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}}
                    System
                .
                .printlnout("其他代码...");})
            .start();newThread
    
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}System
                    .
                    .printlnout("其他代码...");}}
                )
            .start();//主线程两秒后执行sleep
    
            (
            2);System.
            .printlnout("唤醒 obj 上其他线程");synchronized(
            ) .objnotify {
                obj();//唤醒obj上一个线程//obj.notifyAll();  //唤醒obj上所有等待线程  }
                }
            }
        
  • sleep 是 Thread 类的静态方法,wait 是 Object 的方法,Object 又是所有类的父类,所以所有类都有 wait 方法
  • sleep 在阻塞(睡眠)的时候不会释放锁,而 wait 在阻塞的时候会释放锁
  • 6.2 使用 wait/notify 的正确姿势

    wait 和 sleep 的区别:

    wait 与 sleep 的相同点:

    什么时候适合使用 wait

    使用 wait/notify 的注意点

    ) whileLOCK( {
    	.wait//不满足条件,一直等待,避免虚假唤醒) {
    		LOCK();}//满足条件后再运行
    	}
    	synchronized
    (
    ) //唤醒所有等待线程LOCK. {
    	notifyAll
    	LOCK();}
  • 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个 Guarded Object
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者-消费者)
  • 7. 模式之保护性暂停

    定义

    保护性暂停(Guarded Suspension)用在一个线程等待另一个线程的执行结果。
    要点:

    案例代码如下

    Test public static {
    	void main ( String[])String args= {
    		"hello thread!" hello ; Guarded=
    		new guarded Guarded ( );newThread
    		( ()System.->{
    			.printlnout("想要得到结果");synchronized(
    			) Systemguarded. {
    				.printlnout("结果是:"+.getResponseguarded());}System
    			.
    			.printlnout("得到结果");})
    		.start();newThread
    
    		( ()System.->{
    			.printlnout("设置结果");synchronized(
    			) .guardedsetResponse {
    				guarded();hello}}
    			)
    		.start();}}
    	class
    Guarded
    
    /**
    	 * 要返回的结果
    	 */ private {
    	Object
    	; //优雅地使用 wait/notify responsepublic
    	
        Object
    	getResponse ( )//如果返回结果为空就一直等待,避免虚假唤醒while {
    		(
    		==nullresponse ) synchronized( {
    			this )trythis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				}
    			return
    		;
    		} responsepublic
    	void
    
    	setResponse ( Object)this response. {
    		=;response synchronized response(
    		this )//唤醒休眠的线程this {
    			.
    			notifyAll();}}
    		@Override
    	public
    
    	String
    	toString ( )return"Guarded{" {
    		+ "response=" +
    				+ '}' response ;
    				}}
    	public
    Object
    

    带超时判断的暂停

    getResponse ( long)synchronized time( {
    	this )//获取开始时间long {
    		=
    		System currentTime . currentTimeMillis();//用于保存已经等待了的时间long
    		=
    		0 passedTime ; while(
    		==nullresponse ) //看经过的时间-开始时间是否超过了指定时间long {
    			=
    			- waitTime ; time if passedTime(
    			<=0waitTime ) break; {
    				}try
    			//等待剩余时间
    			this {
                      	.
    				wait();waitTime}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}//获取当前时间
    			=
    			System
    			passedTime . currentTimeMillis()-}}currentTime		
               return
    	;
    	} responsepublic
    final
    

    join 源码——使用保护性暂停模式

    synchronized void join ( long)throws millisInterruptedException
        long = {
            System base . currentTimeMillis();long=
            0 now ; if(
    
            < 0millis ) thrownew {
                IllegalArgumentException ( "timeout value is negative");}if
            (
    
            == 0millis ) while( {
                isAlive ())wait( {
                    0);}}
                else
            while ( {
                isAlive ())long= {
                    - delay ; millis if now(
                    <= 0delay ) break; {
                        }wait
                    (
                    );delay=System
                    now . currentTimeMillis()-; } base}
                }
            //暂停线程运行
        LockSupport
    
    8. park & unpark 8.1 基本使用

    park/unpark 都是 LockSupport 类中的的方法

    .
    ;//恢复线程运行parkLockSupport
    
    .
    unpark();threadpublicstatic
    
    void main ( String[])throws argsInterruptedException Thread = {
    		new thread Thread ( ()System.-> {
    			.printlnout("park");//暂停线程运行LockSupport
                .
    			park();System.
    			.printlnout("resume");},
    		"t1") ;.start
    		thread();Thread.
    
    		sleep(1000);System.
    		.printlnout("unpark");//恢复线程运行LockSupport
        	.
    		unpark();thread}
  • wait/notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park&unpark 不必
  • park&unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】
  • 8.2 特点

    与 Object 的 wait/notify 相比

    8.3 原理

    每个线程都有一个自己的 park 对象,并且该对象由 _counter, _cond,__mutex 组成

    情况 1:先调用 park,再调用 unpark

    先调用 park

    然后再调用 unpark

    情况 2:先调用 unpark,再调用 park

    先调用 unpark

    再调用 park

    9. 线程状态转换


    假设有线程 Thread t

    情况一:NEW --> RUNNABLE

    情况二:RUNNABLE <–> WAITING

    情况三:RUNNABLE <–> WAITING

    情况四: RUNNABLE <–> WAITING

    情况五: RUNNABLE <–> TIMED_WAITING
    t 线程用 synchronized(obj) 获取了对象锁后

    情况六:RUNNABLE <–> TIMED_WAITING

    情况七:RUNNABLE <–> TIMED_WAITING

    情况八:RUNNABLE <–> TIMED_WAITING

    情况九:RUNNABLE <–> BLOCKED

    情况十: RUNNABLE <–> TERMINATED

    10. 多把锁

    将锁的粒度细分

    11. 活跃性

    定义:因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性。

    11.1 死锁

    有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。

    t1 线程获得 A 对象锁,接下来想获取 B 对象的锁, t2 线程获得 B 对象锁,接下来想获取 A 对象 的锁, 例:

    void main ( String[])final argsObject {
    
    	A = new Object ( );finalObject
    	B = new Object ( );newThread
    	
    	( ()synchronized(->{
    		A )tryThread {
    			. {
    				sleep(2000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			B )}} {
    			
    			}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		B )tryThread {
    			. {
    				sleep(1000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			A )}} {
    
    			}
    		)
    	.start();}
  • 互斥条件:在一段时间内,一种资源只能被一个进程所使用。
  • 请求和保持条件:进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源。
  • 发生死锁的四个必要条件

    定位死锁的方法:

    注意点:避免死锁要注意加锁顺序;另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 Linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程 id 来定位是哪个线程,最后再用 jstack 排查。

    哲学家就餐问题

    有 5 位哲学家,围坐在圆桌旁。他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
    如果筷子被身边的人拿着,自己就得等待。

    筷子类

    String ; {
        public nameChopstick
    
        ( String)this name. {
            =;name } name@Override
        public
    
        String
        toString ( )return"筷子{" {
            + +"}"name;}}
        class
    Philosopher
    

    哲学家类

    extends Thread Chopstick ; {
    
        Chopstick left;
        public rightPhilosopher
    
        ( String,Chopstick name, Chopstick left) super right( {
            );namethis.
            =;left this left.
            =;right } rightprivate
        void
    
        eat ( )System. {
            .printlnout("eating...");this.
            =;right } right@Override
        public
    
        void
        run ( )while( {
            true )//获得左手筷子synchronized {
                (
                ) //获得右手筷子leftsynchronized {
                    (
                    ) //吃饭righteat {
                        (
                        );}//放下右手筷子
                    }
                    //放下左手筷子
                }
                }
            }
        
  • 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。
  • 避免死锁的方法

    在线程使用锁对象时,顺序加锁即可避免死锁

    11.2 活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

    避免活锁的方法:在线程执行时,中途给予不同的间隔时间即可。

    死锁与活锁的区别

    11.3 饥饿

    某些线程因为优先级太低,导致一直无法获得资源的现象,在使用顺序加锁时,可能会出现饥饿现象。

    12. 可重入锁

    和 synchronized 相比具有的的特点

    基本语法

    ReentrantLock
    = new lock ReentrantLock ( );//加锁.
    lock
    lock();try//需要执行的代码
    } {
    	finally
    //释放锁. {
    	unlock
    	lock();}
  • 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
  • 可重入

    可打断

    void main ( String[])ReentrantLock args= {
    
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//加锁,可打断锁-> {
    		. {
    			lockInterruptibly
    			lock();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,返回,不再向下执行return
                   ;
    			}finally
    		//释放锁. {
    			unlock
    			lock();}}
    		)
    
    	;.lock
    
    	lock();try.
    	start {
    		t1();Thread.
    		sleep(1000);//打断.
    		interrupt
    		t1();}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 使用 lock.tryLock 方法会返回获取锁是否成功。如果成功则返回 true,反之则返回 false
  • 并且 tryLock 方法可以指定等待时间,参数为:tryLock(long timeout, TimeUnit unit),其中timeout 为最长等待时间,TimeUnit 为时间单位
  • 锁超时

    不设置等待时间,立刻失败

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()//未设置等待时间,一旦获取失败,直接返回falseif-> {
               (
    		!.tryLocklock())System. {
    			.printlnout("获取失败");//获取失败,不再向下执行,返回return
                   ;
    			}System
    		.
    		.printlnout("得到了锁");.unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	public
    static
    

    设置等待时间

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//判断获取锁是否成功,最多等待1秒-> {
    		if {
    			(
    			!.tryLocklock(1,TimeUnit. ))SECONDSSystem. {
    				.printlnout("获取失败");//获取失败,不再向下执行,直接返回return
    				;
    				}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,不再向下执行,直接返回return
    			;
    			}System
    		.
    		.printlnout("得到了锁");//释放锁.
    		unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();//打断等待.
    		interrupt
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的
  • //默认是不公平锁,需要在创建时指定为公平锁

    公平锁

    =
    new lock ReentrantLock ( true);
  • synchronized 中也有条件变量,就是 waitSet 等待队列 ,当条件不满足时进入waitSet 等待
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量,这就好比,synchronized 是那些不满足条件的线程都在一间休息室等消息,而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
  • 条件变量

    使用要点:

    = false judge ; publicstatic
    void main ( String[])throws argsInterruptedException ReentrantLock = {
    	new lock ReentrantLock ( );//获得条件变量Condition
    	=
    	. condition newCondition lock();newThread
    	( ().lock->{
    		lock();trywhile
    		({
    			!)Systemjudge. {
    				.printlnout("不满足条件,等待...");//等待.
    				await
    				condition();}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		System . {
    			.printlnout("执行完毕!");.unlock
    			lock();}}
    		)
    	.start();newThread
    
    	( ().lock->{
    		lock();tryThread
    		. {
    			sleep(1);=true
    			judge ; //释放.
    			signal
    			condition();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		. unlock {
    			lock();}}
    		)
    
    	.start();}static
    final
    
    13. 同步模式之顺序控制
    Object = new LOCK Object ( );//判断先执行的内容是否执行完毕static
    Boolean
    = false judge ; publicstatic
    void main ( String[])new argsThread {
    	( ()synchronized(->{
    		) whileLOCK( {
    			! )tryjudge. {
    				wait {
    					LOCK();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout("2");}}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		) SystemLOCK. {
    			.printlnout("1");=true
    			judge ; //执行完毕,唤醒所有等待线程.
                   notifyAll
    			LOCK();}}
    		)
    	.start();}public
    class
    

    交替输出(wait/notify 版本)

    Test static Symbol {
    	= new symbol Symbol ( );publicstatic
    	void main ( String[])new argsThread {
    		( ().run->{
    			symbol("a",1, 2) ;})
    		.start();newThread
    
    		( ().run->{
    			symbol("b",2, 3) ;})
    
    		.start();.run
    		symbol("c",3, 1) ;newThread
    		( ()})->{
    
    		.start();}}
    	class
    Symbol
    
    public synchronized {
    	void run ( String,int str, int flag) for nextFlag( {
    		int=0 i;<; i++loopNumber) iwhile( {
    			!=thisflag . )tryflagthis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout();str//设置下一个运行的线程标记this
    			.
    			=;flag //唤醒所有线程 nextFlagthis
    			.
    			notifyAll();}}
    		private
    	int
    
    	/**
    	 * 线程的执行标记, 1->a 2->b 3->c
    	 */
    	= 1 flag ; privateint
    	= 5 loopNumber ; publicint
    
    	getFlag ( )return; {
    		} flagpublic
    	void
    
    	setFlag ( int)this flag. {
    		=;flag } flagpublic
    	int
    
    	getLoopNumber ( )return; {
    		} loopNumberpublic
    	void
    
    	setLoopNumber ( int)this loopNumber. {
    		=;loopNumber } loopNumber}
    	public
    class
    
    14. ThreadLocal

    ThreadLocal 是 JDK 包提供的,它提供线程本地变量,也就是如果创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程 *** 作这个变量时,实际 *** 作的是自己本地内存里面的变量,从而避免了线程安全问题。

    使用

    ThreadLocalTest public static {
       void main ( String[])// 创建ThreadLocal变量 argsThreadLocal {
          <
          String=new> stringThreadLocal ThreadLocal < ()>;ThreadLocal<
          User=new> userThreadLocal ThreadLocal < ()>;// 创建两个线程,分别使用上面的两个ThreadLocal变量Thread
    
          =
          new thread1 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread1 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread1 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Cristiano",37) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());// 移除.
              
              remove
    		 userThreadLocal();System.
    		 .printlnout(.getuserThreadLocal());})
          ;Thread=
    
          new thread2 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread2 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread2 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Lionel",34) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());})
          ;// 启动线程.
    
          start
          thread1();.start
          thread2();}}
       class
    User
    
    String ; {
       int name;
       public ageUser
    
       ( String,int name) this age. {
          =;name this name.
          =;age } age@Override
       public
    
       String
       toString ( )return"User{" {
          + "name='" +
                + '\'' name + ", age=" +
                + '}' age ;
                }}
       thread1 stringThreadLocal second
    thread2 stringThreadLocal second
    User{name='Cristiano', age=37}
    User{name='Lionel', age=34}
    null
    
    
  • 每个线程中的 ThreadLocal 变量是每个线程私有的,而不是共享的
  • ThreadLocal 其实就相当于其泛型类型的一个变量,只不过是每个线程私有的,stringThreadLocal被赋值了两次,保存的是最后一次赋值的结果
  • 从运行结果可以看出

    原理

    Thread implements Runnable . . {
    	..=
        ThreadLocalnullThreadLocalMap threadLocals ; .=
    
        ThreadLocalnullThreadLocalMap inheritableThreadLocals ; ..
        .}static
    class
    
    ThreadLocalMap static class {
       Entry extends WeakReference < ThreadLocal<?/** The value associated with this ThreadLocal. */Object>> {
           ;
           Entry value(
    
           ThreadLocal<?,Object> k) super v( {
               );k=;
               value } v}
           }
       public
    void
    

    Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而 ThreadLocalMap 是一个定制化的 Hashmap。在默认情况下,每个线程中的这两个变量都为 null.

    ThreadLocal 中的方法

    set ( T)//获取当前线程 valueThread {
    	=
        Thread t . currentThread();//获得ThreadLocalMap对象, 返回Thread类中的threadLocalsThreadLocalMap
        =
        getMap map ( );tif(
        != nullmap ) //ThreadLocal自生的引用作为key,传入的值作为value.
        	set
            map(this,); valueelsecreateMap
        (
            ,)t; value}void
    createMap
    ( Thread,T t) // 创建的同时设置想放入的值 firstValue// threadLocal自生的引用作为key,传入的值作为value {
        .
        =
        tnewthreadLocals ThreadLocalMap ( this,); firstValue}public
    T
    
    get ( )Thread= {
        Thread t . currentThread();ThreadLocalMap=
        getMap map ( );tif(
        != nullmap ) .= {
            ThreadLocalMap.Entry e getEntry map(this);if(
            != nulle ) @SuppressWarnings( {
                "unchecked")T=
                ( result T ).;ereturnvalue;
                } result}
            return
        setInitialValue
        ( );}private
    T
    setInitialValue ( )T= {
         initialValue value ( );Thread=
         Thread t . currentThread();ThreadLocalMap=
         getMap map ( );tif(
         != nullmap ) .set
             map(this,); valueelsecreateMap
         (
             ,)t; valuereturn;
         } valuepublic
    void
    
    remove ( )ThreadLocalMap= {
        getMap m ( Thread.currentThread());if(
         != nullm ) .remove
             m(this);}
  • 在每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中
  • 只有当前线程第一次调用 ThreadLocal 的 set 或者 get 方法时才会创建 threadLocals(inheritableThreadLocals 也是一样)。其实每个线程的本地变量不是存放在 ThreadLocal 实例里面,而是存放在调用线程的 threadLocals 变量里面
  • 从 ThreadLocal 的源码可以看出,无论是 set、get、还是 remove,都是相对于当前线程 *** 作

    Thread t . currentThread();publicclass
    

    因此 ThreadLocal 无法从父线程传向子线程,所以 InheritableThreadLocal 出现了,它能够让父线程中 ThreadLocal 的值传给子线程。

    也就是从 main 所在的线程,传给 thread1 或 thread2

    Test public static {
       void main ( String[])ThreadLocal args< {
          String=new> stringThreadLocal ThreadLocal < ()>;InheritableThreadLocal<
          String=new> stringInheritable InheritableThreadLocal < ()>;// 主线程赋对上面两个变量进行赋值.
    
          set
          stringThreadLocal("this is threadLocal");.set
          stringInheritable("this is inheritableThreadLocal");// 创建线程Thread
    
          =
          new thread1 Thread ( ()// 获得ThreadLocal中存放的值System->{
             .
             .printlnout(.getstringThreadLocal());// 获得InheritableThreadLocal存放的值System
    
             .
             .printlnout(.getstringInheritable());})
          ;.start
    
          thread1();}}
       null
    this is inheritableThreadLocal
    
    public
    

    运行结果

    class

    InheritableThreadLocal 的值成功从主线程传入了子线程,而 ThreadLocal 没有。

    原理

    InheritableThreadLocal < TextendsThreadLocal> < T// 传入父线程中的一个值,然后直接返回protected> {
        T
        childValue ( T)return parentValue; {
            } parentValue// 返回传入线程的inheritableThreadLocals
        // Thread中有一个inheritableThreadLocals变量
    
      	// ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ThreadLocalMap
        getMap
        ( Thread)return t. {
           ; t}inheritableThreadLocals// 创建一个inheritableThreadLocals
        void
    
     	createMap
        ( Thread,T t) . firstValue= {
            tnewinheritableThreadLocals ThreadLocalMap ( this,); firstValue}}
        
  • InheritableThreadLocal 继承了 ThreadLocal,并重写了三个方法。InheritableThreadLocal 重写了createMap 方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals 变量的实例而不再是 threadLocals。当调用 getMap 方法获取当前线程内部的 map 变量时,获取的是 inheritableThreadLocals 而不再是 threadLocals
  • 当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面
  • )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    《Java 并发编程》共享模型之管程_python_内存溢出

    《Java 并发编程》共享模型之管程

    《Java 并发编程》共享模型之管程,第1张

    Java 并发编程》共享模型之管程
    • 1. 共享带来的问题
      • 1.1 临界区
      • 1.2 竞态条件
    • 2. synchronized 解决方案
      • 2.1 synchronized 语法
      • 2.2 synchronized 加在方法上
    • 3. 变量的线程安全分析
    • 4. Monitor 概念
    • 5. synchronized 原理进阶
      • 5.1 轻量级锁(用于优化 Monitor 这类的重量级锁)
      • 5.2 锁膨胀
      • 5.3 自旋优化
      • 5.4 偏向锁(用于优化轻量级锁重入)
      • 5.5 批量重偏向
      • 5.6 批量撤销
    • 6. wait/notify
      • 6.1 原理
      • 6.2 使用 wait/notify 的正确姿势
    • 7. 模式之保护性暂停
    • 8. park & unpark
      • 8.1 基本使用
      • 8.2 特点
      • 8.3 原理
    • 9. 线程状态转换
    • 10. 多把锁
    • 11. 活跃性
      • 11.1 死锁
      • 11.2 活锁
      • 11.3 饥饿
    • 12. 可重入锁
    • 13. 同步模式之顺序控制
    • 14. ThreadLocal

    1. 共享带来的问题 1.1 临界区
    • 一个程序运行多个线程本身没有问题
    • 问题出在多个线程访问共享资源
      (1)多个线程读共享资源其实也没有问题
      (2)在多个线程对共享资源读写 *** 作时发生指令交错,就会出现问题
    • 一段代码内如果存在对共享资源的多线程读写 *** 作,称这块代码块为临界区

    例如,下面代码中的临界区

    static int counter = 0
    static void increment() {
    	// 临界区
    	counter++;
    }
    static void decrement() {
    	// 临界区
    	counter--;
    }
    
    1.2 竞态条件

    多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

    2. synchronized 解决方案

    为了避免临界区的竞态条件发生,有多种手段可以达到目的。

    • 阻塞式的解决方案:synchronized、lock
    • 非阻塞式的解决方案:原子变量

    这里使用阻塞式的解决方案:synchronized 来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻最多只有一个线程能持有【对象锁】,其他线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。

    值得注意的是,虽然 Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

    • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
    • 同步是由于线程执行的先后、顺序不同,需要一个线程等待其他线程运行到某个点
    2.1 synchronized 语法
    synchronized(对象) {   //线程1,线程2(blocked)
    	临界区
    }
    

    案例代码

    static int counter = 0; 
    //创建一个公共对象,作为对象锁的对象
    static final Object room = new Object();
     
    public static void main(String[] args) throws InterruptedException {    
    	Thread t1 = new Thread(() -> {        
        for (int i = 0; i < 5000; i++) {            
            synchronized (room) {     
            	counter++;            
           	 }       
     	   }    
        }, "t1");
     
        Thread t2 = new Thread(() -> {       
            for (int i = 0; i < 5000; i++) {         
                synchronized (room) {            
                	counter--;          
                }    
            } 
        }, "t2");
     
        t1.start();    
        t2.start(); 
        t1.join();   
        t2.join();    
        log.debug("{}",counter); 
    }
    

    可以做这样的类比:

    • synchronized 中的对象,可以想象为一个房间,有唯一入口房间只能一次进入一个人进行计算,线程 t1 和 t2 想象成两个人
    • 当线程 t1 执行到 synchronized 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行 counter++
    • 这时如果 t2 也运行到了 synchronized 时,它发现门锁住了,只能在门外等待,发生了线程上下文切换,阻塞住了
    • 这中间即使 t1 的 CPU 时间片不幸用完,被踢出了门外,这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片才能开门进入
    • 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 counter-- 代码
    2.2 synchronized 加在方法上
    1. 加在成员方法上
    public class Test {
    	//在方法上加上synchronized关键字
    	public synchronized void test() {
    	
    	}
    	//等价于
    	public void test() {
    		synchronized(this) {
    		
    		}
    	}
    }
    
    1. 加在静态方法上
    public class Test {
    	//在静态方法上加上synchronized关键字
    	public synchronized static void test() {
    	
    	}
    	//等价于
    	public void test() {
    		synchronized(Test.class) {
    		
    		}
    	}
    }
    
    3. 变量的线程安全分析

    成员变量和静态变量是否线程安全?

    • 如果它们没有共享,则线程安全
    • 如果它们被共享了,根据它们的状态是否能够改变,又分为两种情况
      (1)如果只有读 *** 作,则线程安全
      (2)如果有读写 *** 作,则这段代码是临界区,需要考虑线程安全

    局部变量是否安全?

    • 局部变量是线程安全的
    • 但局部变量引用的对象则未必线程安全
      (1) 如果该对象没有逃离方法的作用访问,它是线程安全的
      (2) 如果该对象逃离方法的作用范围,需要考虑线程安全

    局部变量线程安全性分析

    public static void test1() {
    	int i = 10;
    	i++;
    }
    

    每个线程调用 test1() 方法时,局部变量 i 会在每个线程的栈帧内存中被创建多份,因此不存在共享,是线程安全的。

    然而,局部变量的引用却有所不同,先看一个成员变量的例子

    public class ThreadUnsafe {
        static final int THREAD_NUMBER = 2;
        static final int LOOP_NUMBER = 200;
    
        ArrayList<String> list = new ArrayList<>();
        public void method1(int loopNumber) {
            for (int i = 0; i < loopNumber; i++) {
                //{临界区,会产生竞态条件
                method2();
                method3();
                //}
            }
        }
        private void method2() {
            list.add("1");
        }
        private void method3() {
            list.remove(0);
        }
    
        public static void main(String[] args) {
            ThreadUnsafe test = new ThreadUnsafe();
            for (int i = 0; i < THREAD_NUMBER; i++) {
                new Thread(() -> {
                    test.method1(LOOP_NUMBER);
                },"Thread" + i).start();
            }
        }
    }
    

    运行之后,可能有一种情况,method2 还未 add,method3 便开始 remove 就会报错:

    Exception in thread "Thread0" Exception in thread "Thread1" java.lang.ArrayIndexOutOfBoundsException: -1
    	at java.util.ArrayList.remove(ArrayList.java:507)
    	at com.czh.concurrent.ThreadUnsafe.method3(ThreadUnsafe.java:26)
    	at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:18)
    	at com.czh.concurrent.ThreadUnsafe.lambda$main
  • 无论哪个线程中的 method2,引用的都是同一个对象中的 list 成员变量
  • (ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748) java.lang.ArrayIndexOutOfBoundsException: -1 at java.util.ArrayList.add(ArrayList.java:465) at com.czh.concurrent.ThreadUnsafe.method2(ThreadUnsafe.java:23) at com.czh.concurrent.ThreadUnsafe.method1(ThreadUnsafe.java:17) at com.czh.concurrent.ThreadUnsafe.lambda$mainpublic(ThreadUnsafe.java:33) at java.lang.Thread.run(Thread.java:748)

    分析:

      class
    • method3 与 method2 分析相同

    将 list 修改为局部变量

    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}private
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        
  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
  • 分析:

    • 情况1:有其他线程调用 method2 和 method3
    • 情况2:在情况1的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法,即
    • method3 的参数分析与 method2 相同

    方法修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会带来线程安全?

      publicclass
    ThreadUnsafe static final {
    
        int = 2 THREAD_NUMBER ; staticfinal
        int = 200 LOOP_NUMBER ; publicfinal
    
    
        void method1 ( int)ArrayList loopNumber< {
            String=new> list ArrayList < ()>;for(
            int =0 i ; <; i ++ loopNumber) i//{临界区,会产生竞态条件method2 {
                (
                );listmethod3(
                );list//}}
                }
            private
        void
    
        method2 ( ArrayList<String).> listadd {
            list("1");}public
        void
    
        method3 ( ArrayList<String).> listremove {
            list(0);}public
        static
    
        void main ( String[])ThreadUnsafe args= {
            new test ThreadUnsafe ( );for(
            int =0 i ; <; i ++ THREAD_NUMBER) inewThread {
                ( ().method1 -> {
                    test();LOOP_NUMBER},
                "Thread"+) . istart();}}
            }
        class
    ThreadSafeSubClass
    
    extends ThreadUnsafe @Override public {
        void
        method3 ( ArrayList<String)new> listThread {
            ( ().remove->{
                list(0);})
            ;}}
        
  • String
  • Integer
  • 从这个例子可以看出 private 或 final 提供【安全】的意义所在,体会开闭原则中的【闭】。

    常见线程安全类

    • StringBuffer
    • Random
    • Vector (List的线程安全实现类)
    • Hashtable(Hash的线程安全实现类)
    • java.util.concurrent 包下的类
    • Hashtable=

    这里的线程安全是指,多个线程调用它们同一个实例的某个方法时,是线程安全的,也可以理解为:

    new table Hashtable ( );newThread
    ( ().put->{
    	table("key","") ;value1})
    .start();newThread
    
    ( ().put->{
    	table("key","value2") ;})
    .start();
  • 它们的每个方法是原子的
  • 但是它们多个方法的组合不是原子的,可能会出现线程安全问题
    • Hashtable=
    new table Hashtable ( );//线程1,线程2if
    (
    . gettable("key")==null ) .put {
    	table("key",); value}
  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
  • String 有 replace,substring 等方法【可以】改变值,那么这些方法又是如何保证线程安全的呢?
  • 不可变类线程安全性

    • 如果没有绑定,则会先去与 Monitor 绑定,并且将 Owner 设为当前线程
    • 当 Monitor 的 Owner 将临界区中代码执行完毕后,Owner 便会被清空,此时 EntryList 中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的
    • 这是因为这些方法的返回值都创建了一个新的对象,而不是直接改变 String、Integer 对象本身
    4. Monitor 概念


    当线程执行到临界区代码时,如果使用了 synchronized,会先查询 synchronized 中所指定的对象 (obj) 是否绑定了 Monitor.

    • 对象在使用了 synchronized 后与 Monitor 绑定时,会将对象头中的 Monitor Word 置为 Monitor 指针
    • 如果已经绑定,则会去查询该 Monitor 是否已经有了 Owner
      (1) 如果没有,则将 Owner 与将当前线程绑定
      (2) 如果有,则放入 EntryList,进入阻塞状态(blocked)
    • 每个对象都会绑定一个唯一的 Monitor,如果 synchronized 中所指定的对象 (obj) 不同,则会绑定不同的 Monitor

    注意:

    • Normal:一般状态,没有加任何锁,前面 62 位保存的是对象的信息,最后 2 位为状态(01),倒数第 3 位表示是否使用偏向锁(未使用:0)
    • Biased:偏向状态,使用偏向锁,前面 54 位保存的当前线程的 ID,最后 2 位为状态(01),倒数第 3 位表示是否使用偏向锁(使用:1)
    5. synchronized 原理进阶

    Java 对象头格式

    64 位虚拟机 Mark Word 结构如下:

    5.1 轻量级锁(用于优化 Monitor 这类的重量级锁)

    轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。

    • 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录对象,内部可以存储锁定对象的 mark word(不在一开始就使用 Monitor)

    • 让锁记录中的 Object Reference 指向锁对象(Object),并尝试用 CAS 去替换 Object 中的mark word,将此 mark word 放入 lock record 中保存

    • 如果 CAS 替换成功,则将 Object 的对象头替换为锁记录的地址和状态 00(轻量级锁状态),并由该线程给对象加锁

    5.2 锁膨胀
    • 如果一个线程在给一个对象加轻量级锁时,CAS 替换 *** 作失败(因为此时其他线程已经给对象加了轻量级锁),此时该线程就会进入锁膨胀过程
    • 此时便会给对象加上重量级锁(使用 Monitor)
      将对象头的 Mark Word 改为 Monitor 的地址,并且状态改为 01 (重量级锁)
      并且该线程放入 EntryList 中,并进入阻塞状态 (blocked)
    5.3 自旋优化

    重量级锁竞争时,还可以使用自旋来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态。

    • 第一种情况
    • 第二种情况
    5.4 偏向锁(用于优化轻量级锁重入)

    轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象) *** 作仍需要 CAS 替换 *** 作,这样会导致性能降低。

    所以引入了偏向锁对性能进行优化:在第一次 CAS 时会将线程的 ID 写入对象的 Mark Word中。此后发现这个线程 ID 就是自己的,就表示没有竞争,就不需要再次 CAS ,以后只要不发生竞争,这个对象就归该线程所有。

    偏向状态

    • Lightweight:使用轻量级锁,前 62 位保存的是锁记录的指针,最后两位为状态(00)
    • 如果开启了偏向锁(默认开启),在创建对象时,对象的 Mark Word 后三位应该是 101
    • 但是偏向锁默认是有延迟的,不会在程序一启动就生效,而是会在程序运行一段时间(几秒之后),才会对创建的对象设置为偏向状态
    • Heavyweight:使用重量级锁,前 62 位保存的是 Monitor 的地址指针,后两位为状态(10)
    • 如果没有开启偏向锁,对象的 Mark Word 后三位应该是 001
    • 调用对象的 hashCode 方法
    • 多个线程使用该对象

    以下几种情况会使对象的偏向锁失效

    • 调用了 wait/notify 方法(调用 wait 方法会导致锁膨胀而使用重量级锁)
    • 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向 T1 的对象仍有机会重新偏向 T2,重偏向会重置Thread ID
    • 当撤销超过 20 次后(超过阈值),JVM 会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程
    5.5 批量重偏向
    • 锁对象调用 wait 方法(obj.wait),会释放对象的锁,使当前线程进入 WaitSet 中,变为 WAITING 状态
    • BLOCKED 状态的线程会在锁被释放的时候被唤醒,但是处于 WAITING 状态的线程只有被锁对象调用了 notify 方法 (obj.notify/obj.notifyAll),才会被唤醒
    5.6 批量撤销

    当撤销偏向锁的阈值超过 40 以后,就会将整个类的对象都改为不可偏向的

    6. wait/notify 6.1 原理

      public
    • 处于 BLOCKED 和 WAITING 状态的线程都为阻塞状态,CPU 都不会分给他们时间片。但是有所区别:
      BLOCKED 状态的线程是在竞争对象时,发现 Monitor 的 Owner 已经是别的线程了,此时就会进入 EntryList 中,并处于BLOCKED状态
      然而,WAITING 状态的线程是获得了对象的锁,但是自身因为某些原因需要进入阻塞状态时,锁对象调用了 wait 方法而进入了 WaitSet 中,处于 WAITING 状态
    • class

    wait 和 notify 都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用这几个方法,示例代码如下:

    Test final static {
        Object = new obj Object ( );publicstatic
        
        void main ( String[])new argsThread {
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}}
                    System
                .
                .printlnout("其他代码...");})
            .start();newThread
    
            ( ()synchronized(->{
                ) Systemobj. {
                    .printlnout("执行...");try.
                    wait {
                        obj();//让线程在obj上一直等待下去} catch
                    ( InterruptedException ). eprintStackTrace {
                        e();}System
                    .
                    .printlnout("其他代码...");}}
                )
            .start();//主线程两秒后执行sleep
    
            (
            2);System.
            .printlnout("唤醒 obj 上其他线程");synchronized(
            ) .objnotify {
                obj();//唤醒obj上一个线程//obj.notifyAll();  //唤醒obj上所有等待线程  }
                }
            }
        
  • sleep 是 Thread 类的静态方法,wait 是 Object 的方法,Object 又是所有类的父类,所以所有类都有 wait 方法
  • sleep 在阻塞(睡眠)的时候不会释放锁,而 wait 在阻塞的时候会释放锁
  • 6.2 使用 wait/notify 的正确姿势

    wait 和 sleep 的区别:

    • sleep 不需要与 synchronized 一起使用,而 wait 需要与 synchronized 一起使用(对象被锁以后才能使用)
    • 阻塞状态都为 TIMED_WAITING
    • 当线程不满足某些条件,需要暂停运行时,可以使用 wait,这样会将对象的锁释放,让其他线程能够继续运行。如果此时使用 sleep,会导致所有线程都进入阻塞,导致所有线程都没法运行,直到当前线程 sleep 结束后,运行完毕,才能得到执行

    wait 与 sleep 的相同点:

    • 当有多个线程在运行时,对象调用了 wait 方法,此时这些线程都会进入 WaitSet 中等待。如果这时使用了 notify 方法,可能会造成虚假唤醒(唤醒的不是满足条件的等待线程),这时就需要使用 notifyAll 方法

    什么时候适合使用 wait

      synchronized

    使用 wait/notify 的注意点

      (
    ) whileLOCK( {
    	.wait//不满足条件,一直等待,避免虚假唤醒) {
    		LOCK();}//满足条件后再运行
    	}
    	synchronized
    (
    ) //唤醒所有等待线程LOCK. {
    	notifyAll
    	LOCK();}
  • 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个 Guarded Object
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者-消费者)
  • 7. 模式之保护性暂停

    定义

    保护性暂停(Guarded Suspension)用在一个线程等待另一个线程的执行结果。
    要点:

    • JDK 中,join 的实现、Future 的实现,采用的就是 Guarded Suspension 模式
    • 因为要等待另一方的结果,因此归类到同步模式
    • publicclass

    案例代码如下

    Test public static {
    	void main ( String[])String args= {
    		"hello thread!" hello ; Guarded=
    		new guarded Guarded ( );newThread
    		( ()System.->{
    			.printlnout("想要得到结果");synchronized(
    			) Systemguarded. {
    				.printlnout("结果是:"+.getResponseguarded());}System
    			.
    			.printlnout("得到结果");})
    		.start();newThread
    
    		( ()System.->{
    			.printlnout("设置结果");synchronized(
    			) .guardedsetResponse {
    				guarded();hello}}
    			)
    		.start();}}
    	class
    Guarded
    
    /**
    	 * 要返回的结果
    	 */ private {
    	Object
    	; //优雅地使用 wait/notify responsepublic
    	
        Object
    	getResponse ( )//如果返回结果为空就一直等待,避免虚假唤醒while {
    		(
    		==nullresponse ) synchronized( {
    			this )trythis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				}
    			return
    		;
    		} responsepublic
    	void
    
    	setResponse ( Object)this response. {
    		=;response synchronized response(
    		this )//唤醒休眠的线程this {
    			.
    			notifyAll();}}
    		@Override
    	public
    
    	String
    	toString ( )return"Guarded{" {
    		+ "response=" +
    				+ '}' response ;
    				}}
    	public
    Object
    

    带超时判断的暂停

    getResponse ( long)synchronized time( {
    	this )//获取开始时间long {
    		=
    		System currentTime . currentTimeMillis();//用于保存已经等待了的时间long
    		=
    		0 passedTime ; while(
    		==nullresponse ) //看经过的时间-开始时间是否超过了指定时间long {
    			=
    			- waitTime ; time if passedTime(
    			<=0waitTime ) break; {
    				}try
    			//等待剩余时间
    			this {
                      	.
    				wait();waitTime}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}//获取当前时间
    			=
    			System
    			passedTime . currentTimeMillis()-}}currentTime		
               return
    	;
    	} responsepublic
    final
    

    join 源码——使用保护性暂停模式

    synchronized void join ( long)throws millisInterruptedException
        long = {
            System base . currentTimeMillis();long=
            0 now ; if(
    
            < 0millis ) thrownew {
                IllegalArgumentException ( "timeout value is negative");}if
            (
    
            == 0millis ) while( {
                isAlive ())wait( {
                    0);}}
                else
            while ( {
                isAlive ())long= {
                    - delay ; millis if now(
                    <= 0delay ) break; {
                        }wait
                    (
                    );delay=System
                    now . currentTimeMillis()-; } base}
                }
            //暂停线程运行
        LockSupport
    
    8. park & unpark 8.1 基本使用

    park/unpark 都是 LockSupport 类中的的方法

    .
    ;//恢复线程运行parkLockSupport
    
    .
    unpark();threadpublicstatic
    
    void main ( String[])throws argsInterruptedException Thread = {
    		new thread Thread ( ()System.-> {
    			.printlnout("park");//暂停线程运行LockSupport
                .
    			park();System.
    			.printlnout("resume");},
    		"t1") ;.start
    		thread();Thread.
    
    		sleep(1000);System.
    		.printlnout("unpark");//恢复线程运行LockSupport
        	.
    		unpark();thread}
  • wait/notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park&unpark 不必
  • park&unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】
  • 8.2 特点

    与 Object 的 wait/notify 相比

    • park&unpark 可以先 unpark,而 wait & notify 不能先 notify
    • park 不会释放锁,而 wait 会释放锁
    • 线程运行时,会将 park 对象中的 _counter 的值设为 0
    • 调用 park 时,会先查看 counter 的值是否为 0,如果为 0,则将线程放入阻塞队列 cond 中
    8.3 原理

    每个线程都有一个自己的 park 对象,并且该对象由 _counter, _cond,__mutex 组成

    情况 1:先调用 park,再调用 unpark

    先调用 park

    • 放入阻塞队列后,会再次将 counter 设置为 0
    • 调用 unpark 方法后,会将 counter 的值设置为 1
    • 去唤醒阻塞队列 cond 中的线程

    然后再调用 unpark

    • 会将 counter 设置为 1(运行时0)
    • 查看 counter 是否为 0
    • 线程继续运行并将 counter 的值设为 0

    情况 2:先调用 unpark,再调用 park

    先调用 unpark

    • 因为 unpark 已经把 counter 设置为 1,所以此时将 counter 设置为 0,但不放入阻塞队列 cond 中

    再调用 park

    • 当前线程所有代码运行完毕,进入 TERMINATED
    • 优点,可以增强并发度
    9. 线程状态转换


    假设有线程 Thread t

    情况一:NEW --> RUNNABLE

    • 当调用 t.start() 方法时,由 NEW --> RUNNABLE

    情况二:RUNNABLE <–> WAITING

    • 当调用了 t 线程用 synchronized(obj) 获取了对象锁后
      (1)调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
      (2)调用 obj.notify() ,obj.notifyAll() ,t.interrupt() 时:如果竞争锁成功,t 线程从 WAITING –> RUNNABLE;如果竞争锁失败,t 线程从 WAITING –> BLOCKED

    情况三:RUNNABLE <–> WAITING

    • 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING
      注意是当前线程在 t 线程对象的监视器上等待

    • t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE 情况

    情况四: RUNNABLE <–> WAITING

    • 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING
    • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –> RUNNABLE

    情况五: RUNNABLE <–> TIMED_WAITING
    t 线程用 synchronized(obj) 获取了对象锁后

    • 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING
    • t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
      (1)竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE
      (2)竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED

    情况六:RUNNABLE <–> TIMED_WAITING

    • 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING
      注意是当前线程在 t 线程对象的监视器上等待

    • 当前线程等待时间超过了 n 毫秒,或 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING –> RUNNABLE

    情况七:RUNNABLE <–> TIMED_WAITING

    • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING
    • 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE

    情况八:RUNNABLE <–> TIMED_WAITING

    • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE –> TIMED_WAITING
    • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE

    情况九:RUNNABLE <–> BLOCKED

    • t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED
    • obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED

    情况十: RUNNABLE <–> TERMINATED

    • 缺点,如果一个线程需要同时获得多把锁,就容易发生死锁
    10. 多把锁

    将锁的粒度细分

      publicstatic
    11. 活跃性

    定义:因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性。

    11.1 死锁

    有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。

    t1 线程获得 A 对象锁,接下来想获取 B 对象的锁, t2 线程获得 B 对象锁,接下来想获取 A 对象 的锁, 例:

    void main ( String[])final argsObject {
    
    	A = new Object ( );finalObject
    	B = new Object ( );newThread
    	
    	( ()synchronized(->{
    		A )tryThread {
    			. {
    				sleep(2000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			B )}} {
    			
    			}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		B )tryThread {
    			. {
    				sleep(1000);}catch
    			( InterruptedException ). eprintStackTrace {
    				e();}synchronized
    			(
    			A )}} {
    
    			}
    		)
    	.start();}
  • 互斥条件:在一段时间内,一种资源只能被一个进程所使用。
  • 请求和保持条件:进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源。
  • 发生死锁的四个必要条件

    • 不可抢占条件:进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放。
    • 循环等待条件:发生死锁时,必然存在一个进程——资源的循环链。
    • classChopstick

    定位死锁的方法:

    • 检测死锁可以使用 jconsole 工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁。


      省略中间的一些信息,找到最后一段信息,末尾处出现 Found 1 deadlock

    注意点:避免死锁要注意加锁顺序;另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 Linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程 id 来定位是哪个线程,最后再用 jstack 排查。

    哲学家就餐问题

    有 5 位哲学家,围坐在圆桌旁。他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
    如果筷子被身边的人拿着,自己就得等待。

    筷子类

    String ; {
        public nameChopstick
    
        ( String)this name. {
            =;name } name@Override
        public
    
        String
        toString ( )return"筷子{" {
            + +"}"name;}}
        class
    Philosopher
    

    哲学家类

    extends Thread Chopstick ; {
    
        Chopstick left;
        public rightPhilosopher
    
        ( String,Chopstick name, Chopstick left) super right( {
            );namethis.
            =;left this left.
            =;right } rightprivate
        void
    
        eat ( )System. {
            .printlnout("eating...");this.
            =;right } right@Override
        public
    
        void
        run ( )while( {
            true )//获得左手筷子synchronized {
                (
                ) //获得右手筷子leftsynchronized {
                    (
                    ) //吃饭righteat {
                        (
                        );}//放下右手筷子
                    }
                    //放下左手筷子
                }
                }
            }
        
  • 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。
  • 避免死锁的方法

    在线程使用锁对象时,顺序加锁即可避免死锁

    11.2 活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

    避免活锁的方法:在线程执行时,中途给予不同的间隔时间即可。

    死锁与活锁的区别

    • 可中断
    • 可以设置超时时间
    11.3 饥饿

    某些线程因为优先级太低,导致一直无法获得资源的现象,在使用顺序加锁时,可能会出现饥饿现象。

    12. 可重入锁

    和 synchronized 相比具有的的特点

    • 可以设置为公平锁 (先到先得)
    • 支持多个条件变量( 具有多个 waitset)
    • //获取ReentrantLock对象private

    基本语法

    ReentrantLock
    = new lock ReentrantLock ( );//加锁.
    lock
    lock();try//需要执行的代码
    } {
    	finally
    //释放锁. {
    	unlock
    	lock();}
  • 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
  • 可重入

    • 如果某个线程处于阻塞状态,可以调用其 interrupt 方法让其停止阻塞,获得锁失败。简而言之就是:处于阻塞状态的线程,被打断了就不用阻塞了,直接停止运行。
    • public

    可打断

      static
    void main ( String[])ReentrantLock args= {
    
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//加锁,可打断锁-> {
    		. {
    			lockInterruptibly
    			lock();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,返回,不再向下执行return
                   ;
    			}finally
    		//释放锁. {
    			unlock
    			lock();}}
    		)
    
    	;.lock
    
    	lock();try.
    	start {
    		t1();Thread.
    		sleep(1000);//打断.
    		interrupt
    		t1();}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 使用 lock.tryLock 方法会返回获取锁是否成功。如果成功则返回 true,反之则返回 false
  • 并且 tryLock 方法可以指定等待时间,参数为:tryLock(long timeout, TimeUnit unit),其中timeout 为最长等待时间,TimeUnit 为时间单位
  • 锁超时

    • 归纳就是,获取失败了、获取超时了或者被打断了,不再阻塞,直接停止运行
    • publicstatic

    不设置等待时间,立刻失败

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()//未设置等待时间,一旦获取失败,直接返回falseif-> {
               (
    		!.tryLocklock())System. {
    			.printlnout("获取失败");//获取失败,不再向下执行,返回return
                   ;
    			}System
    		.
    		.printlnout("得到了锁");.unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	public
    static
    

    设置等待时间

    void main ( String[])ReentrantLock args= {
    	new lock ReentrantLock ( );Thread=
    	new t1 Thread ( ()try//判断获取锁是否成功,最多等待1秒-> {
    		if {
    			(
    			!.tryLocklock(1,TimeUnit. ))SECONDSSystem. {
    				.printlnout("获取失败");//获取失败,不再向下执行,直接返回return
    				;
    				}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();//被打断,不再向下执行,直接返回return
    			;
    			}System
    		.
    		.printlnout("得到了锁");//释放锁.
    		unlock
    		lock();})
    	;.lock
    
    
    	lock();try.
    	start{
    		t1();//打断等待.
    		interrupt
    		t1();Thread.
    		sleep(3000);}catch
    	( InterruptedException ). eprintStackTrace {
    		e();}finally
    	. unlock {
    		lock();}}
    	
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的
  • //默认是不公平锁,需要在创建时指定为公平锁

    公平锁

      ReentrantLock
    =
    new lock ReentrantLock ( true);
  • synchronized 中也有条件变量,就是 waitSet 等待队列 ,当条件不满足时进入waitSet 等待
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量,这就好比,synchronized 是那些不满足条件的线程都在一间休息室等消息,而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
  • 条件变量

    • await 前需要获得锁
    • await 执行后,会释放锁,进入 conditionObject 等待

    使用要点:

    • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
    • 竞争 lock 锁成功后,从 await 后继续执行
    • staticBoolean
    = false judge ; publicstatic
    void main ( String[])throws argsInterruptedException ReentrantLock = {
    	new lock ReentrantLock ( );//获得条件变量Condition
    	=
    	. condition newCondition lock();newThread
    	( ().lock->{
    		lock();trywhile
    		({
    			!)Systemjudge. {
    				.printlnout("不满足条件,等待...");//等待.
    				await
    				condition();}}
    			catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		System . {
    			.printlnout("执行完毕!");.unlock
    			lock();}}
    		)
    	.start();newThread
    
    	( ().lock->{
    		lock();tryThread
    		. {
    			sleep(1);=true
    			judge ; //释放.
    			signal
    			condition();}catch
    		( InterruptedException ). eprintStackTrace {
    			e();}finally
    		. unlock {
    			lock();}}
    		)
    
    	.start();}static
    final
    
    13. 同步模式之顺序控制
    Object = new LOCK Object ( );//判断先执行的内容是否执行完毕static
    Boolean
    = false judge ; publicstatic
    void main ( String[])new argsThread {
    	( ()synchronized(->{
    		) whileLOCK( {
    			! )tryjudge. {
    				wait {
    					LOCK();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout("2");}}
    		)
    	.start();newThread
    
    	( ()synchronized(->{
    		) SystemLOCK. {
    			.printlnout("1");=true
    			judge ; //执行完毕,唤醒所有等待线程.
                   notifyAll
    			LOCK();}}
    		)
    	.start();}public
    class
    

    交替输出(wait/notify 版本)

    Test static Symbol {
    	= new symbol Symbol ( );publicstatic
    	void main ( String[])new argsThread {
    		( ().run->{
    			symbol("a",1, 2) ;})
    		.start();newThread
    
    		( ().run->{
    			symbol("b",2, 3) ;})
    
    		.start();.run
    		symbol("c",3, 1) ;newThread
    		( ()})->{
    
    		.start();}}
    	class
    Symbol
    
    public synchronized {
    	void run ( String,int str, int flag) for nextFlag( {
    		int=0 i;<; i++loopNumber) iwhile( {
    			!=thisflag . )tryflagthis {
    				. {
    					wait();}catch
    				( InterruptedException ). eprintStackTrace {
    					e();}}
    				System
    			.
    			.printlnout();str//设置下一个运行的线程标记this
    			.
    			=;flag //唤醒所有线程 nextFlagthis
    			.
    			notifyAll();}}
    		private
    	int
    
    	/**
    	 * 线程的执行标记, 1->a 2->b 3->c
    	 */
    	= 1 flag ; privateint
    	= 5 loopNumber ; publicint
    
    	getFlag ( )return; {
    		} flagpublic
    	void
    
    	setFlag ( int)this flag. {
    		=;flag } flagpublic
    	int
    
    	getLoopNumber ( )return; {
    		} loopNumberpublic
    	void
    
    	setLoopNumber ( int)this loopNumber. {
    		=;loopNumber } loopNumber}
    	public
    class
    
    14. ThreadLocal

    ThreadLocal 是 JDK 包提供的,它提供线程本地变量,也就是如果创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程 *** 作这个变量时,实际 *** 作的是自己本地内存里面的变量,从而避免了线程安全问题。

    使用

    ThreadLocalTest public static {
       void main ( String[])// 创建ThreadLocal变量 argsThreadLocal {
          <
          String=new> stringThreadLocal ThreadLocal < ()>;ThreadLocal<
          User=new> userThreadLocal ThreadLocal < ()>;// 创建两个线程,分别使用上面的两个ThreadLocal变量Thread
    
          =
          new thread1 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread1 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread1 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Cristiano",37) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());// 移除.
              
              remove
    		 userThreadLocal();System.
    		 .printlnout(.getuserThreadLocal());})
          ;Thread=
    
          new thread2 Thread ( ()// stringThreadLocal第一次赋值.->{
             set
             stringThreadLocal("thread2 stringThreadLocal first");// stringThreadLocal第二次赋值.
             set
             stringThreadLocal("thread2 stringThreadLocal second");// userThreadLocal赋值.
             set
             userThreadLocal(newUser( "Lionel",34) );// 取值System
    
             .
             .printlnout(.getstringThreadLocal());System.
             .printlnout(.getuserThreadLocal());})
          ;// 启动线程.
    
          start
          thread1();.start
          thread2();}}
       class
    User
    
    String ; {
       int name;
       public ageUser
    
       ( String,int name) this age. {
          =;name this name.
          =;age } age@Override
       public
    
       String
       toString ( )return"User{" {
          + "name='" +
                + '\'' name + ", age=" +
                + '}' age ;
                }}
       thread1 stringThreadLocal second
    thread2 stringThreadLocal second
    User{name='Cristiano', age=37}
    User{name='Lionel', age=34}
    null
    
    
  • 每个线程中的 ThreadLocal 变量是每个线程私有的,而不是共享的
  • ThreadLocal 其实就相当于其泛型类型的一个变量,只不过是每个线程私有的,stringThreadLocal被赋值了两次,保存的是最后一次赋值的结果
  • 从运行结果可以看出

      publicclass
    • ThreadLocal可以进行以下几个 *** 作:
      set 设置值
      get 取出值
      remove 移除值

    原理

    Thread implements Runnable . . {
    	..=
        ThreadLocalnullThreadLocalMap threadLocals ; .=
    
        ThreadLocalnullThreadLocalMap inheritableThreadLocals ; ..
        .}static
    class
    
    ThreadLocalMap static class {
       Entry extends WeakReference < ThreadLocal<?/** The value associated with this ThreadLocal. */Object>> {
           ;
           Entry value(
    
           ThreadLocal<?,Object> k) super v( {
               );k=;
               value } v}
           }
       public
    void
    

    Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而 ThreadLocalMap 是一个定制化的 Hashmap。在默认情况下,每个线程中的这两个变量都为 null.

    ThreadLocal 中的方法

    set ( T)//获取当前线程 valueThread {
    	=
        Thread t . currentThread();//获得ThreadLocalMap对象, 返回Thread类中的threadLocalsThreadLocalMap
        =
        getMap map ( );tif(
        != nullmap ) //ThreadLocal自生的引用作为key,传入的值作为value.
        	set
            map(this,); valueelsecreateMap
        (
            ,)t; value}void
    createMap
    ( Thread,T t) // 创建的同时设置想放入的值 firstValue// threadLocal自生的引用作为key,传入的值作为value {
        .
        =
        tnewthreadLocals ThreadLocalMap ( this,); firstValue}public
    T
    
    get ( )Thread= {
        Thread t . currentThread();ThreadLocalMap=
        getMap map ( );tif(
        != nullmap ) .= {
            ThreadLocalMap.Entry e getEntry map(this);if(
            != nulle ) @SuppressWarnings( {
                "unchecked")T=
                ( result T ).;ereturnvalue;
                } result}
            return
        setInitialValue
        ( );}private
    T
    setInitialValue ( )T= {
         initialValue value ( );Thread=
         Thread t . currentThread();ThreadLocalMap=
         getMap map ( );tif(
         != nullmap ) .set
             map(this,); valueelsecreateMap
         (
             ,)t; valuereturn;
         } valuepublic
    void
    
    remove ( )ThreadLocalMap= {
        getMap m ( Thread.currentThread());if(
         != nullm ) .remove
             m(this);}
  • 在每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中
  • 只有当前线程第一次调用 ThreadLocal 的 set 或者 get 方法时才会创建 threadLocals(inheritableThreadLocals 也是一样)。其实每个线程的本地变量不是存放在 ThreadLocal 实例里面,而是存放在调用线程的 threadLocals 变量里面
    • Thread=

    从 ThreadLocal 的源码可以看出,无论是 set、get、还是 remove,都是相对于当前线程 *** 作

    Thread t . currentThread();publicclass
    

    因此 ThreadLocal 无法从父线程传向子线程,所以 InheritableThreadLocal 出现了,它能够让父线程中 ThreadLocal 的值传给子线程。

    也就是从 main 所在的线程,传给 thread1 或 thread2

    Test public static {
       void main ( String[])ThreadLocal args< {
          String=new> stringThreadLocal ThreadLocal < ()>;InheritableThreadLocal<
          String=new> stringInheritable InheritableThreadLocal < ()>;// 主线程赋对上面两个变量进行赋值.
    
          set
          stringThreadLocal("this is threadLocal");.set
          stringInheritable("this is inheritableThreadLocal");// 创建线程Thread
    
          =
          new thread1 Thread ( ()// 获得ThreadLocal中存放的值System->{
             .
             .printlnout(.getstringThreadLocal());// 获得InheritableThreadLocal存放的值System
    
             .
             .printlnout(.getstringInheritable());})
          ;.start
    
          thread1();}}
       null
    this is inheritableThreadLocal
    
    public
    

    运行结果

    class

    InheritableThreadLocal 的值成功从主线程传入了子线程,而 ThreadLocal 没有。

    原理

    InheritableThreadLocal < TextendsThreadLocal> < T// 传入父线程中的一个值,然后直接返回protected> {
        T
        childValue ( T)return parentValue; {
            } parentValue// 返回传入线程的inheritableThreadLocals
        // Thread中有一个inheritableThreadLocals变量
    
      	// ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ThreadLocalMap
        getMap
        ( Thread)return t. {
           ; t}inheritableThreadLocals// 创建一个inheritableThreadLocals
        void
    
     	createMap
        ( Thread,T t) . firstValue= {
            tnewinheritableThreadLocals ThreadLocalMap ( this,); firstValue}}
        
  • InheritableThreadLocal 继承了 ThreadLocal,并重写了三个方法。InheritableThreadLocal 重写了createMap 方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals 变量的实例而不再是 threadLocals。当调用 getMap 方法获取当前线程内部的 map 变量时,获取的是 inheritableThreadLocals 而不再是 threadLocals
  • 当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面
    • 欢迎分享,转载请注明来源:内存溢出

      原文地址: http://outofmemory.cn/langs/743446.html

      (0)
      打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
      上一篇 2022-04-29
      下一篇 2022-04-29

      发表评论

      登录后才能评论

      评论列表(0条)

      保存