并发编程

一.线程基础概念

1.高并发编程的意义、好处和注意事项

好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化

问题:

线程共享资源,存在冲突;

容易导致死锁;

启用太多的线程,就有搞垮机器的可能

2.并行和并发

并行:同一时刻,可以同时处理事情的能力

并发:与单位时间相关,在单位时间内可以处理事情的能力

3.CPU核心数和线程数的关系

同时运行的线程数量和cpu核数量成正比关系,在原始阶段核心数:线程数=1:1  ;使用了超线程技术后---> 1:2

4.CPU时间片轮转机制

时间片轮转算法的基本思想是,系统将所有的就绪进程按先来先服务算法的原则,排成一个队列,每次调度时,系统把处理机分配给队列首进程,并让其执行一个时间片。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序根据这个请求停止该进程的运行,将它送到就绪队列的末尾,再把处理机分给就绪队列中新的队列首进程,同时让它也执行一个时间片。

一个4核cpu同时能执行的线程数最多能有四个,那么为什么在操作线程给我们的感觉又是同时执行了超过4数量的线程呢,这里是cpu在以肉眼不可见的速度进行着线程间的切换,在我们看来就是同时执行了很多线程。cpu的调度又称RR调度,就是执行上上下文切换。

二.线程的基本使用

1.启动线程的三种方式

1.1 继承Thread类创建线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行            体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法来启动该线程。

1.2  通过Runnable接口创建线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2)创建 Runnable实现类的实例,并依此实例作为Thread构造方法参数target来创建Thread对象,该Thread对象才是真正的              线程对象。

(3)调用线程对象的start()方法来启动该线程。

1.3  通过Callable和Future创建线程

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法          的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

 创建类,并通过main方法来调用

/**
 *
 * @author 二万
 *
 */
public class StartThread {
	/**
	 * 实现callable
	 *
	 * @author 二万
	 *
	 */
	public static class CallableThread implements Callable {
		public Integer call() throws Exception {
			return sum();
		}
	}

	/**
	 * 实现那Runnable
	 *
	 * @author 二万
	 *
	 */
	public static class RunnableThread implements Runnable {

		public void run() {
			// 执行的业务逻辑
			sum();
		}

	}

	/**
	 * 继承Thread接口
	 *
	 * @author 二万
	 *
	 */
	public static class ThreadRun extends Thread {
		@Override
		public void run() {
			// 执行的业务逻辑
			sum();
		}

	}
	/**
	 * 线程执行的方法
	 *
	 * @return
	 */
	private static Integer sum() {
		int sum = 0;
		for (int i = 0; i < 100; i++) {
			sum += i;
		}
		System.out.println("当前线程名称:" + Thread.currentThread().getName() + "  sum====" + sum);
		return sum;
	}


	public static void main(final String[] args) throws InterruptedException, ExecutionException {
		// 主线程的名称
		System.out.println(Thread.currentThread().getName());
		// 继承Thread的方式
		Thread th = new ThreadRun();
		th.start();
		// 实现Runable的方式
		Thread thRunable = new Thread(new RunnableThread());
		thRunable.start();
		// 实现Callable的方式,通过FutureTask可以拿到执行结果
		CallableThread caThread = new CallableThread();
		FutureTask result = new FutureTask(caThread);
		Thread callThread = new Thread(result);
		callThread.start();
		Integer sum = result.get();
		System.out.println("得到的计算结果是:" + sum);

		// 直接调用run方法
		th.run();
		thRunable.run();
		callThread.run();
	}
}

通过执行main方法得到的结果如下:

main
当前线程名称:Thread-0  sum====4950
当前线程名称:Thread-1  sum====4950
当前线程名称:Thread-2  sum====4950
得到的计算结果是:4950
当前线程名称:main  sum====4950

 通过调用Thread.currentThread().getName();可以判断主线程main和三种方式都是通过另外启动的线程执行的sum方法。

注意一下,不要通过调用run方法来启动线程,必须通过具体的Thread的start()来启动线程

如果继承Thread的方式直接调用run()方法,则会被当成普通的方法来执行,不会另起线程,而其他他两种方式,连run方法都不会执行。

2.线程的停止

stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。

调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。简单的说,调用interrupt()是把线程种的停止位flag改为true,可以同通过flag来判断是否停止线程。

新建一个类EndThread,里面包括多个线程和一个main方法,main方法作为测试类来测试结果。

