线程基础知识笔记

1、线程安全

2、多个线程多个锁

3、对象锁同步和异步

4、脏读

5、synchronized其他概念

6、volatile关键字

7、线程通信

8、ThreadLocal

1、线程安全

  1.1、线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能保持正确的行为,那么这个类(对象或方法)是线程安全的。

  1.2、synchronized:可以在任意对象及方法上加上锁,而加锁的这段代码成为‘互斥区’或‘临界区’。

举一个简单例子

import java.util.concurrent.atomic.AtomicInteger;

public class MyThread extends Thread{
	
	private int count = 5 ;
	
	//synchronized加锁
	public void run(){
		count--;
		System.out.println(this.currentThread().getName() + " count = "+ count);
	}
	
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

打印出来的结果是

t2 count = 3
t4 count = 1
t3 count = 2
t1 count = 3
t5 count = 0
这个明显不是我想要的。

我们只需要在run方法加上一把锁(synchronized)就可以

t1 count = 4
t5 count = 3
t4 count = 2
t2 count = 1
t3 count = 0
总结:我们代码中线程写的是t1、t2、t3、t4、t5,但是我们打印出来的结果并没有按照这样的顺序去走。

分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的)。一个线程想要执行synchronized修饰的方法里的代码:1 尝试获得锁 2 如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,  而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)

2、多个线程多个锁

      多个线程,每个线程可以拿到自己指定的锁,分别获得锁之后,执行synchroized方法体的内容。

举一个简单例子

public class MultiThread {

	private int num = 0;
	
	/** static */
	public 	synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args) {
		
		//俩个不同的对象
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		
		t1.start();
		t2.start();
		
	}
	
	
	
	
}
打印出来的结果是

tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100

出现以上结果的原因就是因为m1 m2是2个不同的对象。互不影响

按照正常思维的话代码打印结果应该是

tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
才符合我们的逻辑。如果你想要这种结果,就需要在 printNum()前面使用static修饰。如果在synchronized加上static,那么以后使用printNum()所获的的锁就是MultiThread这个类级别的锁

总结: 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁, 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock), 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。

3、对象锁同步和异步

  同步(synchroized):同步的概念就是共享,我们要牢牢记住‘共享’这2个字,如果不是共享的资源,就没有必要进行同步。

  异步(asynchroized):异步的概念就是独立,互相之间不受到任何限制。好像页面发起http请求一样,我们还可以继续浏览或者操作页面的内容。

 同步的目的是为了线程安全,其实对于线程安全来说,需要满足2个特性:

  原子性(同步)

  可见性

举个例子

public class MyObject {

	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/** synchronized */
	public void method2(){
			System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		
		final MyObject mo = new MyObject();
		
		/**
		 * 分析:
		 * t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
		 * t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
		 */
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
		
	}
	
}
打印结果为:

t1
t2
这2个同时打印。

如果我们在method2()加了synchronized方法,则会出现t1打印完秒钟后,才打印t2,因为进入method2()需要method1()释放对象锁。


4、脏读

      对于对象的同步和异步的方法,编码的时候,就必须要考虑问题的整体,不然会出现数据不一致的情况,很经典的错误就是脏读(dirtyread) 

举个例子

public class DirtyRead {

	private String username = "xjd";
	private String password = "123";

	public synchronized void setValue(String username, String password) {
		this.username = username;

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		this.password = password;

		System.out.println("setValue最终结果:username = " + username + " , password = " + password);
	}

	public void getValue() {
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}

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

		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");
			}
		});
		t1.start();
		Thread.sleep(1000);

		dr.getValue();
	}

}

getValue方法得到:username = z3 , password = 123
setValue最终结果:username = z3 , password = 456
结果明显不符合我们期望的,这就是脏读。

我们只需要在getValue()加上synchronized即可 。


5、synchronized其他概念

        关键字synchronized拥有锁重入的功能,也就是在使用synchronized的时候,当一个线程得到一个对象后,再次请求此对象的时候可以再次得到该对象的锁。

举个例子:

形式一、

public class SyncDubbo1 {

	public synchronized void method1(){
		System.out.println("method1..");
		method2();
	}
	public synchronized void method2(){
		System.out.println("method2..");
		method3();
	}
	public synchronized void method3(){
		System.out.println("method3..");
	}
	
	public static void main(String[] args) {
		final SyncDubbo1 sd = new SyncDubbo1();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				sd.method1();
			}
		});
		t1.start();
	}
}

