zoukankan      html  css  js  c++  java
  • java并行程序基础

    1 进程

    	1 进程和线程之间的关系:进程是线程的容器。
    

    2 线程


    1 线程的执行流程:

    • **线程调用start()**方法时,表示线程开始执行;
    • 当线程执行时,处于RUNNABLE状态,表示线程所需的一切资源都准备好了;
    • 当线程遇到synchronized同步块,就进入BLOCED阻塞状态,这时线程会暂停执行,直到获得请求的锁。

    2 WAITING 和 TIMED_WAITING:

    • WAITING 和 TIMED_WAITING都表示等待状态区别是WAITING会进入一个无时间限制的等待,TIMED_WAITING会进行一个有限的等待。
    • WAITING 的线程是在等待一些特殊事件:比如,wait()方法等待notify()方法,join等待目标线程的终止。
    • 线程的基本操作
      1 注意:不要用run()开启新线程,它只会在当前线程中,串行执行run()中的代码。
      2 创建线程的两种方式:实现Runnable接口;继承Thread类;
      //Runnable接口
       public interface Runable{
    		public abstract void run();
    }
    

    4 终止线程:

    • 为何终止:一般线程会自动终止,但是一些服务端的后台线程会常驻系统,它们本身是无穷循环,用于提供某些服务。
      1 使用stop()? //未来将被废弃;
      stop()有一个局限性: 当写入两个相同的值,当线程写到一半,并强行终止,对象就会被写坏。从而出现
      写读不一致的现象。stop发生的问题很难排查!,除非你很清楚你在干什么,否则不要随便使用stop()去
      终止一个线程
      2 自行决定何时退出
      1 在自己的线程类中增加一个stopMe()方法:
    ChangeObjectThread extends Thread{
    	volatile Boolean stopme = false;
    	public void stopMe(){
    		stopme = true;
    	}
    	public void run(){
    		while(true){
    			if(stopme){
    				//退出位置
    				pringtln(“exit by stop me!”);
    				break;
    			}
    			//你的代码
    		}
    	}
    }
    

    5 线程中断

    • 什么是线程中断:
      线程中断不会使线程立即退出,而是给线程一个通知,告知目标线程,有人希望你退出了!至于**目标线程接到通知后
      如何处理,由自己决定。**这点很重要,避免了stop()那样出现的问题。
    • 与线程中断的三种方法:
      1 Thread.interrupt():通知目标线程中断,设置中断标志
      2 Thread.isInterrupted():检查中断标志,判断当前线程是否被中断。
      3 Thread.interrupted():判断当前线程中断状态,但同时会清楚当前线程中断标志位状态
      3 如何操作:
    main(String[] args){
    	Thread t1 = new Thread(){
    		public void run(){
    			while(true){
    				//中断处理程序
    				if(Thread.currentThread().isInterrupted()){
    					sysou("Interrupted!");
    					break;
    				}
    				...;
    				Thread.yield();//线程让步
    			}	
    		}
    	}
    	t1.start();
    	Thread.sleep(2000);
    	t1.interrupt();//单单这里中断是没有效果的,一定要写入上面的中断处理程序
    }
    
    • 中断的方法比stopme()标记手法功能更强劲
    • 强劲之处:在循环体中,出现wait()或sleep(),只能通过中断识别。
      Thread.sleep()方法会抛出一个InterruptedException中断异常,当线程sleep()处于休眠时,如果被中断,异常就会产生。这时会清除中断标记位,如果不处理,在下次循环中无法捕获到这个中断,故在异常处理中,再次设置中断标记位

    6 等待(wait)和通知(notify)
    两个方法签名

    • 等待(wait)
      当线程A中,调用了obj.wait(),线程A停止继续执行,进入现场等待池中等待,让出系统资源,既让出了锁资源。线程A会一直等到其他线程调用obj.notify()位置。obj对象成为多个线程的有效通信手段。
    • wait 和 notify 如何工作?
      当一个线程调用obj.wait(),那么它就进入obj的等待队列。这个等待队列可能有多个线程。当obj.notify()被调用时,它会从等待队列中水机选选择一个线程,将其唤醒(这里的唤醒是允许其去获得锁)。这个选择时随机的。
    • 除了notify可以唤醒线程,还有notifyAll,只是notifyAll唤醒所有的线程,而不是一个。
    • 注意点
      object.wait()不是随便调用的,必须包含在对应同步语句中,无论是wait 还是 notify都需要首先获得目标对象
      (obj)的一个监视器。
    public class SimpleWN{
    	public static void main(String[] args) {
    		Thread t1 = new T1();
    		Thread t2 = new T2();
    		t1.start();
    		t2.start();
    	}
    	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 for obj");
    					obj.wait();
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(System.currentTimeMillis() + ":T1 end!");
    			}
    			super.run();
    		}
    	}
    	
    	public static class T2 extends Thread{
    
    		@Override
    		public void run() {
    			// TODO Auto-generated method stub
    			synchronized (obj) {
    				System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread!");
    				obj.notify();
    				System.out.println(System.currentTimeMillis() + ":T2 end!");
    				
    				try {
    					Thread.sleep(2000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			super.run();
    		}
    		
    	}
    }
    
    • wait 和 sleep :都是等待若干时间,区别是wait可以被唤醒,还有wait方法会释放目标对象的锁。而sleep()不会。

    7 挂起(suspend)和继续执行(resume)

    • 被挂起的线程,必须等到resume()操作后,才能继续执行。
    • suspend是被废弃的方法:被挂起的线程在导致线程暂停的同时,并不会释放任何锁资源。导致其他线程想要访问被它暂用的锁资源,都会受牵连。
    • 怎样用一种可靠的方式使用suspend()?
      利用wait()和notify()方法,在应用层实现suspend 和resume 功能。
    package com.sean.thread;
    
    public class GoodSuspend{
    	public static Object u = new Object();
    	
    	public static void main(String[] args) throws InterruptedException {
    		ChangeObjThread t1 = new ChangeObjThread();
    		ReadObjThread t2 = new ReadObjThread();
    		t1.start();
    		t2.start();
    		Thread.sleep(1000);
    		t1.suspendMe();
    		System.out.println("suspend t1 2 sec");
    		Thread.sleep(20000);
    		System.out.println("resume t1");
    		t1.resumeMe();
    	}
    	
    	public static class ChangeObjThread extends Thread{
    		volatile boolean suspendme = false;
    		public void suspendMe(){
    			suspendme = true;
    		}
    		
    		public void resumeMe() {
    			suspendme = false;
    			synchronized(this){
    				notify();
    			}
    		}
    		@Override
    		public void run() {
    			while (true) {
    				synchronized (this) {
    					while (suspendme) {
    						try {
    							wait();		//运用等待实现挂起
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    					}
    				}
    				synchronized(u){
    					System.out.println("in ChangeObjThread");
    				}
    				Thread.yield();
    			}
    		}
    	}
    	
    	public static class ReadObjThread extends Thread{
    		
    		@Override
    		public void run() {
    			while(true){
    				synchronized (u) {
    					System.out.println("in ReadObjThread");
    				}
    				Thread.yield();
    			}
    		}
    		
    	}
    }
    

    8 等待线程结束(join)和谦让(yield)

    public final void join() throws InterruptedException
    public final synchronized void join(long millis) throws InterruptedException
    
    • join()表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。yield()给出了一个
      最大等待时间,如果超过时间,就会继续往下执行。

    • join()的本质是调用线程wait()在当前线程对象实例上当线程执行完成后,被等待的线程 在退出时调用notifyAll通知所有线程继续执行,因此不要在THread对象上使用类似wait、notify等方法,可能会影响api工作。

    • Thread.yield 一旦执行,会使当前线程让出CPU。但是并不代表不执行,会有一定几率分配到

    public class GoodSuspend {
    	public volatile static int i = 0;
    
    	public static class AddThread extends Thread {
    
    		@Override
    		public void run() {
    			for (i = 0; i < 100000000; i++) {
    
    			}
    		}
    	}
    
    	public static void main(String[] args) throws InterruptedException {
    		AddThread at = new AddThread();
    		at.start();
    		at.join();
    		System.out.println(i);
    	}
    }
    

    3 volatile 与java内存模型(JMM)

    1 java内存模型:围绕着原子性、有序性和可见性展开的。但是可以利用关键字或特殊操作,告诉虚拟机有些地方需要特别
    注意,比如 volatile。
    2 volatile:

    • 使用 volatile 去申明一个变量时,在程序范围内的所有其他线程“可见”的,虚拟机就会小心处理这种情况;
    • volatile并不能代替锁,也无法保证一些复合操作的原子性,当两个线程同时修改数据时,依然会产生冲突
    • volatile能保证数据的可见性有序性
    //volatile无法保证一些复合操作
    public class GoodSuspend {
    	static volatile int i = 0;
    	public static class PlusTask implements Runnable{
    		@Override
    		public void run() {
    			for (int k = 0; k < 10000; k++){
    				i++;
    			};
    		}
    	}
    	public static void main(String[] args) throws InterruptedException {
    		Thread[] task = new Thread[10];
    		for (int i = 0; i < 10; i++) {
    			task[i] = new Thread(new PlusTask());
    			task[i].start();
    		}
    		for (Thread thread : task) {
    			thread.join();
    		}
    		System.out.println(i);
    	}
    }
    

    4 线程组


    1. 和一个篮子一样,篮子里的每个水果是线程。
    public class ThreadGroupName implements Runnable {
    	static volatile int i = 0;
    	public static void main(String[] args) {
    		ThreadGroup tg = new ThreadGroup("PrintGroup");
    		Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");	//定义线程并加入到线程组
    		Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
    		t1.start();
    		t2.start();
    		System.out.println(tg.activeCount());
    		tg.list();
    	}
    	@Override
    	//输出线程信息
    	public void run() {
    		String groupAndName = Thread.currentThread().getThreadGroup().getName() + Thread.currentThread().getName();
    		while (true) {
    			System.out.println("I am group " + groupAndName);
    			try {
    				Thread.sleep(3000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

    5 驻守后台:守护线程(Daemon)


    • 守护线程:系统的守护者,默默完成系统的一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。与之相对应的是用户线程(系统的工作线程)。
    • 要在start()之前设置守护线程,否则抛出异常,当成用户线程使用。
    Thread t = new DaemonT();
    t.setDaemon(true);	//设置守护线程
    t.start();
    

    6 线程优先级:先做重要的事


    • 优先级越高,资源竞争越有优势,更可能抢占资源。
    • 优先级产生的后果无法精准控制,可能出现低优先级一直抢占不到资源,一直无法运行,从而产生饥饿现象.
    • 自己在应用层解决线程调度的问题。
      • 数字越大优先级越高;有效范围在1-10。
    public class PriorityDemo{
    	public static void main(String[] args) {
    		//高权限和低权限的线程做同一件事
    		HightPriority h = new HightPriority();
    		LowPriority l = new LowPriority();
    		h.setPriority(Thread.MAX_PRIORITY);//线程设置为高权限
    		l.setPriority(Thread.MIN_PRIORITY);//线程设置为低权限
    		l.start();
    		h.start();
    	}//~做同一件事情总是高权限线程先完成
    	public static class HightPriority extends Thread{
    		static int count = 0;
    		public void run(){
    			while(true){
    				synchronized (PriorityDemo.class) {
    					//使用同步产生资源竞争,而权限高低会去竞争锁资源
    					count++;
    					if (count > 1000000) {
    						System.out.println("HightPriority is complete!");
    						break;
    					}
    				}
    			}
    		}
    	}
    	public static class LowPriority extends Thread{
    		static int count = 0;
    		public void run(){
    			while(true){
    				synchronized (PriorityDemo.class) {
    					count++;
    					if (count > 1000000) {
    						System.out.println("LowPriority is complete!");
    						break;
    					}
    				}
    			}
    		}
    	}
    }
    

    7 线程安全与synchronized


    • 多线程的写入冲突(线程不安全):当两个线程同时修改一个共享对象,产生覆盖的现象
      比如,有一个共享对象int i,线程A和线程B都对i 累加10000次,我们想要的结果是20000,但我们得到的结果总是小于这个值。

    • 即使用volatile这种关键字也是如此,因为volatile不能解决两个线程同时修改共享对象的问题。

    public class AccountingVol implements Runnable{
    	public static volatile int i = 0;
    	@Override
    	public void run() {
    		for (int k = 0; k < 1000000; k++) {
    			i++;
    		}
    	}
    	public static void main(String[] args) throws InterruptedException {
    		AccountingVol v1 = new AccountingVol();
    		Thread t1 = new Thread(v1);  //此行和下面一行的Runnable实例一定要是同一个,这样才能保证在两个线程工作时,关注到同一个对象锁上,从而保证线程安全。
    		Thread t2 = new Thread(v1);
    		t1.start();
    		t2.start();
    		t1.join();	//主线程愿意等到该线程执行完再执行
    		t2.join();
    		System.out.println(i);
    	}
    }
    
    • 使用 synchronized 来解决线程安全的问题
      • 用法:
        • 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
        • 直接作用于实例对象:相当于对当前实例加锁,…。
        • 直接作用于静态方法:相当于对当前类加锁,进入同步块要获得当前类的锁。
      • 当线程进入被synchronized包裹的代码块,必须先请求锁,如果有其他线程正在持有这把锁,则新到的线程就必须等待。
    //将上例中的run加上一个同步块
    synchronized (v1) {		//同步块,只有一个持有锁的线程可进入
    	for (int k = 0; k < 1000000; k++) {
    		i++;
    	}
    }
    
    • 可将synchronized关键字作用于一个实例方法:意思是进入该方法前,线程需获得当前对象实例的锁。
    //上面同步块代码转为一个方法,这种事获取当前对象实例的锁
    public synchronized void increase(){
    	i++;
    }
    for (int k = 0; k < 1000000; k++) {
    	this.increase();
    }
    
    
    • 想要保证两个线程的线程安全,必须要两个线程都指向同一个Runnable实例接口,这样才能使两个线程关注到同一个对象锁上,既要使用同一把锁,才能保证线程安全,使用下面方法也可以正确执行。
    //这样使用的是当前类的锁,即使不是同一个Runnable对象,也可以正确执行
    public static synchronized void increase(){
    	i++;
    }
    

    8 程序中的幽灵:隐藏的错误

    • 案例:比如两个数相加导致int的溢出,就会导致出现负数的情况

    • 并发下的 ArrayList:ArrayList 是不安全的,可用 Vector 代替 ArrayList

    • 并罚下的诡异的 HashMap:

      • 可能有三种情况:
        • 1 程序正常结束;2 程序正常结束,不符合预期; 3 程序死循环(链表遭到破坏,链表成环);
    • 错误的加锁:

      • 最好别加在Integer对象上,Integer对象在做++运算时是创建一个新的Integer对象的,String对象也是如此。
  • 相关阅读:
    编写登陆认证程序
    模拟实现一个ATM + 购物商城程序
    三级菜单
    12 场景制造
    11 Unity Editor界面
    3.7练习题
    Coolite 弹窗妙用
    Nginx反向代理 实现Web负载均衡
    MySQL 架构
    Lnmmp
  • 原文地址:https://www.cnblogs.com/sean-zeng/p/11024771.html
Copyright © 2011-2022 走看看