Java多线程与并发

一、基础概念

1. 并发级别

并发级别分为阻塞和非阻塞;
非阻塞分为无障碍、无锁(CAS)、无等待(多线程同时读,无同时写)。

1)阻塞:当一个线程进入临界区后,其他线程必须等待。

2)无障碍:一种最弱的非阻塞调度,可自由出入临界区,无竞争时有限步骤完成操作,有竞争时回滚数据。

阻塞与非阻塞的无障碍比较:阻塞类似于悲观锁,它认为一起修改数据有可能把数据修改坏;而非阻塞类似于乐观锁,它任务大家一起修改数据未必会把数据修改坏,当它发现多个线程出现了数据竞争产生冲突,那么无障碍的调用方法会把这条数据回滚。

3)无锁:无锁既是无障碍的,又可保证一个线程可以胜出。
与无障碍相比,无障碍如果发现每次操作产生冲突,那么它不停地重试,这样无法保证有竞争时一定能完成操作。
而无锁保证了每次竞争都有一个线程可以胜出。下面是Java经典的无锁代码:
while (!atomicVar.compareAndSet(localVar, localVar+1))
{
localVar = atomicVar.get();
}

4)无等待:无锁的、要求所有的线程都必须在有限步内完成、无饥饿的。

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

无等待的典型案例:
如果多个线程只有读线程,没有写线程,那么就是无等待的。否则如果有写线程,每次多线程一起写的时候,就会造成有些线程“饥饿”,即因为写冲突而等待。

2. 线程:进程中的执行单元。进程的切换昂贵,而线程更轻量级,代价低。

二、关于线程

1. 线程状态图:

在这里插入图片描述

2. 终止线程的方式

Thread的stop方法(非静态): JDK已经deprecated这个方法了。因为它太暴力,会破坏原子性。例如:线程中先执行name = “abc”; 再执行age = 12; 但当执行完name=“abc”就被stop了,这样age还没有赋值12,会有问题。也就是说,stop执行后,什么时候真正终止线程不可控。

3. 线程中断

线程中断:在该线程完成任务之前,停止其正在进行的一切。接下来线程是死亡,还是等待新任务,还是继续运行,取决于代码怎么写。

要注意的是,Thread的interrupt方法(非静态)并不会中断一个正在执行的线程,即调用了interrupt方法后,线程仍会继续执行。interrupt方法被调用后,只会让isInterrupted方法返回true.

使用interrupt方法的例子:

public class ThreadTest {
	public static void main(String[] args) {
		T1 t = new T1();
		t.start();
		try {
			Thread.sleep(20);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t.interrupt();
		System.out.println("interrupted...");
	}
}

class T1 extends Thread {
	public void run() {
		while(true) {
			System.out.println("is running");
			if (Thread.currentThread().isInterrupted()) {
				System.out.println("aaaaaaa");
				break;
			}
			Thread.yield();
		}
	}
}

输入结果:

is running
is running
is running
is running
is running
aaaaaaa
interrupted...

但是,下面的方法可以将isInterrupted的状态重新置为false:
Thread.sleep(); Object.wait(); Thread的join方法(非静态)

例如,上述代码如果加入Thread.sleep()则永不会停止:

class T1 extends Thread {
	public void run() {
		while(true) {
			System.out.println("is running");
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (Thread.currentThread().isInterrupted()) {
				System.out.println("aaaaaaa");
				break;
			}
			Thread.yield();
		}
	}
}

执行到Thread.sleep时,会抛出Exception: runningjava.lang.InterruptedException: sleep interrupted
抛出异常后会将isInterrupted重新置为false.

4. 线程挂起

suspend和resume方法,都是被JDK废弃的方法。

suspend: 线程挂起,不释放锁。
resume: 继续执行被suspend的线程。

原因在于,suspend不释放锁,直到执行resume. 看下面的代码:

public class Test
{
    static Object u = new Object();
    static TestSuspendThread t1 = new TestSuspendThread("t1");
    static TestSuspendThread t2 = new TestSuspendThread("t2");
 
    public static class TestSuspendThread extends Thread
    {
        public TestSuspendThread(String name)
        {
            setName(name);
        }
 
        @Override
        public void run()
        {
            synchronized (u)
            {
                System.out.println("in " + getName());
                Thread.currentThread().suspend();
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        t2.resume();
        System.out.println("t2 resume");
    }
}

输出结果:

in t1
t2 resume
in t2

由于无法控制线程执行的顺序,如果t2.resume()在执行t2的suspend之前,就会发生死锁。

5. join和yield

yield是Thread类的static native方法,目的是把自己占有的CPU释放,然后和其他线程一起竞争(注意:yield的线程还是有可能再次竞争到CPU的,这跟sleep不同),在JDK说明文档中,说yield是个基本不会用到的方法,通常仅用于test或debug中。

join方法:确保该线程执行完,再执行下面的逻辑。
官网文档:Waits for this thread to die.

public class Test
{
	public volatile static int i = 0;
	 
    public static class AddThread extends Thread
    {
        @Override
        public void run()
        {
            for (i = 0; i < 10000000; i++)
                ;
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        AddThread at = new AddThread();
        at.start();
        at.join(); // 确保线程at执行完毕,在执行下面的逻辑。
        System.out.println(i);
    }
}

加了at.join()输入值为10000000,否则输出为0.

join的本质:

while(isActive()) {
	wait(0);
}

join也可以传递一个方法,表示只做有限期的等待,超过了这个时间,就自动被notifyAll。
例如,上面例子中如果改成at.join(2); 那么输入结果为: 171456.

javadoc上给的建议:不要使用wait, notify/notifyAll在线程实例上,因为JVM会自己调用,否则有可能与你的期望结果不符

6. 守护线程

守护线程是在后台完成的一些系统性任务,如垃圾回收线程,JIT线程等。
只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

通过这个代码把普通线程变成守护线程:
dt.setDaemon(true);

守护线程不会打印System.out.print

7. 线程优先级

当一个高优先级的线程和低优先级的线程共同争夺锁时,高优先级线程并不一定会得到锁,而仅是争夺到锁的概率大。

8. 线程的同步操作

通过Object对象的wait(), notify(), notifyAll()方法来实现线程间的通信。调用这三个方法时,必须在synchornized(obj){} 语句块中,就是必须要先获取对象锁才能调用,否则会抛出IllegalMonitorStateException的异常。

1)wait() : Object类的final native方法,将当前线程置于等待队列,直到被notify或中断。在调用wait()之前,线程必须要获得当前对象级别的锁。进入wait方法后,当前对象释放锁。在wait的线程被notify之后,该线程与其他线程一起竞争CPU。

2)notify() :执行notify方法时,也要在synchronized语句块中,首先要获取对象锁。如果有多个线程在wait,那么从中任选一个, 被notify的线程会等待获取对象的对象锁(notify后,当前线程不会马上释放对象锁,wait所在的线程也不能马上得到对象锁,而要等到程序退出synchronized代码块后,当前线程才能释放锁,wait所在的线程才能获取该对象锁)。

3)notifyAll() : 与notify不同的地方在于它会唤醒所有wait的线程。

总结:wait()会使当前线程进入该对象的等待池中,等待池中的线程不会去竞争对象的锁。
当线程被notify/notifyAll之后,被唤醒的线程便会进入该对象的锁池中,锁池中的对象会去竞争对象锁。

public class WaitNotifyTest2 {
	public static void main(String[] args) {
		Object obj = new Object();
		MyThread1 thread = new MyThread1(obj);
		Thread t = new Thread(thread, "A");
		Thread t2 = new Thread(thread, "B");
		t.start();
		t2.start();
	}
}

class MyThread1 implements Runnable{
	public Object obj;
	