形式二(父子继承关系的)

public class SyncDubbo2 {

	static class Main {
		public int i = 10;
		public synchronized void operationSup(){
			try {
				i--;
				System.out.println("Main print i = " + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	static class Sub extends Main {
		public synchronized void operationSub(){
			try {
				while(i > 0) {
					i--;
					System.out.println("Sub print i = " + i);
					Thread.sleep(100);		
					this.operationSup();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				Sub sub = new Sub();
				sub.operationSub();
			}
		});
		
		t1.start();
	}
	
	
}
这就是锁重入。

   synchronized异常:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序产生严重的逻辑错误,比如很多对象都在等待第一个对象的正确执行完毕再去释放对象锁,但是第一个对象由于异常出现,导致业务代码无法被正常的执行,就释放了锁。那么可想而知后续的对象执行的全部都是错误的逻辑。这里的处理方案有好几种,比如:比如你有20个任务,这些任务都是没有关系的。那么可以使用try catch去捕获然后写入日志。如果这20个任务是有关联关系的,那么就要停止这个线程,记录日志,不同的业务有不同的处理方案。

public class SyncException {

	private int i = 0;
	public synchronized void operation(){
		while(true){
			try {
				i++;
				Thread.sleep(100);
				System.out.println(Thread.currentThread().getName() + " , i = " + i);
				if(i == 20){
					//Integer.parseInt("a");
					throw new RuntimeException();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		
		final SyncException se = new SyncException();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				se.operation();
			}
		},"t1");
		t1.start();
	}
	
	
}
输出结果为:
t1 , i = 1
t1 , i = 2
t1 , i = 3
t1 , i = 4
t1 , i = 5
t1 , i = 6
t1 , i = 7
t1 , i = 8
t1 , i = 9
t1 , i = 10
t1 , i = 11
t1 , i = 12
t1 , i = 13
t1 , i = 14
t1 , i = 15
t1 , i = 16
t1 , i = 17
t1 , i = 18
t1 , i = 19
Exception in thread "t1" java.lang.RuntimeException
	at com.eshine.base.sync005.SyncException.operation(SyncException.java:18)
	at com.eshine.base.sync005.SyncException$1.run(SyncException.java:32)
	at java.lang.Thread.run(Thread.java:724)
t1 , i = 20

synchronized代码块其他细节

 对象锁

public class ObjectLock {

	public void method1(){
		synchronized (this) {	//对象锁
			try {
				System.out.println("do method1..");
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void method2(){		//类锁
		synchronized (ObjectLock.class) {
			try {
				System.out.println("do method2..");
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private Object lock = new Object();
	public void method3(){		//任何对象锁
		synchronized (lock) {
			try {
				System.out.println("do method3..");
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	public static void main(String[] args) {
		
		final ObjectLock objLock = new ObjectLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method1();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method2();
			}
		});
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method3();
			}
		});
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
	
}

同一对象属性的修改不会影响锁的情况

public class ModifyLock {
	
	private String name ;
	private int age ;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	public synchronized void changeAttributte(String name, int age) {
		try {
			System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 开始");
			this.setName(name);
			this.setAge(age);
			
			System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 修改对象内容为: " 
					+ this.getName() + ", " + this.getAge());
			
			Thread.sleep(2000);
			System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 结束");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		final ModifyLock modifyLock = new ModifyLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				modifyLock.changeAttributte("张三", 20);
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				modifyLock.changeAttributte("李四", 21);
			}
		},"t2");
		
		t1.start();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
	
}

死锁:死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况

public class DeadLock implements Runnable{

	private String tag;
	private static Object lock1 = new Object();
	private static Object lock2 = new Object();
	
	public void setTag(String tag){
		this.tag = tag;
	}
	
	@Override
	public void run() {
		if(tag.equals("a")){
			synchronized (lock1) {
				try {
					System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lock2) {
					System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
				}
			}
		}
		if(tag.equals("b")){
			synchronized (lock2) {
				try {
					System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lock1) {
					System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
				}
			}
		}
	}
	
	public static void main(String[] args) {
		
		DeadLock d1 = new DeadLock();
		d1.setTag("a");
		DeadLock d2 = new DeadLock();
		d2.setTag("b");
		 
		Thread t1 = new Thread(d1, "t1");
		Thread t2 = new Thread(d2, "t2");
		 
		t1.start();
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
	

	
}

6、volatile关键字

  volatile关键字的主要作用是使变量在多个线程中可见

public class RunThread extends Thread{

	private  boolean isRunning = true;
	private void setRunning(boolean isRunning){
		this.isRunning = isRunning;
	}
	
	public void run(){
		System.out.println("进入run方法..");
		int i = 0;
		while(isRunning == true){
			//..
		}
		System.out.println("线程停止");
	}
	
	public static void main(String[] args) throws InterruptedException {
		RunThread rt = new RunThread();
		rt.start();
		Thread.sleep(1000);
		rt.setRunning(false);
		System.out.println("isRunning的值已经被设置了false");
	}
	
	
}
打印结果为:

进入run方法..
isRunning的值已经被设置了false
但是线程并没有停止,原因:在线程执行的时候为了提高性能,虚拟机会分配多一块内存空间出来存放线程中使用的变量,线程每次使用到变量就直接读取,而不是从外部读取,导致了这种情况。所以我们需要在private volatile boolean isRunning = true;  这句代码加上volatile 。

进入run方法..
isRunning的值已经被设置了false
线程停止

volatile关键字不具备synchronized关键字的原子性(同步)建议使用AtomicInteger

public class VolatileNoAtomic extends Thread{
	//private static volatile int count;
	private static AtomicInteger count = new AtomicInteger(0);
	private static void addCount(){
		for (int i = 0; i < 1000; i++) {
			//count++ ;
			count.incrementAndGet();
		}
		System.out.println(count);
	}
	
	public void run(){
		addCount();
	}
	
	public static void main(String[] args) {
		
		VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
		for (int i = 0; i < 10; i++) {
			arr[i] = new VolatileNoAtomic();
		}
		
		for (int i = 0; i < 10; i++) {
			arr[i].start();
		}
	}
	
	
	
	
}
最终结果为10000.  当然也可以在addCount() 加 synchronized;最终结果也是一样的 

volatie关键字只具有可见性,没有原子性。要实现原子性建议使用atomic类的系列对象,支持原子性操作(注意:atomic类只保证本身方法原子性,并不保证多次操作的原子性)

举个栗子

public class AtomicUse {

	private static AtomicInteger count = new AtomicInteger(0);

	// 多个addAndGet在一个方法内是非原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性
	/** synchronized */
	public synchronized int multiAdd() {
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		count.addAndGet(1);
		count.addAndGet(2);
		count.addAndGet(3);
		count.addAndGet(4); // +10
		return count.get();
	}

	public static void main(String[] args) {

		final AtomicUse au = new AtomicUse();

		List<Thread> ts = new ArrayList<Thread>();
		for (int i = 0; i < 100; i++) {
			ts.add(new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(au.multiAdd());
				}
			}));
		}

		for (Thread t : ts) {
			t.start();
		}

	}
}
这里我们只能添加synchronized关键字来保证一数据的最终一致。

7、线程通信

使用wait/notify方法实现线程间的通信,这2个方法都是Object的类的方法,也就是说,java为所有类都提供了这2个方法

7.1、wait和notify必须配合synchronized关键字使用

7.2、wait方法释放对象锁,notify不会释放对象锁
 这里有2个例子,先看第一个

public class ListAdd1 {

	private volatile static List list = new ArrayList();

	public void add() {
		list.add("xjd");
	}

	public int size() {
		return list.size();
	}

	public static void main(String[] args) {

		final ListAdd1 list1 = new ListAdd1();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					for (int i = 0; i < 10; i++) {
						list1.add();
						System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
						Thread.sleep(500);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1");

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					if (list1.size() == 5) {
						System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
						throw new RuntimeException();
					}
				}
			}
		}, "t2");

		t1.start();
		t2.start();
	}

}
打印结果:

当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程收到通知:t2 list size = 5 线程停止..
Exception in thread "t2" java.lang.RuntimeException
	at com.eshine.base.conn008.ListAdd1$2.run(ListAdd1.java:43)
	at java.lang.Thread.run(Thread.java:724)
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
这种方式非常的不好,因为t2一直在轮询。导致性能会比较差

第二种方式则更好一点

public class ListAdd2 {
	private volatile static List list = new ArrayList();

