Java并发编程

1.基础知识

        1.1 线程实现方式

                  1.1.1 Thread

                            继承Thread实现多线程只需要3步

                                1. 继承Thread

                                2. 重写run 方法

                                3.调用start方法,启动线程

                        代码如下:

/**
 * 继承Thread  
 * 重写run 方法
 * 调用Start 方法 启动线程
 *
 */
public class MyThread extends Thread {
	
	private String url;
	public MyThread(String url) {
		this.url=url;
	}
	public static void main(String[] args) {
		String bd="baidu.com";
		MyThread myThread=new MyThread(bd);
		myThread.setName("myThread");
		myThread.start();
		System.out.println(Thread.currentThread().getName()+" start");
	}
	@Override
	public void run() {
		System.out.println("down load from "+this.url);
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("complete task");
	}
}

        

                  1.1.2 Runnable

                        实现Runnable接口,如下几步

                                1.定义类实现 Runnable 接口

                                2.实现 run() 方法,编写线程执行逻辑

                                3.创建线程对象实例,调用 start() 方法启动线程  代码如下:

/**
 * 实现接口Runnable
 * 重写run 方法
 * 创建线程对象,传递Runnable接口实例,调用 start() 方法启动线程
 *
 */
public class MyRunnable  implements Runnable{
	
	public static void main(String[] args) {
		String sohu="sohu.com";
		Thread t1=new Thread(new MyRunnable(sohu));
		t1.start();
		System.out.println(Thread.currentThread().getName()+" start");
	}
	
	private String url;
	public MyRunnable(String url) {
		this.url=url;
	}
	@Override
	public void run() {
		System.out.println("down load from "+this.url);
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		System.out.println("complete task");
	}
}

                        

                  1.1.3 Callablle

                        Callable可以阻塞获取线程执行结果  实现多线程,步骤如下:

                                1.定义实现类实现Callable接口 定义返回值类型

                                 2.重写call 方法 执行逻辑

                                 3.定义FutureTask 传入Callable接口实现类

                                 4.定义Thread线程,传入FutureTask 实例,调用start方法启动线程

                                  5.调用FutureTask的get方法,阻塞获取线程执行结果

                代码如下:

/**
 * 1.实现Callable接口 定义返回值类型
 * 2.重写call 方法 执行逻辑
 * 3.定义FutureTask 传入Callable接口实现类
 * 4.定义Thread线程,传入FutureTask 实例,调用start方法启动线程
 * 5.调用FutureTask的get方法,阻塞获取线程执行结果
 *
 */
public class MyCallable implements Callable<Integer>{
	
	public static void main(String[] args) throws Exception {
		FutureTask<Integer> task=new FutureTask<Integer>(new MyCallable(100));
		new Thread(task).start();
		System.out.println(Thread.currentThread().getName()+" start");
		Integer result=task.get();
		System.out.println("线程执行结束,获的结果为:"+result);
	}
	
	@Override
	public Integer call() throws Exception {
		System.out.println("开始计算 "+countNum+"累积总和");
		int total=0;
		for(int i=0;i<countNum;i++) {
			total+=i;
		}
		Thread.sleep(1000);
		System.out.println("计算完成,累积总和:"+total);
		return total;
	}
	
	private Integer countNum;
	public MyCallable(Integer countNum) {
		this.countNum=countNum;
	}
}

                

        1.2 线程状态     

                        NEW   初始状态 Thread t=new Thread()

                        READY 就绪状态  t.start()等CPU调度                                                            Thread.yield()/Object.notify()/Object.notifyAll()/LockSupport.unpark()

                        RUNNING 运行状态 CPU正在调度

                        BLOCKED 阻塞状态 synchronized

​​​​​​​​​​​​​​        ​​​​​​​        ​​​​​​​        WAITING    等待状态 Thread.join/LockSupport.park()/Object.wait()

​​​​​​​​​​​​​​        ​​​​​​​        ​​​​​​​        TIMED_WAITING 超时等待 Thread.join/LockSupport.park()/Object.wait()

