《实战Java高并发程序设计》学习总结(1)

第1章 走入并行世界

1 并发(Concurrency)和并行(Parallelism)都可以表示两个或多个任务一起执行。但并发偏重于多个任务交替执行,而多个任务之间有可能还是串行。并行是真正意义上的“同时执行”。

2 有关并行的两个重要定律。Amdahl定律强调当串行比例一定时,加速比是有上限的。Gustafson定律关心的是如果可被并行化的代码所占比重足够多,那么加速比就能随着CPU的数量线性增长。

  • Amdahl定律,它定义了串行系统并行化后的加速比的计算公式和理论上限

                       加速比定义:   加速比  =   优化前系统耗时 / 优化后系统耗时

                       

注:n表示处理器个数,T表示时间,T1表示优化前耗时,Tn表示使用n个处理器优化后的耗时。F是程序中只能串行执行的比例。

  • Gustafson定律,用于说明处理器个数,串行比例和加速比之间的关系。

                         

3 JAVA的内存模型(JMM)的关键技术点是围绕多线程的原子性,可见性和有序性建立的。原子性是指一个操作是不可中断,即使多线程一起执行,该操作也不会被干扰;可见性是指一个线程修改了某个共享变量,其他线程能否立即知道该修改。有序性是指代码有序执行,这个是最难的,因为为了提高CPU处理性能,指令会重排,存在乱序风险

4 java的32位系统中long型数据的读和写都不是原子性的,多线程之间会相互干扰。

第2章  Java并行程序基础

1 线程是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程进行并发程序的设计,是因为线程间的切换和调度成本远远小于进程。

2 线程的基本操作

  • 新建线程,只要使用new关键字创建一个线程对象,并且将它start( )起来即可。

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");

       }

}

  • 终止线程,使用stop( ),但该函数过于暴力,容易造成数据不同步,不建议使用。
  • 线程中断,在java中是一种重要的线程协作机制。它并不会使线程立即退出,而是给线程发送一个通知告知目标线程。怎么处理则由目标线程自行处理

与线程中断有关的有三个方法,如下

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中断异常。

  • 等待(wait)和通知(notify),wait()和notify()并不是Thread类的,而是输出Object类的。

public final void wait( ) throws InterruptedException

public final native void notify( )

线程A调用了obj.wait( )方法后,线程A会停止执行,进入object对象的等待队列中转为等待状态。直到其他线程调用了obj.notify( )方法,从队列中随机选择一个唤醒。wait( )不是随便调用的,它必须包含在对应的synchronzied语句中,无论是wait( )或notify( )都需要首先获得目标对象的一个监视器。

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( )不会释放任何资源。

  • 挂起(suspend)和继续执行(resume)线程。被挂起(suspend)线程必须要等到resume( )操作后才能继续执行。不推荐使用suspend()来挂起线程,因为它暂停同时不会释放任何资源。
  • 等待线程结束(join)和谦让(yield)

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 final static int MIN_PRIORITY = 1    // 优先级最低
  • public final static int NORM_PRIORITY = 5
  • public final static int MAX_PRIORITY = 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);
	}

}

猜你喜欢

转载自blog.csdn.net/haima95/article/details/81872655