Java多线程之传统线程机制

Posted by JackPeng on June 22, 2016

传统线程机制的回顾

创建线程的两种方式

1 在Thread子类覆盖的run方法中编写运行代码

 Thread t1 = new Thread()
 {
     @Override
     public void run() {
         System.out.println("run in thread run()");
     }
 }

2 在传递给Thread对象的Runnable对象的run方法中编写代码

 Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("run in runnable");
        }
    })

查看Thread的run方法源码:

 @Override
 public void run() {
    if (target != null) {
        target.run();
    }
 }

这个target就是构造函数中传入的runnable对象,因此如下代码的输出应该是?

new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("run in runnable");
        }
    }) {

        @Override
        public void run() {
            System.out.println("run in thread run()");
        }
    }.start();

答案应该是:run in thread run(),因为重写了run方法,也就不会执行target的run方法了。

两种创建线程的方法没有本质区别,但第二种方式更符合面向对象的思想。

线程状态转换

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程的同步互斥通信

同步与互斥

synchronized关键字:隐式锁

可作用于方法和代码块:

作用于方法,锁对象为this,当前实例;

作用于static方法,锁对象为当前类的Class对象;

作用于代码块,需要指定锁对象。

synchronized的作用:同一个锁对象的代码(方法和代码块),同一时间只能被一个线程执行,保证可见性和原子性。

线程通信wait和notify

使用synchronized的地方不一定使用wait和notify,但使用notify和wait有两个必要条件:

(1)wait和notify只有在synchronized关键字作用范围内才有效,否则无意义;

(2)wait和notify的调用对象必须是锁对象,也就是synchronized当前使用的对象,否则无效,有可能跑出异常:illegalmonitorstateexception

以下为两个例子, 例子1:使用两个线程打印,子线程每次打印10个数字,主线程每次打印100个数字,如此往复50次:

public class TraditionalThreadCommunication {

/**
 * @param args
 */
public static void main(String[] args) {
	
	final Business business = new Business();
	new Thread(
			new Runnable() {
				
				@Override
				public void run() {
				
					for(int i=1;i<=50;i++){
						business.sub(i);
					}
					
				}
			}
	).start();
	
	for(int i=1;i<=50;i++){
		business.main(i);
	}
	
}

}
class Business {
  private boolean bShouldSub = true;
  public synchronized void sub(int i){
	  while(!bShouldSub){
		  try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	  }
		for(int j=1;j<=10;j++){
			System.out.println("sub thread sequence of " + j + ",loop of " + i);
		}
	  bShouldSub = false;
	  this.notify();
  }
  
  public synchronized void main(int i){
	  	while(bShouldSub){
	  		try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	  	}
		for(int j=1;j<=100;j++){
			System.out.println("main thread sequence of " + j + ",loop of " + i);
		}
		bShouldSub = true;
		this.notify();
  }
}

例子2 使用wait和notify实现生产者消费者阻塞队列,队列长度为5:

public static void main(String[] args) {
    List<Integer> taskQueue = new ArrayList<Integer>();
    int MAX_CAPACITY = 5;
    Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
    Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
    tProducer.start();
    tConsumer.start();

}

//生产者
public class Producer implements Runnable {
private final List<Integer> taskQueue;
private final int MAX_CAPACITY;

public Producer(List<Integer> sharedQueue, int size) {
    this.taskQueue = sharedQueue;
    this.MAX_CAPACITY = size;
}

@Override
public void run() {
    int counter = 0;
    while (true) {
        try {
            produce(counter++);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

private void produce(int i) throws InterruptedException {
    synchronized (taskQueue) {
        while (taskQueue.size() == MAX_CAPACITY) {
            System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
        }

        Thread.sleep(1000);
        taskQueue.add(i);
        System.out.println("Produced: " + i);
        taskQueue.notifyAll();
    }
}

//消费者

public class Consumer implements Runnable {
private final List<Integer> taskQueue;

public Consumer(List<Integer> sharedQueue) {
    this.taskQueue = sharedQueue;
}

@Override
public void run() {
    while (true) {
        try {
            consume();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

private void consume() throws InterruptedException {
    synchronized (taskQueue) {
        while (taskQueue.isEmpty()) {
            System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
        }
        Thread.sleep(500);
        int i = (Integer) taskQueue.remove(0);
        System.out.println("Consumed: " + i);
        taskQueue.notify();
    }
}

例子1,两个线程会有规律的交叉打印,而例子2则在不同机器上有不同表现,依赖于cpu的调度,最关键的地方在于:例子1中wait的进入条件会随着每一轮的执行而改变一次,而例子2中,只有队列为空或满时才会进入wait,因此没有明显规律。

ThreadLocal实现线程范围的共享变量

ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

(1) 每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量.

(2) 一个ThreadLocal只能存储一个对象,如果需要存储多个对象,则可以用多个ThreadLocal对象,如果很多,则把这些对象封装到一个实体类中。

(3) ThreadLocal的应用场景:如android中的Looper就存储在ThreadLocal变量中,一个线程只能有一个looper。

   Looper.prepare()方法
   
   private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

(4) 测试代码 public class ThreadLocalTest {

private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
public static void main(String[] args) {
	for(int i=0;i<2;i++){
		new Thread(new Runnable(){
			@Override
			public void run() {
				int data = new Random().nextInt();
				System.out.println(Thread.currentThread().getName() 
						+ " has put data :" + data);
				x.set(data);
 /*					MyThreadScopeData myData = new MyThreadScopeData();
				myData.setName("name" + data);
				myData.setAge(data);
				myThreadScopeData.set(myData);*/
				MyThreadScopeData.getThreadInstance().setName("name" + data);
				MyThreadScopeData.getThreadInstance().setAge(data);
				new A().get();
				new B().get();
			}
		}).start();
	}
}

static class A{
	public void get(){
		int data = x.get();
		System.out.println("A from " + Thread.currentThread().getName() 
				+ " get data :" + data);
/*			MyThreadScopeData myData = myThreadScopeData.get();;
		System.out.println("A from " + Thread.currentThread().getName() 
				+ " getMyData: " + myData.getName() + "," +
				myData.getAge());*/
		MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
		System.out.println("A from " + Thread.currentThread().getName() 
				+ " getMyData: " + myData.getName() + "," +
				myData.getAge());
	}
}

static class B{
	public void get(){
		int data = x.get();			
		System.out.println("B from " + Thread.currentThread().getName() 
				+ " get data :" + data);
		MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
		System.out.println("B from " + Thread.currentThread().getName() 
				+ " getMyData: " + myData.getName() + "," +
				myData.getAge());			
	}		
} }

class MyThreadScopeData{
   private MyThreadScopeData(){}
    public static /*synchronized*/ MyThreadScopeData getThreadInstance(){
	    MyThreadScopeData instance = map.get();
	    if(instance == null){
		    instance = new MyThreadScopeData();
		    map.set(instance);
	    }
	    return instance;
    }
    //private static MyThreadScopeData instance = null;//new MyThreadScopeData();
   private static ThreadLocal<MyThreadScopeData> map = new  ThreadLocal<MyThreadScopeData>();

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;
}
}