java多线程(一)

本文介绍Java中实现多线程的两种方式及守护线程的概念,深入探讨synchronized关键字、Lock机制、读写锁、Condition类在并发控制中的应用,并通过实例展示如何在多线程环境下实现资源安全共享。

一直想写博客,但总是想做到完美,结果就一直拖着。先试着写一篇吧,记录下看过的一些东西,对以后应该会有些帮助吧!

学过java的人应该都会知道,实现java多线程一般是两种方式:

1.继承Thread类,重写父类run方法;

2.实现Runnable接口。

下面是Thread类中的run方法(其中target是一个Runnable对象)

public void run() {
	if (target != null) {
	    target.run();
	}
    }
可以看出,如果Thread的run方法就是判断是否传入了Runnable对象,如果传入,则直接调用Runnable接口的run方法(当然这个抽象方法肯定是要我们重写的),如果没有传入,则不进行任何操作。

守护线程:

守护线程的优先级很低,一般是没有其他线程运行的时候,守护线程才会运行。守护线程一般是无限循环的,以等待服务请求或者执行线程的任务,最典型的例子就是java的gc。


import java.util.Date;
import java.util.Deque;

public class CleanerTask extends Thread{
	private Deque<Event> deque;
	
	public CleanerTask(Deque<Event> deque) {
		this.deque = deque;
		setDaemon(true);
	}

	@Override
	public void run() {
		while(true){
			Date date = new Date();
			clean(date);
		}
	}

	private void clean(Date date) {
		long diff;
		boolean delete;
		if(deque.size() == 0){
			return;
		}
		delete = false;
		do{
			Event e = deque.getLast();
			diff = date.getTime() - e.getDate().getTime();
			if(diff > 10000){
				System.out.println("delete "+ e.getEvent());
				deque.removeLast();
				delete = true;
				break;
			}
		}while(diff > 10000);
		if(delete){
			System.out.println("deque size : "+deque.size());
		}
		
	}
}
守护线程的小例子,超过循环清除超过10s的事件。


synchronized

对于了解过java并发的人来说,synchronized真是再熟悉不过了,synchronized关键字用来控制一个方法或一段代码的并发访问,如果一段代码使用了synchronized声明,那么只能有一个线程访问它。每一个用synchronized关键字声明的方法都是临界区,在java中,同一个对象的临界区,在同一时间内只有一个允许被访问。此外需要注意的是,两个线程可以同时访问一个对象不同的synchronized方法,其中一个是静态方法,另一个是非静态方法,如果两个方法修改了相同的变量, 那么就有可能出现数据不一致的情况。


public class Account {
	private double balance;
	
	public synchronized void addAmount(double amount){
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		tmp += amount;
		balance = tmp;
	}
	
	public synchronized void subAmount(double amount){
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		tmp -= amount;
		balance = tmp;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	
	
}
上段代码模拟银行存款和取款的过程,其中存取的方法上都加上了synchronized关键字,保证存取款的结果正确。


在synchronized中,可以调用wait()、notify()、notifyAll()方法,wait方法被一个线程调用时,jvm将这个线程置入休眠,并且释放控制这个同步代码块的对象,同事允许其他线程执行这个对象控制的其他同步代码块。而为了唤醒这个线程,必须在这个对象控制的某个同步代码块中调用notify()或notifyAll()方法。


import java.util.Date;
import java.util.LinkedList;
import java.util.List;

public class EventStorage {
	private int maxSize;
	private List<Date> storage;
	
	public EventStorage() {
		maxSize = 10;
		storage = new LinkedList<Date>();
	}
	