​​​​​​​​​​​​​​        ​​​​​​​        ​​​​​​​        TERMINATED 终止状态

        1.3 终止线程

                 终止线程方式有2种,建议使用Interupted

                1.3.1 stop

                        上代码:

/**
 * stop 会立即终止线程,不建议使用,会使的后续runTask方法执行未完成
 *
 */
public class ThreadStopDemo {

	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(()->{
			int k=0;
			while(k<10) {
				k++;
				try {
					runTask(k);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"t1");
		t1.start();
		Thread.sleep(5000);		
		System.out.println("t1 stop over");
		t1.stop();

	}
	private static void runTask(int k) throws InterruptedException {
		System.out.println("第"+k+"次任务开始");
		Thread.sleep(1000);
		System.out.println("stepA over");
		Thread.sleep(1000);
		System.out.println("stepB over");
		Thread.sleep(1000);
		System.out.println("stepC over");
		System.out.println("第"+k+"次任务结束");
	}
}

                          执行结果如图:

                                   

                        眼尖的朋友就会发现,第二次任务stepB,stepC丢失了,这是什么问题?因为

stop 会立即终止线程,不建议使用,会使的后续runTask方法执行未完成  ,有没有一种更好的方式结束线程呢?可不可以在方法执行时候check一个共享变量flag,如果 循环过程中发现flag 变了,可以在方法执行结束时候退出循环,结束当前任务,这似乎可行。万能的java之父不可能没想到这一点,于是有了Interupted

                1.3.2 Interupted

                        Interupted 如何优雅的中断线程呢,话不多说,上代码

/**
 * Thread.currentThread().isInterrupted() 获取当前线程的中断标记
 * t1.interrupt()  中断当前线程,给当前线程打标记
 * 注意 线程里面调用Thread.sleep 一定要在异常里面重新打标Thread.currentThread().interrupt(),否则会出现异常
 */
public class ThreadInteruptedDemo {

	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(()->{
			int k=0;
			while(!Thread.currentThread().isInterrupted()) {
				k++;
				runTask(k);
			}
		},"t1");
		t1.start();
		TimeUnit.MILLISECONDS.sleep(5000);	
		System.out.println("t1 stop over");
		t1.interrupt();
	}
	private static void runTask(int k) {
		System.out.println("第"+k+"次任务开始");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
		System.out.println("stepA over");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
		System.out.println("stepB over");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
		System.out.println("stepC over");
		System.out.println("第"+k+"次任务结束");
	}
}

                        执行结果如图:

                        

                          从图中可以看出,t1 stop over 结束指令发出来后,stepB,stepC方法依然执行完成了,这就是我们想要的效果。眼尖的网友会发现 在Thread.sleep 里面我们多次捕获异常并且执行了Thread.currentThread().interrupt(); 这是因为 在t1 执行了t1.interrupt()之后,此时t1线程里面runTask方法里面如果调用Thread.sleep 会抛出InterruptedException异常,这个异常会使得t1.interrupt()打标功能失效。所以需要在捕获异常里面重新打标,调用interrupt方法

 * Thread.currentThread().isInterrupted() 获取当前线程的中断标记

 * t1.interrupt()  中断当前线程,给当前线程打标记

 * 注意 线程里面调用Thread.sleep 一定要在异常里面重新打标Thread.currentThread().interrupt(),否则会出现异常

                

        1.4 多线程安全问题

                如下代码

public class ThreadSafeDemo {
	public static int count=0;
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(()->{
			for(int i=0;i<50000;i++) {
					count++;
			}
		},"t1");
		
		Thread t2=new Thread(()->{
			for(int i=0;i<50000;i++) {
					count++;
			}
		},"t2");
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println("count:"+count);
	}
}

                返回结果:count:61685

                        可能每次运行结果都不同,并不是我们期盼的100000,这是为什么呢?  

                         * 产生问题 根本原因在于CPU高速缓存

                         * count++ 操作可以理解为t1 线程取到缓存count值 此时 未执行++操作,t2也取到count内存值,并且缓存

                         * t1线程执行++操作 并且更新内存count值,此时t2线程++操作,由于t2线程是取的缓存count值++,并未取到t2线程最新值

                         * t2线程++ 旧值,更新到内存 ,这就出现了内存覆盖问题。

                        线程安全问题存在于 多个线程访问修改同一个共享变量产生,由于CPU高速缓存内存变量,使得多个线程改动结果不可见导致,为了解决这个问题,于是有了锁

