一、JMM:Java Memory Model(Java内存模型)
- 1.1原子性
操作不可中断 - 1.2可见性
共享变量的修改,其他线程是否能够立即知道这个修改 - 1.3有序性
二、java并行程序基础
Theard案例
class MyThread extends Thread {
@Override
public void run() {
do something...
}
}
MyThread mt = new MyThread("thread name");
mt.start();
Runnable案例
class MyRunnable implements Runnable {
@Override
public void run() {
do something...
}
}
MyRunnable mt = new MyRunnable();
Thread td = new Thread(mt, "thread name");
td.start();
并发中的List
- 异常问题:
ArrayList al = new ArrayList(10);
如果多个线程add数据,ArrayList扩容有些时候会越界 - 解决:
使用Vector,不过性能不好
使用CopyOnWriteArrayList,读多写少下,性能非常好
使用ConcurrentLinkedQueue,高效并发队列,线程安全的LinkedList
并发中的Map
- HashMap,java8解决了
- 其他的话,用ConcurrentHashMap线程安全的hashMap
三、线程操作
新建
start()
终止
自己定义一个boolean值,不要用stop()方法
睡眠 + 中断(一般用于线程中断的异常操作)
Thread.interrupt() # 中断线程,但是如果没有后续逻辑,它其实没有操作什么
/** * 线程中断测试 */ public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread() { @Override public void run() { // 循环监控 while(true) { if (Thread.currentThread().isInterrupted()) { System.out.println("线程中断"); break; } try { Thread.sleep(2000); System.out.println("子线程执行中"); } catch (InterruptedException e) { System.out.println("当前线程睡眠被中断"); // 不设置中断,则下一次循环无法捕获,所以还需要设置中断标记 Thread.currentThread().interrupt(); } } } }; t1.start(); // 子线程执行中 Thread.sleep(5000); // 主线程睡眠5秒 t1.interrupt(); // 强制中断子线程 } console: 子线程执行中 子线程执行中 当前线程睡眠被中断 线程中断
等待和通知(Object类),下面的join方法就是用的这些,所以少用wait和notify,直接用join即可
- wait、notify、notifyAll
- 必须包含在对应的synchronized语句中,必须获得目标对象的一个监视器
sleep不会释放锁,wait会释放
public class ThreadTest { // 锁 final static Object obj = new Object(); public static class T1 extends Thread{ @Override public void run() { synchronized (obj){ System.out.println(System.currentTimeMillis() + " T1 start"); try { System.out.println(System.currentTimeMillis() + " T1 wait from obj"); obj.wait(); // 等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "T1 end"); } } } public static class T2 extends Thread{ @Override public void run() { synchronized (obj){ System.out.println(System.currentTimeMillis() + " T2 start!notify one thread"); obj.notify(); // 唤起,必须是同一个obj锁内 System.out.println(System.currentTimeMillis() + " T2 end!"); try { Thread.sleep(2000); // 睡眠2秒,不放开锁 } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { // ti不一定先执行,多执行几次 Thread t1 = new T1(); t1.start(); Thread t2 = new T2(); t2.start(); } } console: 151125703 2560 T1 start 151125703 2560 T1 wait from obj 151125703 2560 T2 start!notify one thread 151125703 2560 T2 end! 151125703 4560 T1 end //T2输出了end以后sleep了2秒,所以sleep并不会释放锁
等待线程结束join和谦让yield
join,等待线程完成再执行
public volatile static int i = 0; // 同步参数 public static class AddThread extends Thread { @Override public void run() { for (i = 0; i < 1000000; i++) ; } } public static void main(String[] args) throws InterruptedException { Thread t = new AddThread(); t.start(); t.join(); // 如果没有join方法,主线程就已经打印i了;现在需要等t线程完成再打印 System.out.println(i); }
- yield,当前线程让出cpu,重新进行竞争
做完重要的事情了,然后让出cpu,给其他线程一些机会
或者优先级低,怕它占用太多cpu资源,适当的时候使用yield方法 - volatile
它只能保证一个线程修改了数据后,其他线程能够看到这个改动,2个线程如果同时修改某一个数据时,依然会产生冲突
线程组
- 能管理一组线程,比如中断、销毁、停止、优先级等,但是不能同时启动
@Override
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName() +
"-" + Thread.currentThread().getName();
while(true){
System.out.println("I am " + groupAndName);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread t1 = new Thread(tg, new thisClass(), "name1");
Thread t2 = new Thread(tg, new thisClass(), "name2");
t1.start();
t2.start();
// activeCount 估计线程总数,因为线程是动态的
System.out.println(tg.activeCount());
tg.list(); // 打印组内所有线程信息
}
守护线程
- t.start()前设置: t1.setDaemon(true);
- 用户线程结束了,守护线程自动就结束了,所以不要用在类似于IO这样的线程中
- 见过的比如 垃圾回收、数据库线程数量监控等等,eg:如果想main结束,线程就结束,可以设置为守护线程
优先级
- 各个平台表现不一,不严格,有效为1-10
- t.setPriority(Thread.MAX_PRIORITY);
- t.setPriority(Thread.MIN_PRIORITY);
- 或者直接输入1-10
线程池
- newFixedThreadPool 返回固定线程数量的线程池
- newCachedThreadPool 可变数量
- 还有2个是定时执行的,暂时不看了
public static class MyTask implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread ID:"
+ Thread.currentThread().getId());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyTask task = new MyTask();
// 只有5个线程,处理完5个sleep2秒返回线程池,再用这5个
ExecutorService es = Executors.newFixedThreadPool(5);
// 直接扩充成10个线程了
// ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++){
es.submit(task);
// es.execute(task); // 如果线程输出缺失,这个替换submit能查看到堆栈信息
}
}
- 自定义线程
/**
* int corePoolSize 核心线程数,一直存活
* int maximumPoolSize 最大线程,超出会进入拒绝策略
* long keepAliveTime 空闲时间,超过则退出线程
* TimeUnit unit 空闲时间单位
* WorkQueue<Runnable> workQueue 任务队列(常用3个,见下)
* RejectedExecutionHandler handler 拒绝任务的处理策略(4个,见下)
*
* 以下参数顺序同上
*/
......
ThreadPoolExecutor es = new ThreadPoolExecutor(10, 100, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new ThreadPoolExecutor.DiscardPolicy());
......
- 任务队列
- 它是一个BlockingQueue接口的对象
- SynchronousQueue 直接提交的队列,不真实保存数据,当线程池满了就执行拒绝策略,所以一般将最大线程数量设置很大
- ArrayBlockingQueue,有界队列,核心线程满了,进入队列,顺序保存数据
- LinkedBlockingQueue,无界队列,核心线程满了,进入队列,不会失败,无限,直到耗尽内存
- PriorityBlockingQueue,优先任务队列,无界,在确保性能的同时,能保证质量
- 拒绝策略
- AbortPolicy 抛出异常,阻止运行
- CallerRunsPolicy 运行当前被丢弃的任务,性能可能会极具下降
- DiscardOldestPolicy 抛弃最老的任务
- DiscardPolicy 抛弃无法处理的任务,如果允许任务丢失,这个是最好的
- 自定义线程二(一般不用自己创建,使用默认的即可)
同自定义线程,不过可以不用它的策略,自己扩展策略或者使用线程工厂自定义线程池
......
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// t.setDaemon(true); // 可以设为守护线程
System.out.println("create " + t);
return t;
}
}
);
......
- 最优线程池
- Ncpu = CPU的数量 System.out.println(Runtime.getRuntime().availableProcessors());
- Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
- W/C = 等待时间 与 计算时间的比率
- Nthreads = Ncpu * Ucpu * ( 1 + W/C )
- 我拿我的计算机进行了测试
四、同步 synchronized
- 必须是同一对象锁(int行,Integer不行,++操作以后Integer是个新对象)
总结用法:
- 对象锁
- 直接作用于实例方法
直接作用于静态方法
public class ThreadTest implements Runnable { static ThreadTest tt = new ThreadTest(); static int i = 0; @Override public void run() { synchronized (tt) { // 如果不同步,则i会被同时写入,造成实际 <200000 for (int j = 0; j < 100000; j++) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(tt); // 注意!必须是同一个对象 Thread t2 = new Thread(tt); // 注意!必须是同一个对象 t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
另一种写法,抽出同步代码块,变成同步方法/同步静态方法
public synchronized void increase(){ // static也一样 for (int j = 0; j < 100000; j++) { i++; } } @Override public void run() { increase(); }
优化建议
- jdk6以后synchronized和lock性能非常接近了,用哪个都行,就是读写锁感觉很有用
- 减少持有时间
尽量减少同步的方法 - 减少锁粒度
不用考虑太多,除非调用少才合适 - 读写分离锁来替换独占锁
- 锁分离
- 锁粗化
串行的很多锁可以合并为块,开关锁也是要消耗资源的,例如for循环内部的锁不如外部一并加锁 - 读写锁案例:
public class ReadWriteLockDemo { // 原来的锁 private static Lock lock = new ReentrantLock(); // 创建读、写锁 private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = readWriteLock.readLock(); private static Lock writeLock = readWriteLock.writeLock(); // 读取的参考变量 private int value; // 模拟读方法 public Object handleRead(Lock lock) throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-read-begin"); try { lock.lock(); // 模拟读操作 Thread.sleep(2000); // 读操作耗时越多越明显;线程(除了被写占用的线程)会一并进入方法,然后各读各的,各返回各的 return value; } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + "-read-end"); } } // 模拟写方法 public void handleWrite(Lock lock, int index) throws InterruptedException { System.out.println(Thread.currentThread().getName() + "-write-begin"); try { lock.lock(); Thread.sleep(1000); value = index; } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName() + "-write-end"); } public static void main(String[] args) { final ReadWriteLockDemo demo = new ReadWriteLockDemo(); // 模拟读线程 Runnable readRunnale = new Runnable() { @Override public void run() { try { // 用了读锁,所有的读线程会一起进入,相互不阻塞 demo.handleRead(readLock); // 用普通的锁,会一个一个进入 // demo.handleRead(lock); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable writeRunnale = new Runnable() { @Override public void run() { try { // 用了写锁,和普通的lock一样,是阻塞的 demo.handleWrite(writeLock, new Random().nextInt()); // demo.handleWrite(lock, new Random().nextInt()); } catch (InterruptedException e) { e.printStackTrace(); } } }; for (int i = 0; i < 18; i++){ new Thread(readRunnale).start(); } for (int i = 18; i < 20; i++){ new Thread(writeRunnale).start(); } } }