	public void add() {
		list.add("xjd");
	}

	public int size() {
		return list.size();
	}

	public static void main(String[] args) {

		final ListAdd2 list2 = new ListAdd2();

		// 1 实例化出来一个 lock
		// 当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
		// final Object lock = new Object();

		final CountDownLatch countDownLatch = new CountDownLatch(1);

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// synchronized (lock) {
					for (int i = 0; i < 10; i++) {
						list2.add();
						System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
						Thread.sleep(500);
						if (list2.size() == 5) {
							System.out.println("已经发出通知..");
							countDownLatch.countDown();
							// lock.notify();
						}
					}
					// }
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}
		}, "t1");

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				// synchronized (lock) {
				if (list2.size() != 5) {
					try {
						// System.out.println("t2进入...");
						// lock.wait();
						countDownLatch.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
				throw new RuntimeException();
				// }
			}
		}, "t2");

		t2.start();
		t1.start();

	}

}
打印结果:
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知..
Exception in thread "t2" java.lang.RuntimeException
	at com.eshine.base.conn008.ListAdd2$2.run(ListAdd2.java:71)
	at java.lang.Thread.run(Thread.java:724)
当前线程:t1添加了一个元素..
当前线程:t2收到通知线程停止..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
7.3使用wait/notify模拟Queue