	public MyThread1(Object obj) {
		this.obj = obj;
	}
	
	public static int index = 5;
	
	public void run() {
		while (index-- > 0) {
			synchronized(obj) {
				System.out.println("现在的线程是" + Thread.currentThread().getName() + ", 已经得到锁");
				obj.notify();
				System.out.println("线程" + Thread.currentThread().getName() + " Notify了别人");
				try {
					System.out.println("线程" + Thread.currentThread().getName() + " 释放锁");
					obj.wait();
					System.out.println("现在的线程是" + Thread.currentThread().getName() + ", 但还没得到锁");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("********第 " + index + " 轮循环结束");
		}
	}
}

输出结果:
现在的线程是A, 已经得到锁
线程A Notify了别人
线程A 释放锁
现在的线程是B, 已经得到锁
线程B Notify了别人
线程B 释放锁
现在的线程是A, 但还没得到锁
********第 3 轮循环结束
现在的线程是A, 已经得到锁
线程A Notify了别人
线程A 释放锁
现在的线程是B, 但还没得到锁
********第 2 轮循环结束
现在的线程是B, 已经得到锁
线程B Notify了别人
线程B 释放锁
现在的线程是A, 但还没得到锁
********第 1 轮循环结束
现在的线程是A, 已经得到锁
线程A Notify了别人
线程A 释放锁
现在的线程是B, 但还没得到锁
********第 0 轮循环结束

9. synchronized同步锁

synchronized可修饰:
1)代码块:作用的对象是调用这个代码块的对象。
2)方法:作用的对象是调用这个方法的对象。
3)静态方法:作用的对象是这个类的所有对象。
4)类:作用的对象是这个类的所有对象。

例1:synchronized(this){}是对当前对象加锁

class SyncThread implements Runnable {
   private static int count;
 
   public SyncThread() {
      count = 0;
   }
 
   public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }
 
   public int getCount() {
      return count;
   }
}
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

这样会锁住count, 输出结果为:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();

这样锁不住count, 输出结果为:

SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9

例2:synchronized对方法加锁,仍锁定整个对象,但如果另一个线程访问count时不用synchronized, 则仍可访问count。

class Counter implements Runnable {
	private int count;

	public Counter() {
		count = 0;
	}

