Java 多线程同步和异步详解

 

1)多线程并发时,多个线程同时请求同一个资源,必然致使此资源的数据不安全,A线程修改了B线
程的处理的数据,而B线程又修改了A线程处理的数理。显然这是因为全局资源形成的,有时为了解
决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲
系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制
同步:A线程要请求某个资源,可是此资源正在被B线程使用中,由于同步机制存在,A线程请求
不到,怎么办,A线程只能等待下去
异步:A线程要请求某个资源,可是此资源正在被B线程使用中,由于没有同步机制存在,A线程
仍然请求的到,A线程无需等待
java

java中实现多线程
1)继承Thread,重写里面的run方法
2)实现runnable接口web

比较推荐后者,第一,java没有单继承的限制第二,还能够隔离代码数组

2)线程池
要知道在计算机中任何资源的建立,包括线程,都须要消耗系统资源的。在WEB服务中,对于web服
务器的响应速度必需要尽量的快,这就容不得每次在用户提交请求按钮后,再建立线程提供服务
。为了减小用户的等待时间,线程必须预先建立,放在线程池中,线程池能够用HashTable这种数
据结构来实现,看了Apach HTTP服务器的线程池的源代码,用是就是HashTable,KEY用线程对象,
value 用ControlRunnable,ControlRunnable是线程池中惟一能干活的线程,是它指派线程池中的
线程对外提供服务。出于安全考虑,Apach HTTP服务器的线程池它是同步的。安全

3)Java同步机制有4种实现方式:服务器

1.synchronized 多线程

同步方法 
    即有synchronized关键字修饰的方法。 
    因为java的每一个对象都有一个内置锁,当用此关键字修饰方法时, 
    内置锁会保护整个方法。在调用该方法前,须要得到内置锁,不然就处于阻塞状态。
    代码如: 
    public synchronized void save(){}并发

同步代码块 
    即有synchronized关键字修饰的语句块。 
    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
    代码如: 
    synchronized(object){ 
    }
   注: synchronized关键字也能够修饰静态方法,此时若是调用该静态方法,将会锁住整个类dom

2.ThreadLocal
    ThreadLocal 保证不一样线程拥有不一样实例,相同线程必定拥有相同的实例,即为每个使用该
变量的线程提供一个该变量值的副本,每个线程均可以独立改变本身的副本,而不是与其它线程
的副本冲突。
优点:提供了线程安全的共享对象
与其它同步机制的区别:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之
间进行通讯;而 ThreadLocal 是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源
,这样固然不须要多个线程进行同步了。
3.volatile
     volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。
并且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。
    优点:这样在任什么时候刻,两个不一样的线程老是看到某个成员变量的同一个值。
    原因:Java 语言规范中指出,为了得到最佳速度,容许线程保存共享成员变量的私有拷贝,而
且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某
个对象交互时,就必需要注意到要让线程及时的获得共享成员变量的变化。而 volatile 关键字就
是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
     使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在
synchronized 代码块中,或者为常量时,没必要使用。
        线程为了提升效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的
是B。只在某些动做时才进行A和B的同步,所以存在A和B不一致的状况。volatile就是用来避免这种
状况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操做多时使用
较好;线程间须要通讯,本条作不到)
Volatile 变量具备 synchronized 的可见性特性,可是不具有原子特性。这就是说线程可以自
动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,可是只能应用于很是有限的
一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理
想的线程安全,必须同时知足下面两个条件:
对变量的写操做不依赖于当前值;该变量没有包含在具备其余变量的不变式中。异步

//只给出要修改的代码,其他代码与上同
        class Bank {
            //须要同步的变量加上volatile
            private volatile int account = 100;

            public int getAccount() {
                return account;
            }
            //这里再也不须要synchronized 
            public void save(int money) {
                account += money;
            }
        }

4.使用重入锁实现线程同步 ReenreantLockjvm

ReentrantLock感受上是synchronized的加强版,synchronized的特色是使用简单,一切交给JVM去处理,可是功能上是比较薄弱的。在JDK1.5以前,ReentrantLock的性能要好于synchronized,因为对JVM进行了优化,如今的JDK版本中,二者性能是不相上下的。若是是简单的实现,不要刻意去使用ReentrantLock。