	public synchronized void set(){
		while(storage.size() == maxSize){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		storage.add(new Date());
		System.out.println("Set : "+storage.size());
		notifyAll();
	}
	
	public synchronized void get(){
		while(storage.size() == 0){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("Get : "+storage.size()+" : "+((LinkedList<Date>)storage).poll());
		notifyAll();
	}
}
这段代码模拟了生产者-消费者中的缓冲区,当缓冲区容量已满时,线程将会休眠,当检测到缓冲区有空间时,又会唤醒所有在休眠中的线程,同样的,当缓冲区为空时,调用get()方法的线程也会休眠,缓冲区不为空时,重新被唤醒执行。

Lock

作为同步代码的另一种机制,Lock比synchronized更强大、更灵活。Lock接口允许分离读写操作,允许多个读线程和一个写线程,而且具有更好的性能。

读写锁是Lock机制最大的特点,使用读操作锁时,可以允许多个线程同时访问,但是使用写操作锁时,只允许一个线程运行。


import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class PricesInfo {
	private double price1;
	private double price2;
	private ReadWriteLock lock;
	public PricesInfo() {
		price1 = 1.0;
		price2 = 2.0;
		lock = new ReentrantReadWriteLock();
	}
	
	public double getPrice1(){
		lock.readLock().lock();
		double value = price1;
		lock.readLock().unlock();
		return value;
	}
	
	public double getPrice2(){
		lock.readLock().lock();
		double value = price2;
		lock.readLock().unlock();
		return value;
	}
	
	public void setPrices(double price1,double price2){
		lock.writeLock().lock();
		this.price1 = price1;
		this.price2 = price2;
		lock.writeLock().unlock();
	}
}
这是一个获取和改变price的类,获取price时使用了读锁,改变price时使用了写锁。


ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,默认值为false,即非公平模式,锁在选择一个等待的线程时,没有任何约束。当设置为true时,锁将选择等待时间最长的线程。


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

public class PrintQueue {
	private Lock queueLock = new ReentrantLock(true);
	
	public void printJob(Object document){
		queueLock.lock();
		try{
			Long duration = (long)(Math.random()*10000);
			System.out.println(Thread.currentThread().getName()+":PrintQueue: Printing a Job during "+(duration/1000)+" seconds");
			Thread.sleep(duration);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			queueLock.unlock();
		}
		queueLock.lock();
		try{
			Long duration = (long)(Math.random()*10000);
			System.out.println(Thread.currentThread().getName()+":PrintQueue: Printing a Job during "+(duration/1000)+" seconds");
			Thread.sleep(duration);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			queueLock.unlock();
		}
	}
}
public class Job implements Runnable{
	private PrintQueue printQueue;
	public Job(PrintQueue printQueue) {
		super();
		this.printQueue = printQueue;
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+": Going to print a document");
		printQueue.printJob(new Object());
		System.out.println(Thread.currentThread().getName()+": The document has been printed");
	}

}
public class Main {

	public static void main(String[] args) {
		PrintQueue printQueue = new PrintQueue();
		Thread thread[] = new Thread[10];
		for(int i=0; i<10; i++){
			thread[i] = new Thread(new Job(printQueue),"Thread "+ i);
		}
		for(int i=0; i<10; i++){
			thread[i].start();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}
console输出:
Thread 0: Going to print a document
Thread 0:PrintQueue: Printing a Job during 3 seconds
Thread 1: Going to print a document
Thread 2: Going to print a document
Thread 3: Going to print a document
Thread 4: Going to print a document
Thread 5: Going to print a document
Thread 6: Going to print a document
Thread 7: Going to print a document
Thread 8: Going to print a document
Thread 9: Going to print a document
Thread 1:PrintQueue: Printing a Job during 9 seconds
Thread 2:PrintQueue: Printing a Job during 8 seconds
Thread 3:PrintQueue: Printing a Job during 0 seconds
Thread 4:PrintQueue: Printing a Job during 9 seconds
Thread 5:PrintQueue: Printing a Job during 9 seconds
Thread 6:PrintQueue: Printing a Job during 2 seconds
Thread 7:PrintQueue: Printing a Job during 9 seconds
Thread 8:PrintQueue: Printing a Job during 1 seconds
Thread 9:PrintQueue: Printing a Job during 5 seconds
Thread 0:PrintQueue: Printing a Job during 6 seconds
Thread 0: The document has been printed
Thread 1:PrintQueue: Printing a Job during 9 seconds
Thread 1: The document has been printed
Thread 2:PrintQueue: Printing a Job during 8 seconds
Thread 2: The document has been printed
Thread 3:PrintQueue: Printing a Job during 3 seconds
Thread 3: The document has been printed
Thread 4:PrintQueue: Printing a Job during 8 seconds
可以看出,锁获取线程是按创建的顺序来的,即公平模式。

Condition这个类很强大,提供了挂起线程和唤醒线程的机制,可以为多个线程建立不同的Condition


import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Buffer {
	private LinkedList<String> buffer;
	private int maxSize;
	private ReentrantLock lock;
	private Condition lines;
	private Condition space;
	private boolean pendingLines;
	
	public Buffer(int maxSize){
		this.maxSize = maxSize;
		buffer = new LinkedList<String>();
		lock = new ReentrantLock();
		lines = lock.newCondition();
		space = lock.newCondition();
		pendingLines = true;
	}
	
	public void insert(String line){
		lock.lock();
		try {
			while(buffer.size() == maxSize){
				space.await();
			}
			buffer.offer(line);
			System.out.println(Thread.currentThread().getName()+":Inserted Line:"+buffer.size());
			lines.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public String get(){
		String line = null;
		lock.lock();
		try {
			while((buffer.size() == 0) && (hasPendingLines())){
				lines.await();
			}
			if(hasPendingLines()){
				line = buffer.poll();
				System.out.println(Thread.currentThread().getName()+":Line Readed:"+buffer.size());
				space.signalAll();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
		return line;
	}
	
	public void setPendingLines(boolean pendingLines){
		this.pendingLines = pendingLines;
	}

	public boolean hasPendingLines() {
		return pendingLines || buffer.size() > 0;
	}
}
这个缓冲区同样有存取的方法,初始化时创建了读写Condition,insert方法里面,如果缓冲区已满,写线程将会休眠,而此时将会唤醒读线程,同样的如果缓冲区为空,读线程将会休眠,写线程将会被唤醒。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值