	public void countAdd() {
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				try {
					System.out.println(Thread.currentThread().getName() + ":" + (count++));
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	// 非synchronized代码块,未对count进行写操作,所以可以不用synchronized
	public void printCount() {
		for (int i = 0; i < 5; i++) {
			try {
				System.out.println(Thread.currentThread().getName() + " count:" + count);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void run() {
		String threadName = Thread.currentThread().getName();
		if (threadName.equals("A")) {
			countAdd();
		} else if (threadName.equals("B")) {
			printCount();
		}
	}

	public static void main(String[] args) {
		Counter counter = new Counter();
		Thread thread1 = new Thread(counter, "A");
		Thread thread2 = new Thread(counter, "B");
		thread1.start();
		thread2.start();
	}
}

countAdd方法等价于:

public synchronized void countAdd() {
	for (int i = 0; i < 5; i++) {
		try {
			System.out.println(Thread.currentThread().getName() + ":" + (count++));
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

这样调用后

Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();

输出结果为:

B count:1
A:0
B count:1
A:1
B count:2
A:2
A:3
B count:3
A:4
B count:5

例3:锁定一个对象

/**
 * 银行账户类
 */
class Account {
   String name;
   float amount;
 
   public Account(String name, float amount) {
      this.name = name;
      this.amount = amount;
   }
   //存钱
   public  void deposit(float amt) {
      amount += amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //取钱
   public  void withdraw(float amt) {
      amount -= amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
 
   public float getBalance() {
      return amount;
   }
}
 
/**
 * 账户操作类
 */
class AccountOperator implements Runnable{
   private Account account;
   public AccountOperator(Account account) {
      this.account = account;
   }
 
   public void run() {
      synchronized (account) {
         account.deposit(500);
         account.withdraw(500);
         System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
      }
   }
}

调用代码为:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
 
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
   threads[i] = new Thread(accountOperator, "Thread" + i);
   threads[i].start();
}

结果为:

Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0

可见,Account的实例被锁定了,存钱+取钱为原子操作。

例4. synchronized修饰静态方法:为多个实例所共享。
class SyncThread implements Runnable {
   private static int count;
 
   public SyncThread() {
      count = 0;
   }
 
   public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
 
   public synchronized void run() {
      method();
   }
}

这样调用:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

输出结果为:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

例5. 修饰一个类

class SyncThread implements Runnable {
   private static int count;
 
   public SyncThread() {
      count = 0;
   }
 
   public static void method() {
      synchronized(SyncThread.class) {
         for (int i = 0; i < 5; i ++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }
 
   public synchronized void run() {
      method();
   }
}

执行效果同例4.

synchronized关键字总结:

无论synchronized方法修饰方法还是对象,如果它作用的对象是非静态的,那么它取得的锁是对象;如果作用的对象是静态的,则它取得的锁是针对类,即该类的所有的对象公用这个锁。
一个对象只有一个锁与之关联。
同步的系统开销很大,且可能造成死锁,所以要尽量避免使用线程同步。

三、Java内存模型与线程安全

1. 原子性:指一个操作是不可中断的。即使在多个线程一起执行的时候,一个线程的操作一旦开始,就不会被其他线程干扰。

CPU的指令都是原子操作,但我们写的代码就不一定是原子操作了。

比如i++, 就不是原子操作,实际上分三步:读取i; 进行+1; 赋值给i.
再比如在32位的JVM上去读取64位的Long,也不是原子操作;但读取32位的int就是原子操作。

2. 有序性:在多个线程并发执行时,会出现乱序。

3. 可见性:指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。

四、无锁类的原理

1. CAS算法:

CAS包含三个参数:CAS(V, E, N)。V表示要更新的变量;E表示预期值;N表示新值。
1)仅当要更新的变量等于预期值时,才把要更新的变量设置为新值。
2)如果要更新的值与预期值不符,说明有其他线程做了更新,则当前线程什么都不做。
最后,CAS返回当前V的真实值。
3)当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并更新成功,其余会失败。失败的线程不会被挂起,仅被告知失败,并且允许再次尝试。
基于这样的原理,CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

问题:CAS有没有可能在判断V和E相同之后,正要赋值时,切换了线程,更改了V值呢?答案是不会的,CAS的整个几步的操作工程都是一个原子操作,它是由一条CPU指令完成的。

2. 无锁的类:无锁的类比同步算法效率高很多

类1. AtomicInteger

public class AtomicInteger extends Number implements java.io.Serializable
// AtomicInteger的compareAndSet方法就是使用的该CAS原理:
/**
  * Atomically sets the value to the given updated value
  * if the current value {@code ==} the expected value.
  *
  * @param expect the expected value
  * @param update the new value
  * @return true if successful. False return indicates that
  * the actual value was not equal to the expected value.
  */
 public final boolean compareAndSet(int expect, int update) {
     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
 }

其中,unsafe.compareAndSwapInt(this, valueOffset, expect, update);的意思是,如果valueOffset与expect相同,那么再把update的值赋给valueOffset, 实际上valueOffset就是AtomicInteger的value.

那么,如果valueOffset的值不等于expect怎么办?AtomicInteger提供了重试的算法:

/**
  * Atomically increments by one the current value.
  *
  * @return the previous value
  */
 public final int getAndIncrement() {
     for (;;) {
         int current = get();
         int next = current + 1;
         if (compareAndSet(current, next))
             return current;
     }
 }

直到成功为止。

Unsafe是非安全操作,即不保证一定能执行成功。它负责底层的CAS操作。这是个非公开的API.

另外,AtomicBoolean, AtomicLong等,原理一致。

ABA问题

对于上述例子,特殊情况下有问题:即ABA问题
假设当前value=1, 当某线程int current = get()执行后,另一个线程将value变成了2,然后又一个线程将value变成了1,这时再切换回最早的线程,由于value仍等于1,所以还是允许CAS操作,大部分情况下,这种情形是可接受的。对于不可接受时,可以采用AtomicStampedReference类,通过时间戳来判断该value是否被修改过

类2. AtomicReference: 模板类,他可用于封装任意类型的数据,例如String, 可实现类似于AtomicInteger这样的无锁类.

public class Test
{ 
    public final static AtomicReference<String> atomicString = new AtomicReference<String>("hosee");
    public static void main(String[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            final int num = i;
            new Thread() {
                public void run() {
                    try
                    {
                        Thread.sleep(Math.abs((int)Math.random()*100));
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                    if (atomicString.compareAndSet("hosee", "ztk"))
                    {
                        System.out.println(Thread.currentThread().getId() + "Change value");
                    }else {
                        System.out.println(Thread.currentThread().getId() + "Failed");
                    }
                };
            }.start();
        }
    }
}

这样,就对类的成员变量String使用了无锁框架,当一个线程对其进行修改时,其他线程不能修改。

五、同步控制:锁

1. ReentrantLock:可重入锁。

例子:

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

	@Override
	public void run() {
		for (int j = 0; j < 10000000; j++) {
			lock.lock();
			try {
				i++;
			} finally {
				lock.unlock();
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Test test = new Test();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}
}

输出结果为:20000000

从性能上,ReentrantLock和synchronized不相上下。

1)可重入:单线程可以重复进入锁,但也要重复退出。
例如:

lock.lock();
lock.lock();
try {
	 i++;
  
}  finally {
 	lock.unlock();
 	lock.unlock();
}

ReentrantLock是可重入锁,意思是可以反复进入同一个锁,所以它有一个与锁相关的计数器,如果一个已经拥有锁的线程,再次加锁,那么计数器就+1,然后锁需要释放两次才能真正释放。这个过程与synchronized的嵌套类似,如果嵌套了两层synchronized, 只有在退出最外层的synchronized块时,才释放锁。

所以synchronnized也是可重入锁的一种。下面是可重入锁的示例:

public class Child extends Father implements Runnable {
	final static Child child = new Child();// 为了保证锁唯一

	public static void main(String[] args) {
		for (int i = 0; i < 50; i++) {
			new Thread(child).start();
		}
	}

	public synchronized void doSomething() {
		System.out.println("1child.doSomething()");
		doAnotherThing(); // 调用自己类中其他的synchronized方法
	}

	private synchronized void doAnotherThing() {
		super.doSomething(); // 调用父类的synchronized方法
		System.out.println("3child.doAnotherThing()");
	}

	@Override
	public void run() {
		child.doSomething();
	}
}

class Father {
	public synchronized void doSomething() {
		System.out.println("2father.doSomething()");
	}
}

输出结果:

1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
…

可以看出,可重入锁保证了顺序1,2,3.

可重入锁的好处:

  • 可中断。可通过lock.lockInterruptibly()来响应中断(interrupt)
  • 可限时:如果超时不能获得锁,可返回False,不会永久等待产生死锁。

例子:

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

当某个线程获得锁后,sleep 6秒,另外的线程尝试5秒,如果超时,就输出"get lock failed”然后退出了。

  • 公平锁:ReentrantLock可以通过下面的情况设置成公平锁:
public static ReentrantLock fairLock = new ReentrantLock(true);

公平锁是指锁对先来的线程先到先得,一般意义的锁都是不公平锁,公平锁性能要比不公平锁差很多。

2. Condition

Condition类是java.util.concurrent.locks包下的类,Condition的await()方法用于使当前线程等待,同时释放锁,在其他线程使用signal/signalAll方法时,被await()的线程会被唤醒重新获得锁并继续执行。
Condition的await()和signal()的简单例子:

public class Test implements Runnable
{
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
 
    @Override
    public void run()
    {
        try
        {
            lock.lock();
            System.out.println("await");
            condition.await();
            System.out.println("Thread is going on");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        Test t = new Test();
        Thread thread = new Thread(t);
        thread.start();
        Thread.sleep(2000);
 
        lock.lock();
        System.out.println("signal");
        condition.signal();
        lock.unlock();
    }
}

输出结果:

await
signal
Thread is going on

生产者、消费者例子:

class ProducerConsumer {
	final Lock lock = new ReentrantLock();
	final Condition producer = lock.newCondition();
	final Condition consumer = lock.newCondition();

	final Object[] items = new Object[100];
	int putptr, takeptr, count;

	public void put(Object x) {
		lock.lock();
		try {
			while (count == items.length) {
				System.out.println("producer await:" + Thread.currentThread().getName());
				producer.await();
			}
			items[putptr] = x;
			if (++putptr == items.length)
				putptr = 0;
			++count;
			System.out.println("count++:" + count + ", consumer signal producer:" + Thread.currentThread().getName());
			consumer.signal();
		} catch (Exception e) {
		} finally {
			lock.unlock();
		}
	}

	public Object take() {
		lock.lock();
		Object x = null;
		try {
			while (count == 0) {
				System.out.println("consumer await:" + Thread.currentThread().getName());
				consumer.await();
			}
			x = items[takeptr];
			if (++takeptr == items.length)
				takeptr = 0;
			--count;
			System.out.println("count--:" + count + ", producer signal consumer:" + Thread.currentThread().getName());
			producer.signal();
		} catch (Exception e) {

		} finally {
			lock.unlock();
		}
		return x;
	}

	public static void main(String[] args) throws InterruptedException {
		ProducerConsumer buffer = new ProducerConsumer();
		new Thread(new Runnable() {
			@Override
			public void run() {
				int i = 200;
				while (i-- > 0) {
					buffer.take();
				}
			}
		}).start();
		int j = 200;
		while (j-- > 0) {
			buffer.put(j);
		}

	}
}

结果:

consumer await:Thread-0
count++:1, consumer signal producer:main
count++:2, consumer signal producer:main
count++:3, consumer signal producer:main
count++:4, consumer signal producer:main
count++:5, consumer signal producer:main
count++:6, consumer signal producer:main
count++:7, consumer signal producer:main
count++:8, consumer signal producer:main
count++:9, consumer signal producer:main
count++:10, consumer signal producer:main
count++:11, consumer signal producer:main
count++:12, consumer signal producer:main
count++:13, consumer signal producer:main
count--:12, producer signal consumer:Thread-0
count--:11, producer signal consumer:Thread-0
count--:10, producer signal consumer:Thread-0
count--:9, producer signal consumer:Thread-0
count--:8, producer signal consumer:Thread-0
count--:7, producer signal consumer:Thread-0
count--:6, producer signal consumer:Thread-0
count--:5, producer signal consumer:Thread-0
count--:4, producer signal consumer:Thread-0
count--:3, producer signal consumer:Thread-0
count--:2, producer signal consumer:Thread-0
count--:1, producer signal consumer:Thread-0
count--:0, producer signal consumer:Thread-0
consumer await:Thread-0
count++:1, consumer signal producer:main
count++:2, consumer signal producer:main
count++:3, consumer signal producer:main
count++:4, consumer signal producer:main
count++:5, consumer signal producer:main
count++:6, consumer signal producer:main
count++:7, consumer signal producer:main
count++:8, consumer signal producer:main
count++:9, consumer signal producer:main
count++:10, consumer signal producer:main
count++:11, consumer signal producer:main
count++:12, consumer signal producer:main
count++:13, consumer signal producer:main
count++:14, consumer signal producer:main
count++:15, consumer signal producer:main
count++:16, consumer signal producer:main
count++:17, consumer signal producer:main
count++:18, consumer signal producer:main
count++:19, consumer signal producer:main
count++:20, consumer signal producer:main
count++:21, consumer signal producer:main
count++:22, consumer signal producer:main
count++:23, consumer signal producer:main
count++:24, consumer signal producer:main
count++:25, consumer signal producer:main
count++:26, consumer signal producer:main
count++:27, consumer signal producer:main
count++:28, consumer signal producer:main
count++:29, consumer signal producer:main
count++:30, consumer signal producer:main
count++:31, consumer signal producer:main
count++:32, consumer signal producer:main
count++:33, consumer signal producer:main
count++:34, consumer signal producer:main
count++:35, consumer signal producer:main
count++:36, consumer signal producer:main
count++:37, consumer signal producer:main
count++:38, consumer signal producer:main
count++:39, consumer signal producer:main
count++:40, consumer signal producer:main
count++:41, consumer signal producer:main
count++:42, consumer signal producer:main
count++:43, consumer signal producer:main
count++:44, consumer signal producer:main
count++:45, consumer signal producer:main
count++:46, consumer signal producer:main
count++:47, consumer signal producer:main
count++:48, consumer signal producer:main
count++:49, consumer signal producer:main
count++:50, consumer signal producer:main
count++:51, consumer signal producer:main
count++:52, consumer signal producer:main
count++:53, consumer signal producer:main
count++:54, consumer signal producer:main
count++:55, consumer signal producer:main
count++:56, consumer signal producer:main
count++:57, consumer signal producer:main
count++:58, consumer signal producer:main
count++:59, consumer signal producer:main
count++:60, consumer signal producer:main
count++:61, consumer signal producer:main
count++:62, consumer signal producer:main
count++:63, consumer signal producer:main
count++:64, consumer signal producer:main
count++:65, consumer signal producer:main
count++:66, consumer signal producer:main
count++:67, consumer signal producer:main
count++:68, consumer signal producer:main
count++:69, consumer signal producer:main
count++:70, consumer signal producer:main
count++:71, consumer signal producer:main
count++:72, consumer signal producer:main
count++:73, consumer signal producer:main
count++:74, consumer signal producer:main
count++:75, consumer signal producer:main
count++:76, consumer signal producer:main
count++:77, consumer signal producer:main
count++:78, consumer signal producer:main
count++:79, consumer signal producer:main
count++:80, consumer signal producer:main
count++:81, consumer signal producer:main
count++:82, consumer signal producer:main
count++:83, consumer signal producer:main
count++:84, consumer signal producer:main
count++:85, consumer signal producer:main
count++:86, consumer signal producer:main
count++:87, consumer signal producer:main
count++:88, consumer signal producer:main
count++:89, consumer signal producer:main
count++:90, consumer signal producer:main
count++:91, consumer signal producer:main
count++:92, consumer signal producer:main
count++:93, consumer signal producer:main
count++:94, consumer signal producer:main
count++:95, consumer signal producer:main
count++:96, consumer signal producer:main
count++:97, consumer signal producer:main
count++:98, consumer signal producer:main
count++:99, consumer signal producer:main
count++:100, consumer signal producer:main
producer await:main
count--:99, producer signal consumer:Thread-0
count--:98, producer signal consumer:Thread-0
count--:97, producer signal consumer:Thread-0
count--:96, producer signal consumer:Thread-0
count--:95, producer signal consumer:Thread-0
count--:94, producer signal consumer:Thread-0
count--:93, producer signal consumer:Thread-0
count--:92, producer signal consumer:Thread-0
count--:91, producer signal consumer:Thread-0
count--:90, producer signal consumer:Thread-0
count--:89, producer signal consumer:Thread-0
count--:88, producer signal consumer:Thread-0
count--:87, producer signal consumer:Thread-0
count--:86, producer signal consumer:Thread-0
count--:85, producer signal consumer:Thread-0
count--:84, producer signal consumer:Thread-0
count--:83, producer signal consumer:Thread-0
count--:82, producer signal consumer:Thread-0
count--:81, producer signal consumer:Thread-0
count--:80, producer signal consumer:Thread-0
count--:79, producer signal consumer:Thread-0
count--:78, producer signal consumer:Thread-0
count--:77, producer signal consumer:Thread-0
count--:76, producer signal consumer:Thread-0
count--:75, producer signal consumer:Thread-0
count--:74, producer signal consumer:Thread-0
count--:73, producer signal consumer:Thread-0
count--:72, producer signal consumer:Thread-0
count--:71, producer signal consumer:Thread-0
count--:70, producer signal consumer:Thread-0
count--:69, producer signal consumer:Thread-0
count--:68, producer signal consumer:Thread-0
count--:67, producer signal consumer:Thread-0
count--:66, producer signal consumer:Thread-0
count--:65, producer signal consumer:Thread-0
count--:64, producer signal consumer:Thread-0
count--:63, producer signal consumer:Thread-0
count--:62, producer signal consumer:Thread-0
count--:61, producer signal consumer:Thread-0
count--:60, producer signal consumer:Thread-0
count--:59, producer signal consumer:Thread-0
count--:58, producer signal consumer:Thread-0
count--:57, producer signal consumer:Thread-0
count--:56, producer signal consumer:Thread-0
count--:55, producer signal consumer:Thread-0
count--:54, producer signal consumer:Thread-0
count--:53, producer signal consumer:Thread-0
count--:52, producer signal consumer:Thread-0
count--:51, producer signal consumer:Thread-0
count--:50, producer signal consumer:Thread-0
count--:49, producer signal consumer:Thread-0
count--:48, producer signal consumer:Thread-0
count--:47, producer signal consumer:Thread-0
count--:46, producer signal consumer:Thread-0
count--:45, producer signal consumer:Thread-0
count--:44, producer signal consumer:Thread-0
count--:43, producer signal consumer:Thread-0
count--:42, producer signal consumer:Thread-0
count--:41, producer signal consumer:Thread-0
count--:40, producer signal consumer:Thread-0
count--:39, producer signal consumer:Thread-0
count--:38, producer signal consumer:Thread-0
count--:37, producer signal consumer:Thread-0
count--:36, producer signal consumer:Thread-0
count--:35, producer signal consumer:Thread-0
count--:34, producer signal consumer:Thread-0
count--:33, producer signal consumer:Thread-0
count--:32, producer signal consumer:Thread-0
count--:31, producer signal consumer:Thread-0
count--:30, producer signal consumer:Thread-0
count--:29, producer signal consumer:Thread-0
count--:28, producer signal consumer:Thread-0
count--:27, producer signal consumer:Thread-0
count--:26, producer signal consumer:Thread-0
count--:25, producer signal consumer:Thread-0
count--:24, producer signal consumer:Thread-0
count--:23, producer signal consumer:Thread-0
count--:22, producer signal consumer:Thread-0
count--:21, producer signal consumer:Thread-0
count--:20, producer signal consumer:Thread-0
count--:19, producer signal consumer:Thread-0
count--:18, producer signal consumer:Thread-0
count--:17, producer signal consumer:Thread-0
count--:16, producer signal consumer:Thread-0
count--:15, producer signal consumer:Thread-0
count--:14, producer signal consumer:Thread-0
count--:13, producer signal consumer:Thread-0
count--:12, producer signal consumer:Thread-0
count--:11, producer signal consumer:Thread-0
count--:10, producer signal consumer:Thread-0
count--:9, producer signal consumer:Thread-0
count--:8, producer signal consumer:Thread-0
count--:7, producer signal consumer:Thread-0
count--:6, producer signal consumer:Thread-0
count--:5, producer signal consumer:Thread-0
count--:4, producer signal consumer:Thread-0
count--:3, producer signal consumer:Thread-0
count--:2, producer signal consumer:Thread-0
count--:1, producer signal consumer:Thread-0
count--:0, producer signal consumer:Thread-0
consumer await:Thread-0
count++:1, consumer signal producer:main
count++:2, consumer signal producer:main
count++:3, consumer signal producer:main
count++:4, consumer signal producer:main
count++:5, consumer signal producer:main
count++:6, consumer signal producer:main
count++:7, consumer signal producer:main
count++:8, consumer signal producer:main
count++:9, consumer signal producer:main
count++:10, consumer signal producer:main
count++:11, consumer signal producer:main
count++:12, consumer signal producer:main
count++:13, consumer signal producer:main
count++:14, consumer signal producer:main
count++:15, consumer signal producer:main
count++:16, consumer signal producer:main
count++:17, consumer signal producer:main
count++:18, consumer signal producer:main
count++:19, consumer signal producer:main
count++:20, consumer signal producer:main
count++:21, consumer signal producer:main
count++:22, consumer signal producer:main
count++:23, consumer signal producer:main
count++:24, consumer signal producer:main
count++:25, consumer signal producer:main
count++:26, consumer signal producer:main
count++:27, consumer signal producer:main
count++:28, consumer signal producer:main
count++:29, consumer signal producer:main
count++:30, consumer signal producer:main
count++:31, consumer signal producer:main
count++:32, consumer signal producer:main
count++:33, consumer signal producer:main
count++:34, consumer signal producer:main
count++:35, consumer signal producer:main
count++:36, consumer signal producer:main
count++:37, consumer signal producer:main
count++:38, consumer signal producer:main
count++:39, consumer signal producer:main
count++:40, consumer signal producer:main
count++:41, consumer signal producer:main
count++:42, consumer signal producer:main
count++:43, consumer signal producer:main
count++:44, consumer signal producer:main
count++:45, consumer signal producer:main
count++:46, consumer signal producer:main
count++:47, consumer signal producer:main
count++:48, consumer signal producer:main
count++:49, consumer signal producer:main
count++:50, consumer signal producer:main
count++:51, consumer signal producer:main
count++:52, consumer signal producer:main
count++:53, consumer signal producer:main
count++:54, consumer signal producer:main
count++:55, consumer signal producer:main
count++:56, consumer signal producer:main
count++:57, consumer signal producer:main
count++:58, consumer signal producer:main
count++:59, consumer signal producer:main
count++:60, consumer signal producer:main
count++:61, consumer signal producer:main
count++:62, consumer signal producer:main
count++:63, consumer signal producer:main
count++:64, consumer signal producer:main
count++:65, consumer signal producer:main
count++:66, consumer signal producer:main
count++:67, consumer signal producer:main
count++:68, consumer signal producer:main
count++:69, consumer signal producer:main
count++:70, consumer signal producer:main
count++:71, consumer signal producer:main
count++:72, consumer signal producer:main
count++:73, consumer signal producer:main
count++:74, consumer signal producer:main
count++:75, consumer signal producer:main
count++:76, consumer signal producer:main
count++:77, consumer signal producer:main
count++:78, consumer signal producer:main
count++:79, consumer signal producer:main
count++:80, consumer signal producer:main
count++:81, consumer signal producer:main
count++:82, consumer signal producer:main
count++:83, consumer signal producer:main
count++:84, consumer signal producer:main
count++:85, consumer signal producer:main
count++:86, consumer signal producer:main
count++:87, consumer signal producer:main
count--:86, producer signal consumer:Thread-0
count--:85, producer signal consumer:Thread-0
count--:84, producer signal consumer:Thread-0
count--:83, producer signal consumer:Thread-0
count--:82, producer signal consumer:Thread-0
count--:81, producer signal consumer:Thread-0
count--:80, producer signal consumer:Thread-0
count--:79, producer signal consumer:Thread-0
count--:78, producer signal consumer:Thread-0
count--:77, producer signal consumer:Thread-0
count--:76, producer signal consumer:Thread-0
count--:75, producer signal consumer:Thread-0
count--:74, producer signal consumer:Thread-0
count--:73, producer signal consumer:Thread-0
count--:72, producer signal consumer:Thread-0
count--:71, producer signal consumer:Thread-0
count--:70, producer signal consumer:Thread-0
count--:69, producer signal consumer:Thread-0
count--:68, producer signal consumer:Thread-0
count--:67, producer signal consumer:Thread-0
count--:66, producer signal consumer:Thread-0
count--:65, producer signal consumer:Thread-0
count--:64, producer signal consumer:Thread-0
count--:63, producer signal consumer:Thread-0
count--:62, producer signal consumer:Thread-0
count--:61, producer signal consumer:Thread-0
count--:60, producer signal consumer:Thread-0
count--:59, producer signal consumer:Thread-0
count--:58, producer signal consumer:Thread-0
count--:57, producer signal consumer:Thread-0
count--:56, producer signal consumer:Thread-0
count--:55, producer signal consumer:Thread-0
count--:54, producer signal consumer:Thread-0
count--:53, producer signal consumer:Thread-0
count--:52, producer signal consumer:Thread-0
count--:51, producer signal consumer:Thread-0
count--:50, producer signal consumer:Thread-0
count--:49, producer signal consumer:Thread-0
count--:48, producer signal consumer:Thread-0
count--:47, producer signal consumer:Thread-0
count--:46, producer signal consumer:Thread-0
count--:45, producer signal consumer:Thread-0
count--:44, producer signal consumer:Thread-0
count--:43, producer signal consumer:Thread-0
count--:42, producer signal consumer:Thread-0
count--:41, producer signal consumer:Thread-0
count--:40, producer signal consumer:Thread-0
count--:39, producer signal consumer:Thread-0
count--:38, producer signal consumer:Thread-0
count--:37, producer signal consumer:Thread-0
count--:36, producer signal consumer:Thread-0
count--:35, producer signal consumer:Thread-0
count--:34, producer signal consumer:Thread-0
count--:33, producer signal consumer:Thread-0
count--:32, producer signal consumer:Thread-0
count--:31, producer signal consumer:Thread-0
count--:30, producer signal consumer:Thread-0
count--:29, producer signal consumer:Thread-0
count--:28, producer signal consumer:Thread-0
count--:27, producer signal consumer:Thread-0
count--:26, producer signal consumer:Thread-0
count--:25, producer signal consumer:Thread-0
count--:24, producer signal consumer:Thread-0
count--:23, producer signal consumer:Thread-0
count--:22, producer signal consumer:Thread-0
count--:21, producer signal consumer:Thread-0
count--:20, producer signal consumer:Thread-0
count--:19, producer signal consumer:Thread-0
count--:18, producer signal consumer:Thread-0
count--:17, producer signal consumer:Thread-0
count--:16, producer signal consumer:Thread-0
count--:15, producer signal consumer:Thread-0
count--:14, producer signal consumer:Thread-0
count--:13, producer signal consumer:Thread-0
count--:12, producer signal consumer:Thread-0
count--:11, producer signal consumer:Thread-0
count--:10, producer signal consumer:Thread-0
count--:9, producer signal consumer:Thread-0
count--:8, producer signal consumer:Thread-0
count--:7, producer signal consumer:Thread-0
count--:6, producer signal consumer:Thread-0
count--:5, producer signal consumer:Thread-0
count--:4, producer signal consumer:Thread-0
count--:3, producer signal consumer:Thread-0
count--:2, producer signal consumer:Thread-0
count--:1, producer signal consumer:Thread-0
count--:0, producer signal consumer:Thread-0

采用Condition的好处是,通过使用锁,可确保在生产或消费修改临界区时,其他线程不可以对临界区变量进行修改。

3. Semaphore :共享锁Semaphore

一般意义的锁都是互斥的排他的,即只要我获得了锁,其他人就不能得到锁了。
Semaphore允许多个线程同时进入临界区,但进入临界区的线程数有限,额度用完了,其他请求锁的线程还是要在临界区外阻塞。当额度为1时,就相当于普通的锁。

例子:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
 
public class Test implements Runnable
{
    final Semaphore semaphore = new Semaphore(5);
    @Override
    public void run()
    {
        try
        {
            semaphore.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + " done");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        final Test t = new Test();
        for (int i = 0; i < 20; i++)
        {
            executorService.submit(t);
        }
    }
}

有20个线程的线程池,每个线程都要得到Semaphore的许可,而Semaphore只有5个许可,运行后可以看到,5个一批,然后睡眠2秒。

4. ReadWriteLock : 读写锁。

读锁:可以多线程读,不可多线程写。
写锁:其他线程不可读也不可写。

使用方式:
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();

六、concurrent包下的容器代码分析

1. ConcurrentHashMap (类)

ConcurrentHashMap有一个内部类Segment, 它将HashMap切分成若干个段(小的HashMap), 然后让数据在每一段上做hash, 这样多个线程在不同段上的hash操作一定是线程安全的,这样只需要对落在同一个段上的线程做同步就可以了,从而实现了锁的分离,提升了并发性能。

ConcurrentHashMap的缺点:调用size()方法代价高,因为要统计每个段的大小在求和,这时,要对每个段都加锁,然后在统计size。所以,size方法不应该被频繁调用。

2. BlockingQueue (接口)

BlockingQueue(阻塞队列) :当队列为空时,获取元素的线程等待,直到队列中有元素;当队列满时,存储元素的队列会等待,直到队列中有空位。
阻塞队列常用于生产者、消费者场景。

对BlockingQueue接口的实现类有下面几个:
1)ArrayBlockingQueue: 由数组组成的阻塞队列
2)LinkedBlockingQueue: 由链表组成的阻塞队列
3)PriorityBlockingQueue: 支持优先级排序的无界阻塞队列
4)DelayQueue: 使用优先级队列实现的无界阻塞队列
5)SynchronousQueue: 不存储元素的阻塞队列
6)LinkedTransferQueue: 由链表组成的无界阻塞队列
7)LinkedClockingDueue: 由链表组成的双向阻塞队列

通常使用LinkedBlockingQueue来实现生产者、消费者模式。

七、线程池

1. 使用线程池的目的

目的是为了线程复用,避免反复对线程进行创建和销毁操作的开销。

2. JDK提供的线程池相关的API

其中,ThreadPoolExecutor是线程池的一个重要实现。Executor是一个工厂类。

3. 线程池的种类:

1)FixedThreadPool : 线程池中的线程数固定不变。

2)SingleThreadExecutor: 线程池中只有一个线程。

3)CachedThreadPool:缓存线程池,线程池中的线程数量不固定,会根据需求大小进行改变。