2.锁

        2.1 Synchronized

​​​​​​​                如何解决上面安全问题呢,可以对多线程执行访问共享资源加同一把锁,保证操作变量时候只有一个线程访问 具体代码如图

                ​​​​​​​               具体代码如下

/**
 * synchronized 对 t1 t2线程加同一把锁,锁obj实例,确保操作 count++时候 t1,t2只有一个线程在运行
 * 注意不能锁不同的对象,否则锁失效比如t2线程锁obj2实例
 */
public class ThreadSynchrDemo {
	public static int count=0;
	public static void main(String[] args) throws InterruptedException {
		Object obj=new Object();
		Thread t1=new Thread(()->{
			for(int i=0;i<50000;i++) {
				synchronized (obj) {
					count++;
				}
			}
		},"t1");
		
		Thread t2=new Thread(()->{
			for(int i=0;i<50000;i++) {
				synchronized (obj) {
					count++;
				}
			}
		},"t2");
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println("count:"+count);
	}
}

                Synchronized 作为关键字可以修饰在 类上,方法上,代码块上。

                修饰在类上

                        类锁,整个类都是线程安全的

                修饰在方法上

                        锁定整个方法,此方法是线程安全的

                修饰在代码块上

                        锁定的是 { } 之间的代码,{}之间的代码是线程安全的

                加锁过程分析

​​​​​​​                       

我们来回顾下多线程产生安全问题的根本原因,一张图解释如下

图中我们可以了解到,多线程安全问题的根本原因是ThreadA还没执行结束MethodA 并且setA最新值到堆内存时候,ThreadB就开始执行了,此时计算A=A+5 就会产生不一致的结果 ,如何保证线程安全呢。于是我们找个中介者,告诉ThreadB, ThreadA已经执行完毕,ThreadB就可以继续通行。那谁来做这个中介者呢,我们还是需要一个共享内存来完成这个事,如图

红框部分必须是原子操作(CAS),这样似乎可以实现加锁操作了,于是我们看看Synchronized是怎么实现加锁的

        2.2 Lock

3.AQS前置知识

4.AQS

5.多线程组件

        5.1CountDownLatch

                5.1.1 功能

                        一个线程(或者多个), 等待另外N个线程完成得以继续执行

                5.1.2 场景

                        现有3个独立任务,3个独立任务完成获取结果

                5.1.3 代码

                        单线程Demo

public class CountDownLatchDemo {

	public static void main(String[] args) throws InterruptedException {
		final Integer[] arr=new Integer[3];
		Long startTime=System.currentTimeMillis();
		arr[0]=runTaskT1();//2s 100
		arr[1]=runTaskT2();//2s 400
		arr[2]=runTaskT3();//1s 200
		Long endTime=System.currentTimeMillis();
		System.out.println("总计花费:"+(endTime-startTime)/1000+"s"); //5s
		System.out.println("求得总和:"+getTotal(arr)); //700
	}

	private static Integer getTotal(Integer[] arr) {
		int total=0;
		for(Integer num:arr) {
			total+=num.intValue();
		}
		return total;
	}

	protected static Integer runTaskT3() throws InterruptedException {
		Thread.sleep(1000);
		return 200;
	}

	protected static Integer runTaskT2() throws InterruptedException {
		Thread.sleep(2000);
		return 400;
	}

	protected static Integer runTaskT1() throws InterruptedException {
		Thread.sleep(2000);
		return 100;
	}
}

                        执行结果如图

                        

                        结果反馈 总计执行了5S 返回700、虽然结果是对的,这显然不是我们想要的结果,我们想要的是runTaskT1, runTaskT2, runTaskT3 能够并发执行,执行完成最后统计结果

                        CountDownLatch Demo

                        

