Java多线程1

1进程介绍

1.1 操作系统为什么需要进程

多道程序设计在内存中可以运行多个任务,这时就出现了问题,怎么隔离任务间的资源,怎么进行调度,提高效率,提出了进程,OS对进程进行管理,

1.2请简述对进程的理解

多道程序下进程能够保障程序的独立运行,能提高资源利用率,是一个动态的概念,是程序运行的过程,是资源分配与调度的基本单位

2线程介绍

2.1进程和线程有什么区别?

一个进程可以拥有多个线程,线程是轻量级进程能提高并发量,进程间并发运行的切换影响效率,提出线程来提高效率,多线程共享进程的资源,线程的切换消耗相对较小,不需要资源的腾挪,
创建Thread类(子类)的对象

并行与并发

单核CPU与多核CPU的理解
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费。)但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

1 定义Thread类的子类

多线程的创建,方式一:继承于Thread类

创建一个继承于Thread类的子类
重写Thread类的run() –> 将此线程执行的操作声明在run()中
创建Thread类的子类的对象
通过此对象调用start()

Public class MyThread extends Thread{

@Override
public void run(){
	System.out.println("重写Thread父类中的run()");
}

public class Test {
	public static void main(String[] args){
		System.out.println("JVM启动main线程,main线程执行main方法");
		MyThread thread = new MyThread();
		thread.start();
		//启动线程的实质是请求将JVM运行相应的线程,这个线程具体在什么时候运行由(Scheduler)线程调度器来决定
		//start()方法调用结束不意味着子线程开始运行
		
		//新开启的线程会执行run()方法
	}
}

2,当Thread已经有子类了,就不能用第一种方式了 可以使用实现Runnable接口的形式

创建一个实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()

public class MyRunnable implements Runnable{
	//重写Runnable接口中的抽象方法 run(),run()方法就是子线程要执行的代码
	@Override
	public void run(){
		System.out.println("必须通过线程start开启");
	}
}

public class Test{
	public static void main(String[] args){
	MyRunnable runnable = new MyRunnable();
	Thread thread = new Thread(runnable);
	thread.start();
	// Thread(Runnable) 实参可以是匿名内部类对象(不需要变量名)
	Thread tread2 = new Thread(new Runnable(){
			@Override
			public void run(){
				//....
			}
		});
		thread2.start();
	
	}	
}

两者的区别

1、多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由Thread类来处理,run方法实际上还是执行代理对象的,Thread t 是一个代理类 ,来进行功能增强

2、Thread主要描述的是线程,而资源的描述是通过Runnable完成的, 继承Thread的类的线程中成员属性是各个线程独有的,其他线程看不到,除非采用static的方式才能使各个线程都看到,而实现Runnable接口的线程中,成员属性是共有的,
因此在并发编程中,继承了Thread的子类在进行现成同步时不能将成员变量当做锁,因为多个线程拿到的不是同一把锁,可以用static 变量可解决,实现了Runnable没有这个问题

//Callable实现多线程
class MyThread implements Callable<String> {//线程的主体类

    @Override
    public String call() throws Exception {
        for (int x = 0; x < 10; x++) {
            System.out.println("*******线程执行,x=" + x + "********");
        }
        return "线程执行完毕";
    }
}

public class Demo1 {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new MyThread());
        new Thread(task).start();
        System.out.println("线程返回数据" + task.get());

    }
}

策略模式在Thread和Runnable中的应用

Runnable接口最重要的方法—–run方法,使用了策略者模式将执行的逻辑(run方法)和程序的执行单元(start0方法)分离出来,使用户可以定义自己的程序处理逻辑,更符合面向对象的思想。

Thread的构造方法

部分源码

/*下面是Thread 的部分源码*/

public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
init(null, null, name, 0);
}
		↓ ↓	↓	
↓ ↓	
↓	
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
		↓ ↓	↓	
↓ ↓	
↓	
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//中间源码省略
this.target = target;//①
}

/* What will be run. */
private Runnable target; //Thread类中的target属性

@Override
public void run() {
if (target != null) { //②
target.run();
}
}

默认有一个线程名以Thread-开头从0开始计数 如Thread-0 , Thread-1
如果没有传递Runnable或者没有重写run方法则不会调用任何方法
如果构造线程对象时,未传入ThradGroup,Thread会默认获取父线程的ThreadGroup座位该线程的ThreadGroup,此时子线程和父线程会在同一个线程组中
stackSize可以提高线程的深度,放更多栈帧,但是会减少能创建的线程数
默认是0 代表被忽略,如果创建Thread传了该参数会被JNI(Java Native Interface)函数调用,但是某些平台stackSize可能会失效
一般通过-Xss10m设置

多次调用start()会抛出 非法线程状态异常 ,每个线程对象只允许启动一次

为什么线程的启动不直接使用run()而必须使用start()呢?

针对不同OS的解耦 start0()表示需要将此方法依赖不同的OS实现

线程常用方法

Thread.currentThread() 类方法 获得当前进程, Java中任何一段代码都是执行在某个线程中的,执行当前代码的线程就是当前线程 ,返回的是在代码实际运行时候的线程

thread.setName(线程名称) 设置线程名称 对象方法
thread.getName() 返回线程名称
通过设置线程名称有助于程序调试,提高可读性

thread.isAlive() 判断线程是否处于活动状态 活动状态就是线程以启动且尚未终止

