zoukankan      html  css  js  c++  java
  • 【Java】多线程

    多线程

    1).进程

    在操作系统中可以并发执行的一个任务,采用分时间片(微观串行,宏观并行),由操作系统调度

    2).线程

    是进程中并发执行的一个顺序流程

    	线程组成:
    		a.CPU时间片,由操作系统调度
    		b.内存(JVM内存):堆空间(保存对象,即实例变量,线程共享)、栈空间(保存局部变量,线程独立)
    		c.代码,是由程序员决定
    

    3).Thread类

    此类型的对象就代表了一个线程。线程调用start启动后自动执行run方法,run方法是自定义代码

    	用法:
    	使用继承Thread的类(推荐使用匿名内部类)
    		class MyThread extends Thread{public void run(){  该线程的自定义流程;}}
    		main: Thread t1 = new MyThread();
    			 t1.start();  //线程启动
    			 t1.run();   //线程并未启动,只是最普通的函数调用
    
    	使用实现Runnable接口的类(推荐使用匿名内部类),缺点是不能抛异常,无返回值;可参考下文Callable接口
    		class MyThread implements Runnable{public void run() {  该线程的自定义流程}}
    		main:Runnable rthread = new MyThread();
    			 Thread t2 = new Thread(rthread);
    		     t2.start();
    		
    		//匿名内部类:
    		Thread t = new Thread(new Runnable() {
    		      public void run() {}
                    });
    

    4).线程同步(最重要,有线程池,fork-join框架)

    在多线程环境下,并发访问同一个对象(临界资源),由于彼此间切割此对象的原子性操作,对象的状态就会处于一个不一致的状态。在java中,任意对象(Object o)都有一把互斥锁标记(syjchronized(o))用于分配线程

    ①.synchronized:(同步)互斥锁
    第一种:同步代码块
    	Object o = new Object();
    	synchronized(o) { 需要同步的代码块1}
    synchronized(o) { 需要同步的代码块2}
    		
    总结:当一个线程要执行同步代码块时,必须获得锁标记对象的互斥锁标记,没有获取到时就进入线程阻塞,知道得到互斥锁标记。线程执行同步代码块,执行完毕后自动释放互斥锁标记,归还给锁对象。
    线程是否同步,取决于是否争用同一锁对象
    
    	第二种:同步方法
    			public synchronized void method() {}
    			
    总结:当线程要执行同步实例方法时,必须获得当前对象的互斥锁标记,不能获得就进入阻塞。当获取互斥锁标记执行完同步方法,自动释放互斥锁标记,并归还给对象。
    一个线程可以在持有互斥锁标记的前提下获取其他锁对象的互斥锁标记
    
    ②.Lock:锁接口,提供了比synchronized更广泛的所定义语句(有个实现类为ReentrantLock)
    	ReadWriteLock:读写锁,持有一对读锁和写锁,(有一个实现类ReenTrantReadWriteLock)
    				 读锁:允许分配给多个线程(并发)  readLock();
    				 写锁:最多只允许分配给一个线程(互斥), writeLock();    读锁和写锁两者互斥
    	总结:当线程持有读锁时,不能分配写锁给其他线程,但可以分配读锁;同样在线程持有写锁时,也不能分配读锁和写锁给其他线程。(读读并发、写写互斥、读写互斥)	
    
    	用法:ReadWriteLock rwl = new ReentrantReadWriteLock();
    		  Lock readLock = rwl.readLock();	 //读锁
    		  Lock writeLock = rwl.writeLock(); //写锁
    		  readLock.lock();
    			中间为获得读锁的代码块,如果有try/catch时,可将readLock.unlock();写在finally代码块里
    		  readLock.unlock();
    		  
    		  writeLock用法与此相同
    		  
    ③.join()方法
    	如有线程t1,如果有线程t2要在t1执行完再执行时,则可在t2的代码块中添加t1.join();语句
    	例如:在main函数中有线程t1、t2、t3,当线程执行完在执行输出语句时:
    		public static void main (String[] args) {
    			t1.join();  //将三个线程添加到主线程,并且先执行这三个线程
    			t2.join();
    			t3.join();
    			System.out.println(“输出语句”);
    		}
    ④.线程池ExecutorService es = Executors.newFixedThreadPool(int n);//创建线程池,并设置固定并发线程的个数n,
    	将实现Callable接口(Callable<T>,实现方法public T call(){return null})的线程提交(submit)到线程池中,
    	再用Future<T>异步接收线程结束后的结果,用get()方法获取
    	用法:
    Callable<Integer> t1 = new Callable<Integer>(){
    	public Integer call() { return null;} //需要实现call方法
    };
    ExecutorService es = Executors.newFixedThreadPool(2);//可并发两个线程
    Future<Integer> f1 = es.submit(t1); //将t1提交到线程池,并将执行后的结果给Future对象
    Future<Integer> f2 = es.submit(t2); //如果有两个,那么f1,f2是异步执行,互不干扰
    System.out.println(f1.get());//将执行的结果通过future对象的get()方法获取
    
    ⑤.fork-join框架(重要,自从jdk1.7),也是线程池
    	思想:分治-归并算法,大任务--->小任务--->将小任务的结果合并成完整结果
    	用法:
    	class Task extends RecursiveTask<T> {
    		protected  T  compute() {
    			return T类型数据或null;
    		}
    	}
    	main: ForkJoinPool fjp = new ForkJoinPool();
    		 Task t1 = new Task();
    		 fjp.invoke(new Task());   //将线程放入线程池
    		 与此同时可以在执行再一个当前的线程t2
    		 Task t2 = new Task();
    		 T tmp2 = t2.compute();  //将线程t2执行后的结果返回给tmp2
    		 T tmp1 = t1.join();      //在线程t2中将t1执行后的结果返回给tmp1
    

    5).实现线程安全的方法

    	1).加锁:synchronized,互斥锁,对对象加锁,缺点是并发效率低
    	2).锁分级:ReadWriteLock,分配写锁时,不分配读锁;写锁未分配时,可分配多个读锁
    	3).锁分段:ConcurrentHashMap,对整个Map加锁,分16个片段,分别独自加锁(jdk5 – jdk7)
    	4).无锁算法:CAS比较交换算法(利用状态值)
    		ConcurrentHashMap,利用CAS算法,自从jdk8
    		详解:例如对一个变量n=3进行多线程访问
    			执行下面代码:
    				where(true) {
    					int status = n;   //旧值
    					if(status == n) {  //拿旧值和当前值再次做比较
    						//该线程可对n进行修改操作;假如在判断之前n的值被其他线程修改了,则从新这个					      循环步骤
    					} 
    				}
    	5).尽量回避临界资源,尽可能的使用局部变量
    

    6).线程间的通信(等待 -- 通知 机制)

    	例如:有两个t1,t2线程,有对象Object o = new Object();
    	t1: 如果使用o.wait();必须出现在对o加锁的同步代码块中,此时,t1将会释放它拥有的所有锁标记,并进入o的等待队列(阻塞)和释放CPU资源
    		synchronized(o) { o.wait(); }
    		t2: 此时使用t1所释放的锁标记,利用o.notify() / o.notifyAll(),此方法也有放在对o加锁的同步代码块中,
    	    t2会从o的等待队列中释放一个或全部线程
    
    wait 和 sleep方法的区别是:wait会失去锁标记,而sleep不会失去锁标记
    
    代码例子:
    /**
    		 * 两个线程:	一个输出1 - 52
    		 * 			一个输出A - Z
    		 * 	效果:1 2 A 3 4 B … 52 Z
    		 */
    final Object o = new Object();
    		Thread t1 = new Thread() {
    			public void run() {
    				synchronized (o) {
    					for (int i = 1; i <= 52; i += 2) {
    						int tmp = i + 1;
    						System.out.print(i + " " + tmp + " ");
    						o.notifyAll();
    						try {
    							o.wait();
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    					}
    				}
    			}
    		};
    		t1.start();
    		Thread t2 = new Thread() {
    			public void run() {
    				synchronized (o) {
    					for (char i = 'A'; i <= 'Z'; i++) {
    						System.out.print(i + " ");
    						o.notifyAll();
    						try {
    							o.wait();
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    					}
    				}
    			}
    		};
    		t2.start();
    		t1.join();
    	 	t2.join();
    
  • 相关阅读:
    前端 ---- ajax(2)
    前端 ---- ajax(1)
    前端 ---- 博客项目
    Vue 重复进入相同路由消除警报
    axios和message注册全局变量不一样
    element-ui 的input组件 @keyup.enter事件的添加办法
    前端 ----Express
    MyBatis学习一
    SpringMVC学习一
    JVM学习一
  • 原文地址:https://www.cnblogs.com/jwnming/p/13634007.html
Copyright © 2011-2022 走看看