public class CountDownLatchDemo {

	public static void main(String[] args) throws InterruptedException {
		//t1 线程
		final CountDownLatch latch = new CountDownLatch(3);
		final Integer[] arr=new Integer[3];
		Long startTime=System.currentTimeMillis();
		//t1 线程9
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					arr[0]=runTaskT1();
				}catch(Exception e) {
					e.printStackTrace();
				}
				finally {
					latch.countDown();
				}
				
		
			}
		},"t1").start();
		//t1 线程
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					arr[1]=runTaskT2();
				}catch(Exception e) {
					e.printStackTrace();
				}	
				finally {
					latch.countDown();
				}
			}
		},"t2").start();
		//t3 线程
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					arr[2]=runTaskT3();
				}catch(Exception e) {
					e.printStackTrace();
				}	
				finally {
					latch.countDown();
				}
			}
		},"t3").start();
		latch.await();
		Long endTime=System.currentTimeMillis();
		System.out.println("总计花费:"+(endTime-startTime)/1000+"s");
		System.out.println("求得总和:"+getTotal(arr));
	}

	private static Integer getTotal(Integer[] arr) {
		int total=0;
		for(Integer num:arr) {
			total+=num.intValue();
		}
		return total;
	}

	protected static Integer runTaskT3() throws InterruptedException {
		Thread.sleep(1000);
		return 200;
	}

	protected static Integer runTaskT2() throws InterruptedException {
		Thread.sleep(2000);
		return 400;
	}

	protected static Integer runTaskT1() throws InterruptedException {
		Thread.sleep(2000);
		return 100;
	}
}

                执行结果如图

                         

                可以得知 该程序总计运行了2s 总结果700,对比单线程 CountDownLatch执行时长取决于最耗时的那个线程执行时长,而单线程执行时长取决于总耗时,CountDownLatch无疑效率更高

                5.1.4 应用场景   

                         a. 多个互不通信的任务并发执行

                         b.大任务拆分,子任务并发处理,比如 大数据统计,可以将大数据按照一定维度拆分,启动多线程计算拆分的子任务,最后归并处理,forkjoin也可以

                5.1.5 注意事项

                        latch.countDown();一定要写在finally里面 防止程序发生异常,计数的那个线程latch.countDown()并没有正确的执行,计数器没有复位,导致await方法一直处于阻塞状态,这是个致命的问题

                5.1.6 原理分析

        5.2 Semaphore

               5.2.1 功能

                        同一时间段只能有N个线程访问,其他线程等待挂起,等到线程释放,等待线程恢复访问(限流)

               5.2.2 场景

                        现有1000个任务需要多线程运行,为防止CPU过载,允许同时运行线程为4个

                ​​​5.2.3 代码

                        

/**
 * 初始化1000个任务 多线程运行
 * 防止CPU过载,允许执行任务线程最大为4个,执行完当前任务 其他线程方可启动执行
 * @author wuliangfeng
 *
 */
public class SemaphoreDemo {
	
	public static void main(String[] args) {
		Semaphore sp=new Semaphore(4);
		String[] taskIds=getTaskId(100);
		for(int i=0;i<taskIds.length;i++) {
			String taskId=taskIds[i];
			new Thread(()->{
				try {
					sp.acquire();
					runTask(taskId);
				}catch(Exception e) {
					e.printStackTrace();
				}finally {
					sp.release();
				}
				
			},"thread"+i).start();
		}
	}

	private static String[] getTaskId(int num) {
		String[] taskIds=new String[num];
		for(int i=0;i<num;i++) {
			taskIds[i]="Task"+i;
		}
		return taskIds;
	}

	private static void runTask(String taskId) throws InterruptedException {
		System.out.println("当前线程:"+Thread.currentThread().getName()+",获取并处理任务:"+taskId);
		Thread.sleep(100);
		System.out.println(taskId+" 任务处理结束");

	}
}

               5.2.4 原理分析

​​​​​​​

        5.3 CyclicBarrier

        5.4 ForkJoin

        5.5 CompletableFuture

        

6.线程池

7.死锁问题

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值