相比于synchronized,ReentrantLock在功能上更加丰富,它具备可重入、可中断、可限时、公平锁等特色。

ReenreantLock类的经常使用方法有:

ReentrantLock() : 建立一个ReentrantLock实例 

lock() : 得到锁 

unlock() : 释放锁 

package test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Bank {
    private int account = 100;
    //须要声明这个锁
    private Lock lock = new ReentrantLock();
    public int getAccount() {
        return account;
    }

    //这里再也不须要synchronized 
    public void save(int money) {
        lock.lock();
        try{
            account += money;
        }finally{
            lock.unlock();
        }
    }
}

lockInterruptibly():得到响应中断的锁

interrupt():中断

可中断:在java中,线程的中断(interrupt)只是改变了线程的中断状态。对于执行通常逻辑的线程,若是调用它的interrupt()方法,那么对这个线程没有任何影响,线程会继续正常地执行下去;对于wait中等待notify/notifyAll唤醒的线程(包括sleep,join),其实这个线程已经"暂停"执行,由于它正在某一对象的休息室中,这时若是它的中断状态被改变,那么它就会抛出异常。那么它与被notify/All唤醒的线程的区别是,正常唤醒的线程会继续执行wait下面的语句,而在wait中被中断的线程则将控制权交给了catch语句,一些正常的逻辑要被放到catch中来运行。

由于普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()可以响应中断。因此利用这一点来处理线程的死锁问题。案例代码以下:

package test;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable
{
	public static ReentrantLock lock1 = new ReentrantLock();
	public static ReentrantLock lock2 = new ReentrantLock();

	int lock;

	public Test(int lock)
	{
		this.lock = lock;
	}

	@Override
	public void run()
	{
		try
		{
			if (lock == 1)
			{
				lock1.lockInterruptibly();
				try
				{
					Thread.sleep(500);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
				lock2.lockInterruptibly();
			}
			else
			{
				lock2.lockInterruptibly();
				try
				{
					Thread.sleep(500);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
				lock1.lockInterruptibly();
			}
		}
		catch (Exception e)
		{
			// TODO: handle exception
			e.printStackTrace();
		}
		finally
		{
            //isHeldByCurrentThread()查询当前线程是否保持此锁
			if (lock1.isHeldByCurrentThread())
			{
				lock1.unlock();
			}
			if (lock2.isHeldByCurrentThread())
			{
				lock2.unlock();
			}
			System.out.println(Thread.currentThread().getId() + ":线程退出");
		}
	}

	public static void main(String[] args) throws InterruptedException
	{
		Test t1 = new Test(1);
		Test t2 = new Test(2);
		Thread thread1 = new Thread(t1);
		Thread thread2 = new Thread(t2);
		thread1.start();
		thread2.start();
		Thread.sleep(1000);
		DeadlockChecker.check();
	}

	static class DeadlockChecker
	{
		//ThreadMXBean是Java 虚拟机线程系统的管理接口。实现此接口的实例是一个 MXBean,能够经过调用 ManagementFactory.getThreadMXBean() 方法或从平台 MBeanServer 方法得到它。
		private final static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
		final static Runnable deadlockChecker = new Runnable()
		{
			@Override
			public void run()
			{
				while (true)
				{
					//线程 ID 是一个经过调用线程的 Thread.getId() 方法返回的 long 型正值。线程 ID 在其生存期间是惟一的。线程终止时,该线程 ID 能够被从新使用。
					//findDeadlockedThreads()线程的死锁检测方法,返回装有死锁线程ID的long型数组
					long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
					if (deadlockedThreadIds != null)
					{
						//getThreadInfo(long[], boolean, boolean)用来获取线程堆栈的跟踪(各类状态信息的复制),这里的目的是为了获取全部处于活动状态线程的线程信息
						ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
						//两层循环为了确保被中断的线程是处于活动状态中的线程
						for (Thread t : Thread.getAllStackTraces().keySet())
						{
							for (int i = 0; i < threadInfos.length; i++)
							{
								if(t.getId() == threadInfos[i].getThreadId())
								{
									t.interrupt();
								}
							}
						}
					}
					try
					{
						Thread.sleep(5000);
					}
					catch (Exception e)
					{
						// TODO: handle exception
					}
				}

			}
		};
		
		public static void check()
		{
			Thread t = new Thread(deadlockChecker);
			//设置为守护线程,做用:当守护线程是jvm中仅剩的线程时它会自动离开。垃圾回收GC就是一个典型的守护线程。
			//注:当全部的常规线程运行完毕之后,守护线程无论运行到哪里,java虚拟机都会退出运行,因此守护线程中最好不要写一些会影响程序的业务。
			t.setDaemon(true);
			t.start();
		}
	}

}

可限时:超时不能得到锁,就返回false,不会永久等待构成死锁

使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。

举个例子来讲明下可限时:

package test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable
{
	public static ReentrantLock lock = new ReentrantLock();

	@Override
	public void run()
	{
		try
		{
			if (lock.tryLock(5, TimeUnit.SECONDS))
			{
				Thread.sleep(6000);
			}
			else
			{
				System.out.println("get lock failed");
			}
		}
		catch (Exception e)
		{
		}
		finally
		{
			if (lock.isHeldByCurrentThread())
			{
				lock.unlock();
			}
		}
	}
	
	public static void main(String[] args)
	{
		Test t = new Test();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}

}

使用两个线程来争夺一把锁,当某个线程得到锁后,sleep6秒,每一个线程都只尝试5秒去得到锁。

因此一定有一个线程没法得到锁。没法得到后就直接退出了。

输出:

get lock failed

公平锁

public ReentrantLock(boolean fair) 

public static ReentrantLock fairLock = new ReentrantLock(true);

通常意义上的锁是不公平的,不必定先来的线程能先获得锁,后来的线程就后获得锁。不公平的锁可能会产生饥饿现象。

公平锁的意思就是,这个锁能保证线程是先来的先获得锁。虽然公平锁不会产生饥饿现象,可是公平锁的性能会比非公平锁差不少。

5.使用阻塞队列实现线程同步

使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 
本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 
LinkedBlockingQueue<E>是一个基于已链接节点的,范围任意的blocking queue。 
队列是先进先出的顺序(FIFO)

LinkedBlockingQueue 类经常使用方法 
    LinkedBlockingQueue() : 建立一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,若是队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,若是队列空则阻塞 

package test;

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
 * 当队列中没有元素时take会被阻塞
 * 当队列慢了时put会被阻塞
 */
public class BlockingSynchronizedThread {
    /**
     * 定义一个阻塞队列用来存储生产出来的商品
     */
    private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(10);
    /**
     * 定义生产商品个数
     */
    private static final int size = 20;
    /**
     * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
     */
    private int flag = 0;

    private class LinkBlockThread implements Runnable {
        @Override
        public void run() {
            int new_flag = flag++;
            System.out.println("启动线程 " + new_flag);
            if (new_flag == 0) {
                for (int i = 0; i < size; i++) {
                    int b = new Random().nextInt(255);
                    System.out.println("生产商品:" + b + "号");
                    try {
                        queue.put(b);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("仓库中还有商品:" + queue.size() + "个");
                    try {
                        Thread.sleep(100);//生产速度大于消费速度
                        //Thread.sleep(5000);消费速度大于生产速度
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } else {
                for (int i = 0; i < size / 2; i++) {
                    try {
                        int n = queue.take();
                        System.out.println("消费者买去了" + n + "号商品");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("仓库中还有商品:" + queue.size() + "个");
                    try {
                        Thread.sleep(5000);
                        //Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
        LinkBlockThread lbt = bst.new LinkBlockThread();
        Thread thread1 = new Thread(lbt);
        Thread thread2 = new Thread(lbt);
        thread1.start();
        thread2.start();

    }

}

注:BlockingQueue<E>定义了阻塞队列的经常使用方法,尤为是三种添加元素的方法,咱们要多加注意,当队列满时:

  add()方法会抛出异常

  offer()方法返回false

  put()方法会阻塞

6.AtomicInteger

AtomicInteger,一个提供原子操做的Integer的类。在Java语言中,++i和i++操做并非线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则经过一种线程安全的加减操做接口。

AtomicInteger提供的接口。

//获取当前的值

public final int get()

//取当前的值,并设置新的值

 public final int getAndSet(int newValue)

//获取当前的值,并自增

 public final int getAndIncrement()

//获取当前的值,并自减

public final int getAndDecrement()

//获取当前的值,并加上预期的值

public final int getAndAdd(int delta)

synchronized的方法和一个AtomicInteger的方法来进行测试,直观的感觉下性能上的差别

package test;

import java.util.concurrent.atomic.AtomicInteger;  
public class AtomicIntegerCompareTest {  
    private int value;  
      
    public AtomicIntegerCompareTest(int value){  
        this.value = value;  
    }  
      
    public synchronized int increase(){  
        return value++;  
    }  
      
    public static void main(String args[]){  
        long start = System.currentTimeMillis();  
          
        AtomicIntegerCompareTest test = new AtomicIntegerCompareTest(0);  
        for( int i=0;i< 1000000;i++){  
            test.increase();  
        }  
        long end = System.currentTimeMillis();  
        System.out.println("time elapse:"+(end -start));  
          
        long start1 = System.currentTimeMillis();  
          
        AtomicInteger atomic = new AtomicInteger(0);  
          
        for( int i=0;i< 1000000;i++){  
            atomic.incrementAndGet();  
        }  
        long end1 = System.currentTimeMillis();  
        System.out.println("time elapse:"+(end1 -start1) );  
          
          /*运行结果:time elapse:19
            time elapse:9
  AtomicInteger拥有更高效的性能
*/
    }  
}

4)一旦一个线程进入一个实例的任何同步方法,别的线程将不能
进入该同一实例的其它同步方法,可是该实例的非同步方法仍然可以被调用

Demo1:

package test;

public class SynTest {
	//非同步
	static void method(Thread thread){
		System.out.println("begin "+thread.getName());
		try{
			Thread.sleep(2000);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		System.out.println("end "+thread.getName());
	}
	 
	//同步方式一:同步方法
	synchronized static void method1(Thread thread){//这个方法是同步的方法,每次只有一个线程能够进来
		System.out.println("begin "+thread.getName());
		try{
			Thread.sleep(2000);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		System.out.println("end "+thread.getName());
	}
	 
	//同步方式二:同步代码块
	static void method2(Thread thread){
		synchronized(SynTest.class) {
			System.out.println("begin "+thread.getName());
			try{
				Thread.sleep(2000);
			}catch(Exception ex){
				ex.printStackTrace();
			}
			System.out.println("end "+thread.getName());
		}
	}
	 
	 
	//同步方式三:使用同步对象锁
	private static Object _lock1=new Object();
		private static byte _lock2[]={};//听说,此锁更可提升性能。源于:锁的对象越小越好
		static void method3(Thread thread){
			synchronized(_lock1) {
				System.out.println("begin "+thread.getName());
				try{
					Thread.sleep(2000);
				}catch(Exception ex){
					ex.printStackTrace();
				}
			System.out.println("end "+thread.getName());
		}
	}
	 
	public static void main(String[] args){
		//启动3个线程,这里用了匿名类
		for(int i=0;i<3;i++){
			new Thread(){
				public void run(){
					//method(this);
					method1(this);
					method2(this);
					method3(this);
				}
			}.start();
		}
	}
}

 Demo2:

package test;

public class SynTest2 {
	public static void main(String[] args){
		Callme target=new Callme();
		Caller ob1=new Caller(target,"Hello");
		Caller ob2=new Caller(target,"Synchronized");
		Caller ob3=new Caller(target,"World");
	}
}
package test;

public class Callme {
	synchronized void test(){
		System.out.println("测试是不是:一旦一个线程进入一个实例的任何同步方法,别的线程将不能进入该同一实例的其它同步方法,可是该实例的非同步方法仍然可以被调用");
	}
	 
		void nonsynCall(String msg){
			System.out.println("["+msg);
			System.out.println("]");
		}
	 
		synchronized void synCall(String msg){
			System.out.println("["+msg);
			System.out.println("]");
		}
}
	 
class Caller implements Runnable{
	String msg;
	Callme target;
	Thread t;
	 
	Caller(Callme target,String msg){
		this.target=target;
		this.msg=msg;
		t=new Thread(this);
		t.start();
	}
	 
	public void run() {
		// TODO Auto-generated method stub
		//target.nonsynCall(msg);
		target.synCall(msg);
		target.test();
	}
}
相关文章
相关标签/搜索