public class EndThread {
	private static class UseThread extends Thread {
		public UseThread(final String name) {
			super(name);
		}

		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			while (!Thread.interrupted()) {
				try {
					Thread.sleep(1000);
					System.out.println(threadName + " is run!");
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(final String[] args) throws InterruptedException {
		Thread endThread = new UseThread("endThread");
		endThread.start();
		Thread.sleep(2000);
		endThread.interrupt();
		System.out.println(endThread.isInterrupted());
		System.out.println(endThread.isInterrupted());
	}
}

执行main方法,测试结果如下 

.........
endThread is run!
endThread is run!
endThread is run!
endThread is run!
true
false

 endThread.isInterrupted()的方法是可以判断endThread当前是否中断。

 endThread.interrupt(),将线程的中断标志位置为true

 Thread.interrupted()  ,这个静态方法,是判断当前线程是否中断,并且置中断标志位为false,换句话说,就算被中断,再调用一次这个方法,则把中断标志位置为false。我们就用main方法作为线程来测试一下

public static void main(final String[] args) throws InterruptedException {
		System.out.println(Thread.interrupted());
		Thread.currentThread().interrupt();
		System.out.println(Thread.interrupted());
		System.out.println(Thread.interrupted());
}

测试结果:
false
true
false

3.线程的生命周期

 线程可分为5个状态

(1).新建(NEW):新创建了一个线程对象。

(2)可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

(3) 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
(4) 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种: 

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

(5) 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

4.关于阻塞状态的几个方法详解

(1)Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。

(2)Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。比如说,你玩游戏机的过程中,其他人看着你玩,你觉得不好意思,然后你就给大家说,我现在把游戏机让出来,我们一起抢谁抢到就是谁的。yield并不会释放锁

(3)t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。

public class UserJoin {
	static class JumpThread extends Thread {

		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "执行了");

		}

	}

	static class Thread1 extends Thread {
		Thread thread;

		Thread1(Thread jumpThread) {
			this.thread = jumpThread;
		}

		@Override
		public void run() {
			try {
				thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName());
		}

	}

	public static void main(String[] args) {
		Thread jumpThread=new JumpThread();
		jumpThread.setName("线程2");
		Thread thread1=new Thread1(jumpThread);
		thread1.setName("线程1");
		thread1.start();
		jumpThread.start();
	}
}
测试结果:
线程2执行了
线程1

可以在线程2(jumpThread),再次调用其它线程(比如说线程3)的join,这样执行顺序就是线程3,线程2,线程1.

(4)obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。

(5)obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

5.线程的优先级

(1) java中的线程优先级具有继承的特性,比如线程1启动线程2,那么线程2的优先级就和线程1的优先级是一样的
(2) 线程的优先级只能确保CPU尽量将执行的资源让给优先级高的线程用,但不保证定义的高优先级的线程的大部分都能先于低优先级的线程执行完。
(3) 线程的优先级具有随机性,也就是高优先级的线程不一定每一次都先执行完。

 所以不建议使用优先级。

6.ThreadLocal

在并发编程中,为了防止为了防止竞态的产生(同一时间访问同一变量,造成数据错乱),我们需要同步机制来确保数据正确,在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

 而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

我们这里新建一个测试类来看看具体结果

public class UseThreadLocal {
	
	//可以理解为 一个map,类型 Map
	static ThreadLocal threadLaocl = new ThreadLocal(){
		@Override
		protected Integer initialValue() {
			return 1;
		}
	};