Thread.sleep(millis) 让当前线程休眠指定的毫秒数(currentThread返回的线程)

thread.getId()可以活的线程的唯一标识,某个编号的现成运行结束后,该编号可能被后续创建的线程使用,重启JVM后同一个线程的编号可能不一样

Thread.yield()方法的作用是放弃当前的CPU资源

thread.setPriority(num) ;设置线程的优先级
Java的线程优先级取值范围是1~10 如果超出这个范围会抛出异常
illegalArgumentException(非法参数异常)
线程优先具有继承性,在线程A创建了线程B 则B线程的优先级与A线程是一样的

thread.interrupt() //仅是给线程标记中断 线程有thread.isInterrupted()方法,该方法返回线程的中断标志 true or false

thread.setDaemon()设置守护线程 守护线程是为其他线程提供服务的线程,如GC就是一个典型的守护线程,守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程,守护线程会自动销毁,JVM会退出 设置守护线程应该在线程启动之前

public class Test{
 public static void main(String[] args){
 		SubDaemonThread thread = new SubDaemonThread();
 		thread.setDaemon(true); //设置守护线程的代码应该在线程启动之前否则会报非法线程状态异常
 		thread.start();
 		//当main线程结束,守护线程thread也销毁了
 	}
}

Thread.state 枚举类型可通过getState()方法获得

1 NEW新建状态 在调用start()启动之前的状态
2 RUNNABLE 可运行状态 包含READY和RUNNING yield方法将RUNNABLE转为READY 操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。
3 BLOCKED 线程发起阻塞的IO操作或者申请由其他线程占用的独占资源 转为阻塞
满足条件转换为RUNNABLE
4WAITING 等待状态 线程执行了object.wait()(可以使执行当前代码的线程等待,暂停执行,直到接到通知或被中断为止,只能由锁对象调用,调用wait()方法当前线程会释放锁),thread.join方法(挂起调用线程的执行,直到被调用的对象完成他的执行,T1,T2,T3三个线程怎么保证按一定顺序执行, 按顺序try join 例如1-2-3 则在2中try T1.join,3中try T2.join)然后依次(代码)启动三个线程) 会把看成转为WAITING等待状态,执行object.notify() 方法 或者加入的线程执行完毕,当前线程都会转换为RUNNABLE状态
5TIMED_WAITING状态,与WAITING类似 区别在于不会无限等待 如果线程没有在指定的时间范围内完成期望的操作,该线程自动转化为RUNNABLE 如sleep(long)
wait(long)

6TERMINATED 终止状态 线程结束处于终止状态

常用API

获取当前存活的线程数:public int activeCount()

一个Java程序有哪些线程?

Finalizer:GC守护线程

RMI:Java自带的远程方法调用(秋招面试,有个面试官问过)

Monitor :是一个守护线程,负责监听一些操作,也在main线程组中

其它:我用的是IDEA,其它的应该是IDEA的线程,比如鼠标监听啥的。

守护线程作用

A和B之间有一条网络连接,可以用守护线程来进行发送心跳,一旦A和B连接断开,非守护线程就结束了,守护线程(也就是心跳没有必要再发送了)也刚好断开。

join

作用 可以理解为插队 让主线程停止直到join的进程运行完成

join必须在start方法之后,并且join()是对wait()的封装。(源码中可以清楚的看到)
也就是说,t.join()方法阻塞调用此方法的线程(calling thread)进入 TIMED_WAITING或WAITING 状态。直到线程t完成,此线程再继续。
join也有人理解成插队,比如在main线程中调用t.join(),就是t线程要插main线程的队,main线程要去等待

中断

java的中断与操作系统的中断不一样,只是一个状态,Java的方法可以选择对这个中断进行响应,也可以不响应,响应的意思是写相应的代码

重难点

1)设置了中断可以被wait() join,sleep等感知 方法签名上都有throws 中断异常
这个就是用来相应中断状态修改的
2)如果现线程阻塞在InterruptibleChannel 类中的IO操作中,那么这个channel会被关闭
3)如果阻塞在Selector中那么select方法会立即返回
以上3种情况能自动感知(基于底层实现)并且在做出相应的操作都会重置中断状态为false

除了以上3种方法 如果线程阻塞在LockSuooirt.park()方法也叫挂起,这个时候的中断也会导致线程唤醒,但不会重置中断状态,所以唤醒偶去检测中断状态将是true

InterruptedException

通常将带有throws的这个异常的方法称为阻塞方法
这些方法的特征是时间不可控,方法的放回往往依赖于外部条件
如果希望早点返回,往往可以通过中断来实现
感知中断是通过轮询中断状态来实现

关闭线程

多线程编程的优势和风险

优势

1)提高吞吐率
2)提高响应性,采用一些专门的线程负责用户的请求处理
3)充分利用多核处理器资源

风险

1)线程安全问题,多线程共享数据时,如果没有采取正确的并发访问措施,就可能会产生数据一致性问题
2)线程活性问题,由于程序自身的缺陷或者由资源稀缺性导致的
a)死锁
b)锁死 类似睡美人故事中王子挂了
c)活锁 类似小猫咬自己尾巴
d)饥饿 类似健壮的雏鸟总是从母鸟嘴中抢到食物
3)上下文切换
4)可靠性,可能会由一个线程导致将JVM意外终止,其他的线程无法执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值