1 并发(Concurrency)和并行(Parallelism)都可以表示两个或多个任务一起执行。但并发偏重于多个任务交替执行,而多个任务之间有可能还是串行。并行是真正意义上的“同时执行”。
2 有关并行的两个重要定律。Amdahl定律强调当串行比例一定时,加速比是有上限的。Gustafson定律关心的是如果可被并行化的代码所占比重足够多,那么加速比就能随着CPU的数量线性增长。
- Amdahl定律,它定义了串行系统并行化后的加速比的计算公式和理论上限
加速比定义: 加速比 = 优化前系统耗时 / 优化后系统耗时
注:n表示处理器个数,T表示时间,T1表示优化前耗时,Tn表示使用n个处理器优化后的耗时。F是程序中只能串行执行的比例。
- Gustafson定律,用于说明处理器个数,串行比例和加速比之间的关系。
3 JAVA的内存模型(JMM)的关键技术点是围绕多线程的原子性,可见性和有序性建立的。原子性是指一个操作是不可中断,即使多线程一起执行,该操作也不会被干扰;可见性是指一个线程修改了某个共享变量,其他线程能否立即知道该修改。有序性是指代码有序执行,这个是最难的,因为为了提高CPU处理性能,指令会重排,存在乱序风险。
4 java的32位系统中,因为long型数据是64位,所以它的读和写都不是原子性的,多线程之间会相互干扰。
1 进程是系统进行资源分配和调度的基本单位。线程是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程进行并发程序的设计,是因为线程间的切换和调度成本远远小于进程。
2 线程的基本操作
Thread t1 = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Hello world");
}
};
t1.start();
注:start()会新建一个线程并让这个线程执行run()。不要直接用run()启动新线程,它只会在当前线程中串行执行run()中的代码
因为java是单继承,可以采用实现接口Runnable来执行上面的步骤
public class CreateThread1 implements Runnable {
public static void main(String[ ] args) {
Thread t1 = new Thread(new CreateThread1()) ;
t1.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Hello world");
}
}
与线程中断有关的有三个方法,如下
public void Thread.interrupt( ) // 中断线程
public boolean Thread.isInterrupted( ) // 判断是否被中断
public static boolean Thread.interrupted( ) // 判断是否被中断,并清除当前中断状态
# interrupte() 并没有让t1中断
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("hello");
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
# isInterrupted() 让t1中断
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(Thread.currentThread().isInterrupted()) { // 判断是否有被中断,有则退出
System.out.println("interrupted!");
break;
}
System.out.println("hello");
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
注:Thread.sleep( ) 方法会让当前线程休眠若干时间,如果此时有个中断它会抛出一个InterruptedException中断异常。InterruptedException不是运行时异常,所以程序必须要捕获并处理它
public final void wait( ) throws InterruptedException
public final native void notify( )
线程A调用了obj.wait( )方法后,线程A会停止执行,进入object对象的等待队列中转为等待状态。直到其他线程调用了obj.notify( )方法,从队列中随机选择一个唤醒。wait( )不是随便调用的,它必须包含在对应的synchronzied语句中,无论是wait( )或notify( )都需要首先获得目标对象的一个监视器。
线程T1在正确执行wait( )前,首先必须获得object对象的监视器。而wait( )方法在执行后,会释放这个监视器。这样做的目的是使得其他等待在object对象上的线程不至于因为T1的休眠而全部无法正常执行。
线程T2在notify( )调用前,也必须获得object的监视器。接着T2执行了notify( )尝试唤醒一个等待线程,假设唤醒了T1。T1唤醒后要尝试重新获得object的监视器,如果暂时无法获得,T1还必须要等待这个监视器。当监视器获得后,T1才可以继续执行。
public class MultiThreadLong { final static Object object = new Object(); public static class T1 extends Thread{ public void run(){ synchronized(object){ System.out.println(System.currentTimeMillis()+": T1 start!"); try{ System.out.println(System.currentTimeMillis()+": T1 wait for object"); object.wait(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(System.currentTimeMillis()+": T1 end!"); } } } public static class T2 extends Thread{ public void run(){ synchronized(object){ System.out.println(System.currentTimeMillis()+": T2 start! notify one thread"); object.notify(); System.out.println(System.currentTimeMillis()+": T2 end!"); try{ Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } } } } public static void main(String[] args){ Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } }
输出
1534760587264: T1 start!
1534760587264: T1 wait for object
1534760587264: T2 start! notify one thread
1534760587264: T2 end!
1534760589267: T1 end! # T1 并没有立即继续执行,而且等待T2释放了object的锁。所以它跟上一条日志间隔2秒
注:Object.wait( )和Thread.sleep( )都可以让线程等待若干时间,除了wait( )可以被唤醒外,wait( )会释放目标对象的锁。而sleep( )不会释放任何资源。
public final void join( ) throws InterruptedException // 表示无限等待,会一直阻塞当前线程直到目标线程执行完毕
public final synchronized void join(long millis) throws InterruptedException // 超过最大的等待时间millis后会自行继续执行
public static native void yield( ) ; // 静态方法,一旦执行会使当前线程让出CPU,让别的线程先用。让出不代表不执行,也会试着去抢CPU资源
public volatile static int i = 0;
public static class AddThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(i=0;i<100000000;i++);
}
}
public static void main(String[] args){
AddThread at = new AddThread();
at.start();
try{
at.join();
}catch(InterruptedException e){
}
System.out.println(i);
}
主函数中,如果不使用join()等待AddThread,输出0。但使用join()函数后表示主线程愿意等待AddThread执行完毕后再一起往前走。所以输出100000000。
注:join()的本质是让调用线程wait()在当前线程对象实例上,让调用的线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用notifyAll( )通知所有等待的线程继续执行。
3 用volatile申明变量时,表示该变量可能会被某些程序或者线程修改。为了确保这个变量被修改后,虚拟机会采用一些特殊的手段保证该变量的可见性。
static volatile int i = 0;
public static class PlusTask implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int k=0;k<10000;k++)
i++;
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] threads = new Thread[10];
for(int i=0;i<10;i++){
threads[i] = new Thread(new PlusTask());
threads[i].start();
}
for(int i=0;i<10;i++)
threads[i].join();
System.out.println(i);
}
如果 i++是原子性,最终值会是100000,但通过volatile是无法保证i++的原子性操作,所以输出总是小于100000。
volatile能保证数据的可见性和有序性,如下所示,如果ready值没有声明为volatile属性时,ready=true的赋值ReaderThread线程无法收到。
private static volatile boolean ready; private static int number; public static class ReaderThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub while(!ready); System.out.println(System.currentTimeMillis()+": "+number); } } public static void main(String[] args) throws InterruptedException { System.out.println(System.currentTimeMillis()+": 1"); new ReaderThread().start(); Thread.sleep(1000); System.out.println(System.currentTimeMillis()+": 2"); number = 42; ready = true; Thread.sleep(1000); System.out.println(System.currentTimeMillis()+": 3"); }
4 分门别类的管理:线程组,在一个系统中如果线程数量很多且功能分配明确,可以将相同功能的线程放在一个线程组中如下
public class MultiThreadLong implements Runnable {
public static void main(String[] args){
ThreadGroup tg = new ThreadGroup("PrintGroup"); // 建立一个"PrintGroup"的线程组
Thread t1 = new Thread(tg,new MultiThreadLong(),"T1");
Thread t2 = new Thread(tg,new MultiThreadLong(),"T2");
t1.start();
t2.start();
System.out.println(tg.activeCount()); // 获得活动线程的总数
tg.list(); // 打印线程组的所有线程信息
}
@Override
public void run() {
// TODO Auto-generated method stub
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();
}
}
}
}
输出
2
I am PrintGroup-T1
I am PrintGroup-T2
java.lang.ThreadGroup[name=PrintGroup,maxpri=10]
Thread[T1,5,PrintGroup]
Thread[T2,5,PrintGroup]
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
I am PrintGroup-T1
I am PrintGroup-T2
5 驻守后台:守护线程(Daemon),系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程,JIT线程等。
public class DaemonDemo {
public static class DaemonT extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("I am alive");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t = new DaemonT();
t.setDaemon(true); // 设置为守护线程,但必须要在start()调用之前
t.start();
Thread.sleep(2000);
}
}
t被设置为守护线程,系统只有主线程main为用户线程,因此在main线程休眠2秒后退出时,整个程序也会结束,t也结束。如果不把线程t设置为守护线程,main线程结束后,t线程还好不停地打印,永远不会结束。
6 java中的线程都可以有自己的优先级,使用1到10表示优先级,有三个内置的静态标量表示:
public class PriorityDemo {
public static class HightPriority extends Thread{
static int count = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(PriorityDemo.class){
count++;
if(count > 10000000){
System.out.println("HightPriority is complete");
break;
}
}
}
}
}
public static class LowPriority extends Thread{
static int count = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(PriorityDemo.class){
count++;
if(count > 10000000){
System.out.println("LowPriority is complete");
break;
}
}
}
}
}
public static void main(String[] args){
Thread high = new HightPriority();
LowPriority low = new LowPriority();
high.setPriority(Thread.MAX_PRIORITY); // 设置优先级
low.setPriority(Thread.MIN_PRIORITY); // 设置优先级
low.start();
high.start();
}
}
high优先级高,所以在多数情况下会比low快,但不是每次都比low快
7 关键字synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每次只有一个线程进入同步块。主要有如下几种用法
public class AccountingVol implements Runnable {
static AccountingVol instance = new AccountingVol();
static int i = 0;
public static void increase(){
i++;
}
public synchronized void increase2(){ // 第一种方法,函数定义为同步
i++;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<10000000;j++){
// 第二种方法,对象定义为同步
synchronized(instance){
i++;
}
//increase2();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
8 ArrayList是一个线程不安全的容器。如果多线程使用ArrayList可能会导致程序出错。可以使用线程安全的Vector来代替ArrayList
9 HashMap也是线程不安全,多线程访问HashMap也会导致程序出错。可以使用ConcurrentHashMap来代替HashMap
10 一个错误的加锁,如下所示,给i加锁,但因为i是Integer对象,是不变对象,每次的值变其实都是新建一个Integer对象,即锁加在了不同的对象上。可以修改为synchronized(instance)即可
public class BadLockOnInteger implements Runnable {
public static Integer i = 0;
static BadLockOnInteger instance = new BadLockOnInteger();
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<10000000;j++){
synchronized(i){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}