    /**
     * 运行3个线程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i

测试结果如下

从测试结果可以分析得到,三个线程同时操作ThreadLocal中的值,并不会造成竞态,导致数据错乱。其实对于ThreadLocal可以看作就是一个Map中Thread作为key,共同变量作为值。上面测试类就可以理解为 一个map,类型 Map

7.守护线程

 守护线程和被守护的线程是一起死的,不是同年同月同日生,但是同年同月同日死。

public class DaemonThread {
	
	private static class UseThread extends Thread {
		@Override
		public void run() {
			try {
				try {
					System.out.println(Thread.currentThread().getName()
							+ " I am extends Thread.");
					Thread.sleep(5);//模拟业务执行了5秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} finally {
				System.out.println("...........finally");
			}
		}
	}

	public static void main(String[] args) throws InterruptedException, 
		ExecutionException {
		UseThread useThread = new UseThread();
		useThread.setDaemon(true);
		useThread.start();
		Thread.sleep(2);//模拟业务执行了2秒
		Thread.currentThread().interrupt();
	}
}

执行结果如下

Thread-0 I am extends Thread.

可以看到finally并没有执行。这里是因为在被守护的线程main线程在执行业务2s的之后,调用interrupt后,就中断了main线程并且死掉,而UseThread,因为main的中断,直接死掉,并不会调用finally的语句。

8.synchronized的使用
(1)synchronized(this)锁住当前对象

public class SyncTest implements Runnable {
	private  int count;

	public SyncTest(int count) {
		this.count = count;
	}

	public void run() {
//		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + (count++));
//			}
		}
	}

	public  int getCount() {
		return count;
	}

	public static void main(String[] args) {
		int count=0;
		 SyncTest s1 = new SyncTest(count);
		 SyncTest s2 = new SyncTest(count);
		 Thread t1 = new Thread(s1);
		 Thread t2 = new Thread(s2);
		 
		SyncTest s = new SyncTest(count);
		Thread t3 = new Thread(s);
		Thread t4 = new Thread(s);
//		t1.start();
//		 t2.start();
		 t3.start();
		 t4.start();
	}
}

开启t1.start(),t2.start()时,测试结果如下:

Thread-3:0
Thread-3:2
Thread-3:3
Thread-3:4
Thread-3:5
Thread-2:1
Thread-2:6
Thread-2:7
Thread-2:8
Thread-2:9

开启t3.start(),t4.start()时,测试结果如下:

Thread-3:0                              
Thread-2:1
Thread-2:3
Thread-3:2
Thread-2:4
Thread-3:5
Thread-2:6
Thread-3:7
Thread-3:9
Thread-2:8

通过结果可以看到在synchronized锁住的是对象的时候,一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。为什么在t3和t4确实同时执行呢,因为每个对象都持有不同的锁。

这里值得注意的一点是,虽然synchronized锁住了对象,当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块

(2)给指定对象加锁

  (3)给类加锁,该类的所有对象公用一把锁,保持线程同步

(4)给静态方法加锁,这里的实际效果就是和类加锁是同一效果syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。

/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;
 
   public SyncThread() {
      count = 0;
   }
 
   public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
 
   public synchronized void run() {
      method();
   }
}
 
public class Demo00{
	
	public static void main(String args[]){
		SyncThread syncThread1 = new SyncThread();
		SyncThread syncThread2 = new SyncThread();
		Thread thread1 = new Thread(syncThread1, "SyncThread1");
		Thread thread2 = new Thread(syncThread2, "SyncThread2");
		thread1.start();
		thread2.start();
	}
}

 syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。

9  Java中的volatile 

10.等待和通知

      wait()    对象上的方法

     notify/notifyAll  对象上的方法

    等待方:
       获取对象的锁;
       循环里判断条件是否满足,不满足调用wait方法,
       条件满足执行业务逻辑
   通知方来说
        获取对象的锁;
        改变条件
        通知所有等待在对象的线程

这里我们新建一个快递类,当快递送达到一个新的地方,我们就通知用户。

public class Express {
	public final static String CITY = "ShangHai";
	private String site;/* 快递到达地点 */

	public Express() {
	}

	public Express(String site) {
		this.site = site;
	}

	/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理 */
	public synchronized void changeSite() {
		this.site = "BeiJing";
		notifyAll();
	}

	public synchronized void waitSite() {
		while (CITY.equals(this.site)) {
			try {
				wait();
				System.out.println("check site thread[" + Thread.currentThread().getId() + "] is be notifed.");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("the site is " + this.site + ",I will call user.");
	}
}

新建一个测试类

public class TestWN {
    private static Express express = new Express(Express.CITY);

    /*检查地点变化的线程,不满足条件,线程一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
        	express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++){//三个线程
            new CheckSite().start();
        }
        Thread.sleep(1000);
        express.changeSite();//快递地点变化
    }
}
测试结果如下:
check site thread[14] is be notifed.
the site is BeiJing,I will call user.
check site thread[13] is be notifed.
the site is BeiJing,I will call user.
check site thread[12] is be notifed.
the site is BeiJing,I will call user.
check site thread[11] is be notifed.
the site is BeiJing,I will call user.
check site thread[10] is be notifed.
the site is BeiJing,I will call user.

notify和notifyAll应该用谁?
应该尽量使用notifyAll,使用notify因为有可能发生信号丢失的的情况。因为nofify只是随机唤醒一个线程,有时候会唤醒不需要的线程,导致信号传达错误。

使用wait必须在同步代码块中使用

11. condition的使用

public class LockConditionTest {
	private static Lock lock = new ReentrantLock();
	private static Condition condition = lock.newCondition();
	static int i = 0;

	static class Thread1 extends Thread {
		@Override
		public void run() {
			System.out.println("Thread1.........is running");
			lock.lock();
			try {
				while (i == 0) {
					condition.await();
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
			System.out.println("Thread1.........is notified");
		}
	}

	static class Thread2 extends Thread {
		@Override
		public void run() {
			lock.lock();
			try {
				i = 1;
				condition.signalAll();
				System.out.println("thread2 ....is signal");
			} finally {
				lock.unlock();
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = new Thread1();
		Thread thread2 = new Thread2();
		thread2.start();
		Thread.sleep(20);
		thread1.start();

	}
}

12.LockSupport 的使用

https://www.cnblogs.com/qingquanzi/p/8228422.html

13.

 

你可能感兴趣的:(并发编程)