1. 并发编程的基础概念
- CPU核心数和线程数的关系
核心数
就是同时能运行的线程数,4核CPU:能同时运行4个线程
Intel超线程技术(1:2):把1个物理CPU模拟成2个逻辑CPU,所以4核能同时运行8个线程
- CPU时间片轮转机制
RR调度
,即使只有1个CPU核心数,100个线程,也感觉像是同时运行,因为CPU将时间分片,每个线程循环在时间片内被CPU执行,各个线程极快切换,用户感知上以为是同时在运行。
- 进程和线程
进程
:资源分配的最小单位。操作系统要为进程分配CPU,内存资源、磁盘IO等等资源。进程和CPU没有任何关系。
线程
:CPU调度的最小单位。线程要依附于进程而存在。
- 并行和并发
并行:一个启动了超线程技术的4核CPU,并行度是 8,可以同时运行8个线程。
并发:在xx时间内,并发量是xxx。一定和时间相关,脱离了时间就无意义了。实现并发最常用的机制就是 时间片轮转机制。例:1s内执行100个时间片:并发量100;1s内执行200个时间片:并发量200。
- 高并发编程的意义、好处和注意事项
模块化、异步化、简单化
注意:安全
并不是线程越多越好,假设1000个线程抢夺8个核心来运行,就会不停的进行上下文切换
(即CPU对于需要切换的线程的资源的存取):每次上下文切换大概占用20000个cpu时间周期
2. 天生就是多线程的Java程序
public class OnlyMain{
public static void main(String[] args){
//虚拟机线程管理的接口
ThreadMxBean threadMxBean = ManagementFactory.getThreadMxBean();
//取得线程信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo : threadInfos){
System.out.println("["+threadInfo.getThreadId()+"]"+" "+threadInfo.getThreadName());
}
}
}
测试会新建6个线程:
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
如何启动线程
- 类Thread
- 接口Runnable
- 接口Callable
//方式1:拓展Thread类,new Thread后start()启动
public class NewThread{
private static class UseThread extends Thread{
@Override
public void run(){
super.run();
//do my work
System.out.println("I am in UseThread");
}
}
//方式2 实现Runnable接口
private static class UseRun implements Runnable{
@Override
public void run(){
System.out.println("I am in UseRun");
}
}
//方式3 实现Callable接口,允许有返回值
private static class UseCall implements Callable<String>{
@Override
public String call() throws Exception{
System.out.println("I am in UseCall");
return "CallRequest";
}
}
public static void main(String[] args){
UseThread useThread = new UseThread();
useThread.start();
UseRun useRun = new UseRun();
new Thread(useRun).start();
UseCall useCall = new UseCall();
//包装一下call,FutureTask实现了RunnableFuture接口,
//RunnableFuture接口继承了Runnable和Future接口(java接口可以多继承!)
//Future接口可以获取任务状态、取消任务。
FutureTask<String> futureTask = new FutureTask<>(useCall);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
如何让线程停止
- stop() 废弃
- suspend() 废弃
- resume() 废弃
- destroy() 废弃
- interrupt(); //协作式 而非 抢占式
- static方法interrupted(); //判断是否中断
- isInterrupted() //判断是否中断
public class EndThread{
private static class UseThread extends Thread{
public UseThread(String name){
super(name);
}
@Override
public void run(){
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " interrupt flag = "+isInterrupted());
//测试1
while(true){
System.out.println(threadName + "is running");
}
//测试2
while(!isInterrupted()){
System.out.println(threadName + "is running");
}
//测试3
//当线程被interrupt()中断,flag被设为true,退出while循环,但Thread.interrupted()会修改标志位,所以while外的log会打印出false
while(!Thread.interrupted()){
System.out.println(threadName + "is running");
System.out.println(threadName + "inner interrupt flag = "+isInterrupted());
}
System.out.println(threadName + " interrupt flag = "+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException{
Thread endThread = new UseThread("endThread");
endThread.start();
Thread.sleep(5);
endThread.interrupt(); //对interrupt没做处理,则不起作用
}
}
public class HasInterrputException {
private static SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!isInterrupted()) {
try {
System.out.println("UseThread:"+formater.format(new Date()));
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(threadName+" catch interrput flag is "
+isInterrupted()+ " at "
+(formater.format(new Date())));
//TODO 这句很关键
//虽然这里异常被捕获了,但flag也被设置为了false
interrupt();
e.printStackTrace();
}
System.out.println(threadName);
}
System.out.println(threadName+" interrput flag is " + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread useThread = new UseThread("HasInterrputEx");
useThread.start();
System.out.println("Main:"+formater.format(new Date()));
Thread.sleep(800);
System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
useThread.interrupt();
}
}
补充:
- Thread的
start()
方法调用了start0()
方法 -
start()
之后线程进入就绪状态,也就是可运行状态,至于线程何时从就绪状态进入到运行状态,是操作系统决定的,操作系统给线程分配了时间和资源,线程就进入运行状态,不然就是就绪状态(例如start()
、interrupt()
、sleep时间到
、notify()
、notifyAll()
之后)。 - Thread的
start()
方法不能执行多次,不然会报错 - Thread的
run()
方法就相当于普通类的成员方法,单纯的调用不会新建线程 -
join()
方法 把指定的线程加入到当前线程中,插队执行。
【面试:如何让两个线程顺序执行 --->使用join
】 -
yield()
方法 当前的线程让出CPU执行权,将线程从运行转到可运行状态。所以也有可能让出执行权之后马上被CPU执行了 -
setDeamon()
将线程在启动之前变成守护线程
3. 线程的生命周期

4. 线程间的共享
不同线程操作相同的变量或代码,为了控制顺序
同步机制
- synchronized内置锁,因为是Java内置的
能保证同一时刻,只能有一个线程处于方法或者同步块里面。
public class SynTest {
private long count =0;
private Object obj = new Object();//作为一个锁
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
//方法1:对方法进行加锁
public synchronized void incCount(){
count++;
}
//方法2:对方法中的对象加锁
public void incCount2(){
synchronized (obj){
count++;
}
}
/**
* 方法1、2都是对象锁
* 方法1锁的是当前类的对象(this),方法2锁的是obj对象
*/
public void incCount3(){
synchronized (this){
count++;
}
}
//线程
private static class Count extends Thread{
private SynTest simplOper;
public Count(SynTest simplOper) {
this.simplOper = simplOper;
}
@Override
public void run() {
for(int i=0;i<10000;i++){
simplOper.incCount();//count = count+10000
}
}
}
public static void main(String[] args) throws InterruptedException {
SynTest simplOper = new SynTest();
//启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//20000
}
}
演示类锁和对象锁
- 类锁的说法不是很规范,jdk中没有描述,这只是一个概念,因为类锁锁的是类的class对象。
- 所以
synchronized
还是锁的对象 - 而class对象是什么?是把jdk里的类加载到虚拟机里的时候,虚拟机为这个类产生的一个class对象。
- 如果对
静态类
、静态变量
上锁,不管多少线程执行,都会同步执行,因为静态变量在虚拟机里有且只有一个
总结
- synchronized 普通方法 ---》 锁当前类的实例
- synchronized 静态方法 ---》 锁虚拟机只有一份的当前类的class对象
- synchronized obj ---》 锁当前对象
- synchronized 静态obj --》 锁虚拟机只有一份的obj静态变量
public class SynClzAndInst {
//使用类锁的线程
private static class SynClass extends Thread{
@Override
public void run() {
System.out.println("TestClass is running...");
synClass();
}
}
//类锁,实际是锁类的class对象,
//因为static表示静态,与类的对象实例没关系
private static synchronized void synClass(){
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end");
}
private static Object obj = new Object(); //静态的,虚拟机内有且只有一个
private void synStaticObject(){
synchronized (obj){ //类似于类锁,Obj在全虚拟机只有一份
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end");
}
}
//使用对象锁
private static class SynObject implements Runnable{
private SynClzAndInst synClzAndInst;
public SynObject(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance is running..."+synClzAndInst);
synClzAndInst.instance();
}
}
//使用对象锁
private static class SynObject2 implements Runnable{
private SynClzAndInst synClzAndInst;
public SynObject2(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance2 is running..."+synClzAndInst);
synClzAndInst.instance2();
}
}
//锁对象
private synchronized void instance(){
SleepTools.second(3);
System.out.println("synInstance is going..."+this.toString());
SleepTools.second(3);
System.out.println("synInstance ended "+this.toString());
}
//锁对象
private synchronized void instance2(){
SleepTools.second(3);
System.out.println("synInstance2 is going..."+this.toString());
SleepTools.second(3);
System.out.println("synInstance2 ended "+this.toString());
}
public static void main(String[] args) {
SynClzAndInst synClzAndInst = new SynClzAndInst();
Thread t1 = new Thread(new SynObject(synClzAndInst));
SynClzAndInst synClzAndInst2 = new SynClzAndInst();
Thread t2 = new Thread(new SynObject2(synClzAndInst2));
Thread t3 = new Thread(new SynObject2(synClzAndInst));
-------------------------------------------------------------------------------------
t1.start(); //t1锁的是synClzAndInst这个对象
t2.start(); //t2锁的是synClzAndInst2这个对象
//两个线程不会相互影响,因为两个线程传入的对象不是同一个,各自被锁对于线程执行没有影响
-------------------------------------------------------------------------------------
t1.start();
t3.start();
//t1执行完,才会执行t3,因为两个线程中传的是同一个对象,而对象又被锁了
-------------------------------------------------------------------------------------
t1.start();
t3.start();
SynClass synClass = new SynClass();
synClass.start();
SleepTools.second(1);
//t3仍然等待t1执行完才执行,但是类锁的synClass与对象锁互不影响
}
}
5. 线程间的协作
等待和通知
- wait()
让当前线程进入等待状态
不是线程的,是Object的
- notify/notifyAll
通知当前等待的线程
notify只会通知一个等待线程,notifyAll通知所有在对象上等待的线程
不是线程的,是Object的
- 等待和通知的标准范式
等待方:
- 获取对象的锁
- 检查条件,条件不满足 wait
- 条件满足,执行业务代码
syn(obj){ while(条件不满足){ obj.wait(); //调用wait后会释放锁,便于通知方获取锁 } 执行业务代码 }
通知方:
- 获取对象的锁
- 修改条件
- 通知等待方
syn(obj){ 执行业务代码,修改条件 obj.notify()/notifyAll(); //这里不会释放锁 } //这里执行完会释放锁
补充:yield、sleep都不会释放锁
- notify和notifyAll应该用谁
一般情况下尽量用notifyAll
如果业务条件满足的话,用notify也没有问题
jdk中没有指定唤醒某个线程的api
网友评论