一直想写博客,但总是想做到完美,结果就一直拖着。先试着写一篇吧,记录下看过的一些东西,对以后应该会有些帮助吧!
学过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方法里面,如果缓冲区已满,写线程将会休眠,而此时将会唤醒读线程,同样的如果缓冲区为空,读线程将会休眠,写线程将会被唤醒。