BlockingQueue:顾名思义,一个支持阻塞式机制的队列,他有put、take方法

put:把一个object放入到BlockingQueue中,如果BlockingQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue有空间再继续

take:取走BlockingQueue的首个对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据加入

public class MyQueue {
	
	//1 需要一个承装元素的集合 
	private LinkedList<Object> list = new LinkedList<Object>();
	
	//2 需要一个计数器
	private AtomicInteger count = new AtomicInteger(0);
	
	//3 需要制定上限和下限
	private final int minSize = 0;
	
	private final int maxSize ;
	
	//4 构造方法
	public MyQueue(int size){
		this.maxSize = size;
	}
	
	//5 初始化一个对象 用于加锁
	private final Object lock = new Object();
	
	
	//put(anObject): 把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续.
	public void put(Object obj){
		synchronized (lock) {
			while(count.get() == this.maxSize){
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//1 加入元素
			list.add(obj);
			//2 计数器累加
			count.incrementAndGet();
			//3 通知另外一个线程(唤醒)
			lock.notify();
			System.out.println("新加入的元素为:" + obj);
		}
	}
	
	
	//take: 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入.
	public Object take(){
		Object ret = null;
		synchronized (lock) {
			while(count.get() == this.minSize){
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//1 做移除元素操作
			ret = list.removeFirst();
			//2 计数器递减
			count.decrementAndGet();
			//3 唤醒另外一个线程
			lock.notify();
		}
		return ret;
	}
	
	public int getSize(){
		return this.count.get();
	}
	
	
	public static void main(String[] args) {
		
		final MyQueue mq = new MyQueue(5);
		mq.put("a");
		mq.put("b");
		mq.put("c");
		mq.put("d");
		mq.put("e");
		
		System.out.println("当前容器的长度:" + mq.getSize());
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mq.put("f");
				mq.put("g");
			}
		},"t1");
		
		t1.start();
		
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				Object o1 = mq.take();
				System.out.println("移除的元素为:" + o1);
				Object o2 = mq.take();
				System.out.println("移除的元素为:" + o2);
			}
		},"t2");
		
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t2.start();
		
		
	}
	
	
	
}
打印结果为:
新加入的元素为:a
新加入的元素为:b
新加入的元素为:c
新加入的元素为:d
新加入的元素为:e
当前容器的长度:5
新加入的元素为:f
移除的元素为:a
移除的元素为:b
新加入的元素为:g

8、ThreadLocal

概念:线程局部变量,是一种多线程间并发访问变量的解决方案。与synchronized加锁方式不同,TheadLocal不提供锁,而是使用空间换时间的手段,为每一个线程提供变量的独立副本,以保证线程安全。

public class ConnThreadLocal {

	public static ThreadLocal<String> th = new ThreadLocal<String>();
	
	public void setTh(String value){
		th.set(value);
	}
	public void getTh(){
		System.out.println(Thread.currentThread().getName() + ":" + this.th.get());
	}
	
	public static void main(String[] args) throws InterruptedException {
		
		final ConnThreadLocal ct = new ConnThreadLocal();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				ct.setTh("张三");
				ct.getTh();
			}
		}, "t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
					ct.getTh();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t2");
		
		t1.start();
		t2.start();
	}
	
}
结果:

t1:张三
t2:null


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值