Java零散知识补充
程序员文章站
2022-05-14 13:57:01
...
线程池的参考网址:http://www.cnblogs.com/exe19/p/5359885.html
四种线程参考网址:http://blog.csdn.net/hupitao/article/details/24453083
volatile
volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.
简单地说就是防止编译器对代码进行优化.比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(产生四条代码).
Java中的ReentrantLock和synchronized两种锁定机制的对比
- 实现多线程和并发性,使用的类库核心类库包含一个 Thread类,可以用它来构建、启动和操纵线程
- Java 语言包括了跨线程传达并发性约束的构造 —— synchronized 和 volatile
synchronized回顾
- 代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)
- 原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突
- 可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为
- 总结就是:被锁定的成员将继续宁执行完后,才能再次执行后面的代码,volatile也类似这样
synchronized (lockObject) {
// update object state
}
如此看来同步相当好了,是么?那么为什么 JSR 166 小组花了这么多时间来开发 java.util.concurrent.lock框架呢?
-->>答案很简单-同步是不错,但它并不完美。它有一些功能性的限制 —— 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
ReentrantLock回顾
- java.util.concurrent.lock中的 Lock框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为关键字(语言特性)
- 这就为 Lock的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义
- ReentrantLock类实现了 Lock,它拥有与 synchronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性
- 它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
- 它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。(注意释放锁)
Lock lock = new ReentrantLock();
lock.lock();
try {
// update object state
}
finally {
lock.unlock();
}
多线程池ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
线程池案例
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
--- 从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。
--- 不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
其实是对的实现(提供默认值)
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE,可灵活回收空闲线程,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
Executors.newSingleThreadExecutor(); //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
Executors.newFixedThreadPool(int); //创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
Executors.newScheduledThreadPool (int); //创建一个定长线程池,支持定时及周期性任务执行。