线程知识总结

实现线程有两种方法,一种是实现runnable接口,一种是继承Thread线程类。关于这两者的区别是前者只是实现了runnable接口的一个类而已并不是线程,后者继承Thread才是线程

我们来看看代码具体了解下

public class ThreadTest {
		public static void main(String[] args) {
			T1 t1=new T1("t1");
			T2 t2=new T2("t2");
			new Thread(t1).start();//1语句 从测试代码可以看t2才是一个真正意义上的线程对象而t1不是
			t2.start();//2语句  这里值得注意的是语句1没执行完时 语句2就开始执行了语句1和语句2时并发执行的
		}


	}
	class T1 implements Runnable {
		String name;
		public T1(String name) {
			this.name = name;
		}
		public void run() {
			for(int i=0;i<5;i++){
				System.out.println("i am "+name);
			}
		}

	}
	class T2 extends Thread {
		String name;
		public T2(String name) {
		
			this.name = name;
		}
		public void run() {
			
			for(int i=0;i<5;i++){
				System.out.println("i am "+name);
			}
		}
	}

 

有了线程我们可以并发处理,但这在一定程度上也会让出现读取脏数据,或者说丢失更新,数据混乱等情况的发生,例如单例模式中两线程同时访问公共单例获取方法 一线程准备给对象引用赋值时,一线程却读取对象的引用为空,也准备创建对象,并把引用赋予局部变量。

这样一来就造成单例对象数据混乱,出现两个实例,和我们的本意相违背。

为了同步,我们引入了synchronized关键字来同步。synchronized关键字既可以用于对象前,也可以用于方法修饰。synchronized(对象引用名) public synchronized void(){}

synchronized关键字保证资源的互斥性,即一段时间之类只能有一个线程访问。线程访问之前必须持有资源锁才能访问。

线程同步简单口述下,就不发代码了。下面我们来看下线程协作。

这里要用到关键字wait() notify() 方法 线程协作我们来看看经典代码生产者 消费者

public class Cooperation {
		public static void main(String[] args) {
			SuperMonitor monitor = new SuperMonitor();
			new Producer(monitor,200);
			new Consumer(monitor,200);
			
			
			try
			{
				Thread.sleep(1000);	
			}
			catch (InterruptedException e) {
					e.printStackTrace();
			}

		}
	}
	//生产 消费要平衡 生产后再消费,消费完后再生产 由管理员类控制
	class  SuperMonitor {
		boolean valueSet = false;//标志是否在生产
		int count;//生产的物品总量
		public SuperMonitor() {
		
		}
		public synchronized int get() {
			if(!valueSet) {//生产线程不再生产了,那么消费线程进入等待池,等待生产线程唤醒
				
				try{
					wait();
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
		
			}
			
			valueSet = false;
			System.out.println("已消费"+count+"产品");
			notify();
			return count;
		}
		public synchronized void set(int i) {
		
			if(valueSet) {//如何生产线程可以生产了,生产线程进入等待池,等待消费线程唤醒
			
				try{
					wait();
				}
				catch (InterruptedException e) {
					//e.printStackTrace();
				}
			}

			valueSet = true;
			count=i;
			System.out.println("已生产"+count+"产品");
			notify();
		
	   }
	 }
	   class Producer implements Runnable{
			SuperMonitor monitor;
			int speed;//生产速度相关系数
			
		    public Producer(SuperMonitor monitor,int speed) {
				this.monitor = monitor;
				this.speed = speed;
				new Thread(this,"Producer").start();
			}
			
			public void run() {
				int i=0;
				while(true){
					monitor.set(++i);
					try
					{
						Thread.sleep((int)Math.random()*speed);	
					}
					catch (InterruptedException e) {
					//e.printStackTrace();
					}
				}
			
			}
			
	   }
	   class Consumer implements Runnable{
		   SuperMonitor monitor;
		   int speed;//消费数量相关系数
		  
			public Consumer(SuperMonitor monitor,int speed) {
				this.monitor = monitor;
				this.speed = speed;
				new Thread(this,"Consumer").start();
			}
			
			public void run() {
				while(true) {
					monitor.get();
					
				try
			    {
					Thread.sleep((int)Math.random()*speed);	
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		 }
	   }

 

这里指的注意的是管理员类里get set 方法需要加锁因为多线程环境下可能找造成同时调用方法,否则会造成异常

以上是线程的一些基本功能。但是我们知道,线程同步增加加了线程竞争资源的等待时间,线程切换同样需要cpu开销,所以说进行线程优化是必不可少的。

线程性能优化大致有以下几点:

1 减少锁的持有时间

我们可以看下以下的代码

public synchronized void get() {
    h1();
    get();//假设只有get()方法需要同步
   h2();
}

代码这样写肯定是不妥的,因为h1();h2();方法并不需要同步,这样只会扩大了锁的持有时间,当线程持有锁后,必须执行完锁相关的任务才能释放锁。H1();h2();方法无疑是增加了任务的执行时间,也就锁的持有时间。所以说在需要加锁的方法前加上关键字才是恰当的做法。

减小锁粒度

减少锁粒度很经典的一个应用就是ConcurrentHashMap 当执行put方法时并会获得整个资源锁,而是通过HashCode()方法计算需要存放的段,然后获得段的锁。在多线程环境下,当有多个线程同时执行put操作时,由于hashcode()所得到不同的地址段(冲突少),所以说是对不同的段加锁。也就是说可以同时进行put操作,做到真正的并行。同理多个线程同时进行get操作时也不要获取整个ConcurrentHashMap的资源锁,而是获取某一个段的锁就可以了。

但是这种方式也存在一定的弊端,当要统计整个条目的数量时用锁方式,则要用循环的方式,一一加锁,再用循法的方法一一加数,最后再循法一一解锁。

3 粗化锁粒度

当有一连串对资源的锁申请时,会选择对锁进行粗化操作

public void get() {

	synchronized (lock){};
	synchronized (lock){};
	synchronized (lock){};
}

以上代码并不是合理的,因为频繁的加锁,释放锁需要很到的系统开销,将那一连串整合到一起,可以有效减少同步释放开销。

4 锁分离

我们都知道读操作和读操作时可以同时进行的,这也是锁分离的结果如果一个文件资源只有一个资源锁,那么一用户在读的同时其他用户是不能进行读操作的。就在这时我们可以引入都锁(非独占),写锁(独占)来是实现读读不互斥,读写互斥,写写互斥。

5 jvm 也提供了一系列支持来优化锁开销,自旋锁减少线程状态和上下文切换。

另有锁消除在运行时排除不可能存在锁竞争的资源的地方,节省锁请求资源时间。还有锁偏向,当一对象被一线程持有,若下次来之前此锁不被其他线程拥有,则再一次获取此锁时不会在同步。但锁偏向不适宜锁资源竞争激烈的环境因为此时有大量的线程切换,锁偏向基本不起作用,在此情况下禁用锁偏向反而有利于提升系统性能。

  

 

 

 

 

posted @ 2015-08-27 20:23  kimoyoyo21  阅读(367)  评论(0编辑  收藏  举报