JAVA多线程高并发1

JAVA多线程

1.并行与并发

并发:同一个CPU处理多个线程,哪个线程获取到CPU的时间片,则执行该线程,即CPU的轮询调度。
并行:多个CPU同时处理多个线程。现在的多核心CPU可以做到真正的并发
在这里插入图片描述

2.同步和异步

同步:调用方必须等待响应方执行完毕才返回(A接口调用B接口,A等待B接口的相应结果
使用场景:在编排的流程中,必须等待拿到响应结果才能去做下一步操作,且在实时链路中相互之间有串联或关联数据的
异步:非阻塞式调用,立即返回,调用方无需等待响应方返回实际结果,响应方会通过状态、通知或回调来告知调用方
使用场景:主线程中提交耗时任务到线程池,然后通过Feture来异步获取任务执行结果,这里也可以由异步任务发消息等途径来通 知主线程。注意:主线程会一直等待(阻塞)future.get()的结果,直到结果返回
在这里插入图片描述

3.线程的5种状态

NEW:线程创建但是还没有调用start()方法
RUNNABLE :可运行线程的状态,线程已经在JVM虚拟机中执行,但是可能需要等待操作系统资源,如处理器。RUNNABLE包括RUNNING和READY
BLOCKED :阻塞的线程意味着正在等待监视器锁,来进入或重入synchronized代码块或者方法
WAITING :等待状态,需要其他线程中断或通知来唤醒
TIMED_WAITING :定时等待状态,在指定等待时间后返回,或提前被其他线程中断或通知返回
TERMINATED :终止线程的线程状态,线程已执行完成
在这里插入图片描述
Thread.yield():线程让步,使用该方法的线程会让出cpu时间片,然后和其它线程一起去竞争cpu时间片
Thread.sleep():线程休眠,线程让出cpu时间片,时间到了之后cpu会继续执行该线程,注意:cpu不会释放当前线程所占有的锁
Thread.join():等待该线程死亡/终止,当前线程会等待调用该方法的线程执行完毕后才能继续执行,threadA.join(),threadB.join()
Object.wait():线程等待,调用该方法的线程必须先持有锁,例如在synchronized代码块内,wait() 方法会释放锁,必须调用notify或者nofityAll来唤醒,涉及到线程之间的通信

4.死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,产生原因:两个及以上的线程,抢占2把及以上的锁,抢占锁的顺序不一致
死锁避免:
1.不使用锁,不使用2把及以上的锁
2.必须使用2把及以上锁的时候,确保在整个应用程序中对获取锁的顺序是一致的
3.尝试获取具有超时释放的锁,例如Lock中的tryLock来获取锁
4.当发生了Java-level的锁时,重启程序来干掉进程/线程

5.Demo

Thread.sleep使用:

package com.s2020;
/**
 * 
 * @author chenw
 * 2020/02/08
 *
 */
public class SleepDemo {
	public static void main(String[] args) {
		Thread thread1 = new Thread(()->{
			System.out.println(Thread.currentThread().getName()+"开始休眠");
			try {
				//线程休眠3s
				Thread.sleep(3000);
				System.out.println(Thread.currentThread().getName()+"结束休眠");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}) ;
		Thread thread2 = new Thread(()->{
			System.out.println(Thread.currentThread().getName()+"开始休眠");
			try {
				//线程休眠3s
				Thread.sleep(3000);
				System.out.println(Thread.currentThread().getName()+"结束休眠");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}) ;
		thread1.start();
		thread2.start();
	}

}

运行结果:

Thread-1开始休眠
Thread-0开始休眠
Thread-0结束休眠
Thread-1结束休眠

Thread.join

package com.s2020;
/**
 * 
 * @author chenw
 * 2020/02/08
 */
public class JoinDemo {
	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = createThread(100, "线程1");
		Thread thread2 = createThread(200, "线程2");
		thread1.start();
		thread2.start();
		//thread1运行结束主线程才继续执行下面代码
		thread1.join();
		//thread2运行结束主线程才继续执行下面代码
		thread2.join();
		System.out.println("主线程运行结束...");
	}
	
	public static Thread createThread(int number,String threadName) {
		return new Thread(()->{
			int sum = 0;
			for(int i=0;i<=number;i++) {
				sum = sum+i;
			}
			try {
				//睡眠5s方便测试
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"的计算结果:"+sum);
		},threadName);
	}

}

运行结果

线程2的计算结果:20100
线程1的计算结果:5050
主线程运行结束...

Thread.yield()

package com.s2020;
/**
 * 
 * @author chenw
 * 2020/02/08
 *
 */
public class YieldDemo {
	public static void main(String[] args) {
		new Thread(()->{
			for(int i=0;i<10;i++) {
				if(i==5) {
					//线程让步,让出当前cpu的时间片,和其它线程一起去竞争cpu的时间片
					Thread.yield();
				}
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		},"线程1" ).start();
	}
}

结果:

线程1:0
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9

synchronized 同步代码块
演示2个窗口的售票,同步代码块,线程获取对象的锁(锁对象)和获取类的锁

package com.s2020;
/**
 * 
 * @author chenw
 * 2020/02/08
 *
 */
public class SynchronizedBlock {
	private static Object obj = new Object();
	private int window1 = 0; //窗口1的票数
	private int window2 = 0; //窗口2的票数
	private int count = 10000; //总票数

	public static void main(String[] args) throws InterruptedException {
		SynchronizedBlock synchronizedBlock = new SynchronizedBlock();
		new Thread(()->{
			while(synchronizedBlock.count>0) {
				synchronizedBlock.sellTicket2();
				//二选一
				//synchronizedBlock.sellTicket();
			}
		},"窗口1").start();
		new Thread(()->{
			while(synchronizedBlock.count>0) {
				synchronizedBlock.sellTicket2();
				//二选1
				//synchronizedBlock.sellTicket();
			}
		},"窗口2").start();
		//睡眠3s,方便观察
		Thread.sleep(3000);
		System.out.println("窗口1卖了:"+synchronizedBlock.window1+"张票");
		System.out.println("窗口2卖了:"+synchronizedBlock.window2+"张票");

	}
	public void sellTicket() {
		//多个线程去竞争obj对象的锁,获得对象的锁的线程执行同步代码块的代码,
		//同一时刻只能有一个线程获得对象的锁,同步锁/非公平锁/可重入锁
		synchronized (obj) {
			if(count>0) {
				count--;
				if(Thread.currentThread().getName().equals("窗口1")) {
					window1++;
				}else {
					window2++;
				}
			}
		}
	}
	
	public void sellTicket2() {
		//多个线程获取类的锁
		//同一时刻只能有一个线程获得对象的锁,同步锁/非公平锁/可重入锁
		synchronized (SynchronizedBlock.class) {
			if(count>0) {
				count--;
				if(Thread.currentThread().getName().equals("窗口1")) {
					window1++;
				}else {
					window2++;
				}
			}
		}
	}


}

窗口1卖了:6068张票
窗口2卖了:3932张票

synchronized同步方法(静态方法属于类,非静态通过对象调用,但是要同一个对象才有作用):

package com.s2020;
/**
 * 
 * @author chenw
 * 2020/02/08
 *
 */
public class SellTicket2 {
	private static int ticketCount = 10000;
	private static int threadFirstSellCount;
	private static int threadSecondSellCount;

	public static void main(String[] args) throws InterruptedException {
		sell();
	}
	public static void sell() throws InterruptedException {
		Thread thread1 = new Thread(()-> {
			while(ticketCount>0) {
				sellCount();
			}
		},"窗口1");
		
		Thread thread2 = new Thread(()-> {
			while(ticketCount>0) {
				sellCount();
			}
		},"窗口2");
		
		thread2.start();
		thread1.start();

		Thread.sleep(3000);
		System.out.println("窗口1卖了:"+threadFirstSellCount);
		System.out.println("窗口2卖了:"+threadSecondSellCount);
	}
	
	//同步方法,静态的属于类,也可以是非静态通过对象调用
	private static synchronized void sellCount() {
		if(ticketCount>0) {
			ticketCount--;
			if(Thread.currentThread().getName().equals("窗口1")) {
				threadFirstSellCount++;
			}else {
				threadSecondSellCount++;
			}
		}
	}
}

窗口1卖了:5119
窗口2卖了:4881

线程池+Callable接口演示wait(),nofityAll和notify
Object.wait()和notifyAll(),notify():线程的等待和唤醒,在调用wait方法前必须先获取对象的锁。wait必须通过nofity或者notifyAll来唤醒,线程被唤醒后去获取cpu的时间片,获取直到继续执行接下的代码。
两个线程打印A1B2C3…Z26的例子

package com.s2020;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * 
 * @author chenw
 * 2020/02/08
 *
 */
public class PrintDemo {
	private static Object obj = new Object();
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//线程池
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		/**
		 * Future维护线程的执行结果,异步调用,可以通过future.get()方法获取线程执行的结果
		 * 
		 */
		Future<String> future = executorService.submit(new Callable<String>() {
			//重写Callable接口的call方法
			public String call() throws Exception {
				int i = 1;
				//线程获取对象的锁,得到锁则执行,否则等待
				synchronized (obj) {
					while(i<=26) {
						//唤醒所有在队列中等待的线程
						obj.notifyAll();
						System.out.print(i);
						i++;
						//线程进入等待状态
						obj.wait();
					}
					obj.notifyAll();
				}
				System.out.println();
				return Thread.currentThread().getName()+"执行完毕";
			}
			
		});
		Future<String> future2 = executorService.submit(new Callable<String>() {
			public String call() throws Exception {
				char letter = 'A';
				synchronized (obj) {
					while(letter<='Z') {
						obj.notifyAll();
						System.out.print(letter);
						letter++;
						obj.wait();
					}
					obj.notifyAll();
				}
				System.out.println();
				return Thread.currentThread().getName()+"执行完毕";
			}
		});
		//为了方便观察结果
		Thread.sleep(3000);
		//future.get()方法获取Callable的call方法返回的结果,
		//在结果没有返回之前主线程会一直等待(阻塞),直到结果返回在执行下一行
		System.out.println(future.get());
		System.out.println(future2.get());
	}
}

运行结果:

1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z

pool-1-thread-1执行完毕
pool-1-thread-2执行完毕

ReentrantLock
ReentrantLock 和synchronized 都是可重入锁,独占锁
synchronized是非公平锁
ReentranLock(true 公平锁/false 非公平锁);默认为非公平锁
区别:
synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活
ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂
ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;
而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁

package com.s2020;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class PrintReentrantLockDemo {
	//可重入锁,默认为非公平锁
	private static ReentrantLock lock = new ReentrantLock();
	//线程通信
	private static Condition condition = lock.newCondition();
	public static void main(String[] args) {
		print();
	}
	public static void print() {
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		executorService.submit(()->{
			lock.lock();
			try {
				for(int i=1;i<27;i++) {
					//唤醒在等待的线程,类似于Object.notifyAll()
					condition.signal();
					System.out.print(i);
					//进入等待状态,类似于Object.wait()
					condition.await();
				}
			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				condition.signal();
				//释放锁,获取锁的次数和释放锁的次数必须一致
				lock.unlock();
			}
		});
		executorService.submit(()->{
			lock.lock();
			try {
				for(char a='A';a<='Z';a++) {
					condition.signal();
					System.out.print(a);
					condition.await();
				}
			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				condition.signal();
				lock.unlock();
			}
		});
	}

}

运行结果:

1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值