4)ScheduledThreadPool:计划任务调度线程池,比如每个5分钟怎样。

下面看三种线程池的构造器的实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor(nThreads, nThreads,
     0L, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>());
}
 
 
public static ExecutorService newSingleThreadExecutor() {
 return new FinalizableDelegatedExecutorService
  (new ThreadPoolExecutor(1, 1,
     0L, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>()));
}
 
 
public static ExecutorService newCachedThreadPool() {
 return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
     60L, TimeUnit.SECONDS,
     new SynchronousQueue<Runnable>());
}

可以看出,他们都是ThreadPoolExecutor的不同实例,调用了其不同的构造器,只是参数不同。

public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue) {
 	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
	Executors.defaultThreadFactory(), defaultHandler);
}

下面解释下每个参数的含义:
1)corePoolSize:线程池中核心线程数的数目。
2)maximumPoolSize:线程池中最多能容纳的线程数目。(对于FixedThreadPool来说corePoolSize相等)
3)keepAliveTime: 当当时的线程数大于corePoolSize时,若超过keepAliveTime时间后,多出的corePoolSize的那些线程将被终结。
4)unit: keepAliveTime的单位
5)workQueue: 当任务数量大时,线程池中可容纳的线程数无法满足时,提交的任务会被放到阻塞队列中,线程空闲下来,则会不断从阻塞队列中取数据。

对于FixedThreadPool, 当请求大以至于线程中中可容纳的线程数不足时,会放到LinkedBlockingQueue中排队等待,这样当队列中元素很多时(LinkedBlockingQueue最多可容纳Integer.MAX_VALUE个元素),非常耗费内存。

CachedThreadPool则不同, 它的核心线程数是0,所以必然将请求任务放到SynchronousQueue中,这个队列只能有一个线程向其中添加元素,同时另一个线程从中获取元素时,才能成功,而仅添加无获取时会添加失败。当返回失败时,则线程池开始扩展线程。这就是为什么CachedThreadPool中的线程数目时不确定的。当60秒线程仍未被使用时,线程被销毁。
newScheduledThreadPool举例:每2秒钟打印一次时间

public static void main(String[] args) {
	ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
	// 如果前面的任务还未完成,则调度不会启动。
	ses.scheduleWithFixedDelay(new Runnable() {
		@Override
		public void run() {
			System.out.println(System.currentTimeMillis() / 1000);
			System.out.println(System.currentTimeMillis() / 1000);
		}
	}, 0, 2, TimeUnit.SECONDS);// 启动0秒后执行,然后周期2秒执行一次
}

4. 线程池的工作流程

1)线程池中每个执行的线程,都会调用runWorker

final void runWorker(WorkQueue w) {
 	w.growArray(); // allocate queue
	 for (int r = w.hint; scan(w, r) == 0; ) {
 	 	r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
	 }
 }

scan()是扫描是否有任务要做,r是个随机的数。

总结:当当前线程调用scan方法后,不会执行当前线程的WorkQueue中的任务,而是通过一个随机数r, 来得到其他WorkQueue的任务。
当前线程不会只着眼于自己的任务,而是优先完成其他任务,这样做可防止饥饿现象的发生,从而防止了某些线程因为卡死或者其他原因而无法及时完成任务,或者某个线程的任务量很大,而其他线程却没事可做。

5. ForkJoin思想:分而治之

fork/join类似于MapReduce算法,两者的区别是:Fork/Join只有在必要时,如任务非常大时才分割成一个个小任务,而MapReduce总是在开始执行第一步进行分割。所以,Fork/Join更适合一个JVM内的线程级别,而MapReduce适合分布式系统。

ForkJoin的接口:
RecursiveAction:无返回值
RecursiveTask:有返回值

例子:

import java.util.ArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class CountTask extends RecursiveTask<Long> {

	private static final int THRESHOLD = 10000;
	private long start;
	private long end;

	public CountTask(long start, long end) {
		super();
		this.start = start;
		this.end = end;
	}

	@Override
	protected Long compute() {
		long sum = 0;
		boolean canCompute = (end - start) < THRESHOLD;
		if (canCompute) {
			for (long i = start; i <= end; i++) {
				sum = sum + i;
			}
		} else {
			// 分成100个小任务
			long step = (start + end) / 100;
			ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
			long pos = start;
			for (int i = 0; i < 100; i++) {
				long lastOne = pos + step;
				if (lastOne > end) {
					lastOne = end;
				}
				CountTask subTask = new CountTask(pos, lastOne);
				pos += step + 1;
				subTasks.add(subTask);
				subTask.fork();// 把子任务推向线程池
			}
			System.out.println("size:" + subTasks.size());
			for (CountTask t : subTasks) {
				sum += t.join();// 等待所有子任务结束
			}
		}
		return sum;
	}

	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		CountTask task = new CountTask(0, 200000L);
		ForkJoinTask<Long> result = forkJoinPool.submit(task);
		try {
			long res = result.get();
			System.out.println("sum = " + res);
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

把从1累加到200000拆分成100个子任务来逐个完成,然后在进行汇总累加。

八、并发设计模式

1. 单例模式

实现1. 饿汉模式:通过私有方法和static类确保单例。

public class Singleton {
	private Singleton() {};
	private static Singleton instance = new Singleton();
	public static Singleton getInstance() {
		return instance;
	}
}

缺点:如果还有其他静态变量,比如public static int status = 1;
那么,即使我不想new Singleton的实例,仅调用Singleton.status,也会new Singleton的实例。

实现2. 懒汉模式:

public class Singleton {
	private Singleton(){}
	public static Singleton instance;
	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

缺点:高并发时,会有线程安全问题,例如,在执行instance = new Singleton(); 时,在高并发场景下,通过synchronized会造成线程阻塞,影响性能。

实现3. 高并发场景下好的实现方式:通过内部静态类

public class Singleton {
	private Singleton(){}
	private static class SingletonHolder {
		public static Singleton instance = new Singleton();
	}
	public static final Singleton getInstance() {
		return SingletonHolder.instance;
	}
}

这就避免了第一种饿汉模式问题,即使还有个成员变量public static int status = 1; 在调用Singleton.status时,仍然不会调用到new Singleton();而仅在显式调用Singleton.getInstance()的时候,才会调用到new Singleton();

并且不必使用synchronized,避免了高并发下的性能问题。

实现4. 双重校验锁:懒汉模式的升级版本

public class Singleton {
	private Singleton(){}
	public static volatile Singleton instance;
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
	} 
}

双重校验锁是懒汉模式的升级版,在高并发场景下降低了锁的粒度,性能更好。

双重校验锁在极端情况下会失败:

// 双重校验锁
class Singleton3 {
	private static volatile Singleton3 instance;
	private Singleton3(){}
	
	public static Singleton3 getInstance() {
		if (instance == null) {
			synchronized(Singleton3.class) { // 1
				if (instance == null) { // 2
					/*
					 * 双重校验锁方式偶尔会失败的原因:在Singleton3()的函数体被执行之前,变量instance就变成了非null.
					 * 
					 * JVM在执行instance = new Singleton3()语句时,会分为下面的三步:
					 * Step1. mem = allocate(); // 预分配内存给Singleton
					 * Step2. instance = mem; // 把instance置为非null
					 * Step3. ctorSingleton(instance); // 调用构造器来初始化
					 * 
					 * 所以,当线程1执行到instance = new Singleton3()语句的Step2时,另一个线程2执行到了 if (instance == null) , 
					 * 那么该线程2会直接返回还没有初始化的instance, 导致instance不是我们想要的。
					 * 
					 * 这种失败只是发生在极端情况下,目前我尚未遇到过。
					 */
					instance = new Singleton3(); // 3
				}
			}
		}
		return instance;
	}
}

2. 不变模式:一个类被创建后,其属性和方法都不可改变,实现方式是把类和类的成员变量都用final修饰。

例如:
public final class Product {
// 确保无子类
private final String no;
// 私有属性,不会被其他对象获取
private final String name;
// final保证属性不会被2次赋值
private final double price;

public Product(String no, String name, double price) {
	// 在创建对象时,必须指定数据
	super();
	// 因为创建之后,无法进行修改
	this.no = no;
	this.name = name;
	this.price = price;
}

public String getNo() {
	return no;
}

public String getName() {
	return name;
}

public double getPrice() {
	return price;
}

}

JDK中的不变模式有String, Boolean, Byte, Character, Double, Float, Integer, Long, Short.

3)Future模式:核心思想是异步调用。

例子:
interface Data {
public String getResult();
}

class FutureData implements Data {
protected RealData realdata = null; // FutureData是RealData的包装
protected boolean isReady = false;

public synchronized void setRealData(RealData realdata) {
	if (isReady) {
		return;
	}
	this.realdata = realdata;
	isReady = true;
	notifyAll(); // RealData已经被注入,通知getResult()
}

public synchronized String getResult()// 会等待RealData构造完成
{
	while (!isReady) {
		try {
			wait(); // 一直等待,直到RealData被注入
		} catch (InterruptedException e) {
		}
	}
	return realdata.result; // 由RealData实现
}

}

class RealData implements Data {
protected final String result;

public RealData(String para) {
	// RealData的构造可能很慢,需要用户等待很久,这里使用sleep模拟
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < 10; i++) {
		sb.append(para + " ");
		try {
			// 这里使用sleep,代替一个很慢的操作过程
			Thread.sleep(100);
		} catch (InterruptedException e) {
		}
	}
	result = sb.toString();
}

public String getResult() {
	return result;
}

}

class Client {
public Data request(final String queryStr) {
final FutureData future = new FutureData();
new Thread() {
public void run() {
// RealData的构建很慢,
// 所以在单独的线程中进行
RealData realdata = new RealData(queryStr);
future.setRealData(realdata);
}
}.start();
return future; // FutureData会被立即返回
}
}

public class FutureTest {
public static void main(String[] args) {
Client client = new Client();
// 这里会立即返回,因为得到的是FutureData而不是RealData
Data data = client.request(“name”);
System.out.println(“请求完毕”);
try {
// 这里可以用一个sleep代替了对其他业务逻辑的处理
// 在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 使用真实的数据
System.out.println("数据 = " + data.getResult());
}
}

也可通过JDK提供的API “FutureTask”实现上述的功能:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class FutureMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 构造FutureTask
FutureTask future = new FutureTask(new RealData1(“a”));
ExecutorService executor = Executors.newFixedThreadPool(1);
// 执行FutureTask,相当于上例中的 client.request(“a”) 发送请求
// 在这里开启线程进行RealData的call()执行
executor.submit(future);
System.out.println(“请求完毕”);
try {
// 这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 相当于data.getResult (),取得call()方法的返回值
// 如果此时call()方法没有执行完成,则依然会等待
System.out.println("数据 = " + future.get());
}
}

class RealData1 implements Callable {
private String para;

public RealData1(String para) {
	this.para = para;
}

@Override
public String call() throws Exception {
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < 10; i++) {
		sb.append(para);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {

		}
	}
	return sb.toString();
}

}

  1. 生产者消费者模式

九。BIO, NIO与AIO

NIO: New IO, 具有以下特征:
1)NIO是基于块(block)的,以block为基本单位。(磁盘上存储数据的单位也是Block, 这样性能上比基于流的方式好)
2)为所有的原始类型提供(Buffer)缓存支持
3)增加了Channel对象,作为新的原始的I/O的抽象类。
4)支持锁和内存映射文件的访问接口
5)提供了基于selector的异步网络IO

所有从Channel中的读写操作,都要经过buffer,channel的另一端就是操作的文件。

  1. 阻塞IO: 如传统的Socket
    对于每个客户端的请求,服务器端都要启动一个线程去处理,即在调用InputStream.read()时是阻塞的,它会一直等到数据从Server端返回时(或请求超时)才返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才返回。具体流程如下图:

  2. NIO

NIO的工作原理:
1)由一个专门的线程来处理所有的IO事件,并负责分发
2)事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3)线程通讯:线程之间通过wait, notify等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。

每个线程的处理流程都是读取数据、解码、计算处理、编码、发送响应。

Java NIO的服务器端只需启动一个专门的线程来处理所有的IO事件,这种通信模型是怎样实现的呢?Java NIO采用了双向channel进行数据传输,而不是单向stream,在channel上可以注册我们感兴趣的事件。

在客户端和服务器端各自维护了一个管理channel的对象,称之为selector,该对象能检测一个或多个channel上的事件。以服务器端为例,如果服务器端的selector注册了读事件,某时刻客户端给服务器端发送了一些数据,阻塞IO这时会调用read()方法阻塞地读取数据,而NIO的服务器端会在selector中添加一个读事件。服务器端的处理线程会轮询低访问selector,如果访问selector发现有感兴趣的事件到达,则会处理这些事件,如果没有感兴趣的事件,则处理线程会一直阻塞直到感兴趣的事件到达为止。

4.BIO、NIO、AIO比较

1)阻塞与非阻塞:
阻塞: ATM排队取款,你只能排队等待(使用阻塞IO, java调用会一直阻塞到读写完成才能返回,期间什么也不能干)
非阻塞:柜台取款,取个号,然后可以去干其他事,等号广播会通知你办理,没有到号你就不能去;你可以不断问大堂经理你排到了没,大堂经理如果说还没到你,你就不能去。(使用非阻塞IO,如果不能读写,Java调用会马上返回,当IO时间分发器会通知可读写时在继续继续读写,不断循环直到读写完成)。

2)BIO、NIO、AIO
BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情,就会造成不必要的线程开销,当然,也可以使用线程池机制进行改善。
NIO: 同步非阻塞,服务器的实现模式为一个请求一个线程,即客户端发送的请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时,才启动一个线程进行处理。
AIO(NIO2.0):异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端IO请求都是由OS先完成了再去通知服务器应用去启动线程进行处理。

3)BIO、NIO、AIO应用场景
BIO:适用于连接数小,JDK1.4之前只有BIO.
NIO: 适用于连接数多,且读写过程短,如聊天室服务器
AIO:适用于连接数多,且读写过程长,如相册服务器,AIO可充分调用操作系统参与并发操作。AIO于JDK1.7开始实现。

注:IO属于底层操作,需要操作系统支持,并发的处理也需要操作系统支持,所以性能方面不同的操作系统差异可能会比较明显。

十。锁的优化

减少锁的持有时间

public synchronized void test() {
step1();
step2();
step3();
}

如果这个过程仅需要对step2加锁,那么可以改成:
public void test() {
step1();
synchronized(this) {
step2();
}
step3();
}

  1. 减少锁的粒度

对于被多个线程访问的大对象,可以将其拆分成小对象,这样可以增加并行度,降低锁竞争。
经典案例是ConcurrentHashMap

  1. 锁分离
    经典例子:
    ReadWriteLock: 根据功能将锁分成读锁和写锁
    LinkedBlockingQueue: 从队列头部取数据,从队列尾部放数据,这二者操作互不影响,所以可以将锁分离。

  2. 锁粗化
    虽然要尽量减少锁的持有时间,但也要避免不停地创建锁再释放。例如:
    public void test() {
    synchronized(lock) {
    step1();
    }
    synchronized(lock) {
    step2();
    }
    }
    这时可以优化成:
    public void test() {
    synchronized(lock) {
    step1();
    step2();
    }
    }

  3. 锁消除
    首先把避免在方法中对局部变量加锁。例如,在方法中使用StringBuffer.append
    也可以使用锁消除,即可以通过对JVM的参数进行设置,让编译器在编译时即消除这样的锁。

  4. 错误使用锁的例子
    public class IntegerLock {
    static Integer i = 0;

    public static class AddThread extends Thread {
    public void run() {
    for (int k = 0; k < 100000; k++) {
    synchronized (i) {
    i++;
    }
    }
    }
    }

    public static void main(String[] args) throws InterruptedException {
    AddThread t1 = new AddThread();
    AddThread t2 = new AddThread();
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
    }
    }

由于Integer是final的,所以每次++后,都会产生一个新的Integer对象,所以两个线程争夺的锁是不同的,所以不是线程安全的。

  1. ThreadLocal及源码分析

ThreadLocal实际上应该叫做ThreadLocalVariable,即线程局部变量。它其实是一个容器。

ThreadLocal可以解决的问题:当一个线程类的static的成员变量被该类的同一个实例以多线程方式访问时,static变量是为这多个线程所共享的,那么,要把这个static变量变成线程私有的,就需要使用ThreadLocal,把:
private static int number = 0;
变成:
private static ThreadLocal numberContainer = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
实际上,ThreadLocal里面就是封装了一个map, 类似于private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>());

ThreadLocal的实现和HashMap差不多,但在hash冲突的处理上不同,HashMap是通过链表来解决冲突的,而ThreadLocal是将索引++, 放到下一个索引处来解决冲突。

十一。Java 8 对于并发的新支持

LongAdder: 与AtomicLong类似,但比它性能更好。
LongAdder与AtomicLong都使用了原子操作来提升性能,但LongAdder在AtomicLong基础上进行了热点分离,这类似于有锁操作中的减小锁粒度,将一个锁分成若干个锁来提高性能。

1)AtomicLong: 内部有个value变量,当多线程并发自增时,通过CAS指令从机器指令级别操作,保证并发的原子性。唯一制约AtomicLong性能的原因是高并发,高并发意味着CAS的失败几率更高,重试的次数更多,越多线程重试,CAS的失败几率越高,造成恶性循环,使AtomicLong效率降低。

2)LongAdder: 将把一个value拆分成若干cell, 把所有cell加起来,就是value. 所以对LongAdder的加操作,只需要对不同的cell来操作,不同的线程对不同的cell进行CAS操作,CAS的成功率当然高了。

综上,对于并发不高时,AtomicLong比LongAdder性能高,在并发高时,LongAdder性能好于AtomicLong. 即LongAdder是以空间换时间的策略。

  1. StampedLock: 对ReadWriteLock的改进。

StampedLock认为,读不应该阻塞写:即当有线程读时,也可以有线程写,这时让读的线程重新读,而不是像ReadWrtieLock那样,不让其他线程写。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值