程序,进程,线程,多进程,多线程
1、程序:可以实现多个功能的代码体,也叫软件
2、进程:是一个实体,有自己的地址空间站,如:文本区域,数据区域,堆栈
是一个运行中的程序:cpu赋予生命值时,就是进程
3、线程:是进程中的一个任务,是一个顺序执行流,有自己独立的堆栈,与其它线程共享进程中的地址空间
4、多进程:也既是可以打开运行多个软件,就像同时可以运行QQ与网易云音乐,这两个软件就是两个进程
5、多线程:一个线程中的多个任务,可以同时进行多个顺序执行流,它的好处是:
<1>提高cpu的资源利用率
<2>可以共享同一个资源(静态资源,同一个对象实例)
线程的状态图解
(1)新建状态:新建一个线程对象,设置好需要执行的任务
(2)就绪状态:调用线程的start方法,进入准备状态,等待cpu分配时间片段
(3)运行状态:当cpu将时间片段给了线程对象后,线程开始执行任务
(4)阻塞状态:正在运行中的线程由于某种原因,放弃了cpu的使用权,即线程放弃了时间片段,进入阻塞状态
阻塞分为3种:
<1>等待阻塞:运行中的线程调用wait()方法,jvm将此线程放入等待池中
<2>同步阻塞:运行中的线程想要获取同步的锁对象时,如果锁对象被其他的线程占用,jvm将此线程放入锁池中
<3>其他阻塞:当线程中执行到阻塞方法或者是thread.slepp()或者是其他线程join时,该线程进入阻塞状态
(5)当线程执行完任务后,表示结束,线程停止
并发原理:
cpu在一个时间片段里只能做一件事。微观上,cpu是将时间片段分成很多个小的时间片段,尽可能的将时间片段分配给多个线程,所以针对于某一个线程来说是走走停停,断断续续的,但是在宏观上,我们人类是感觉不出来,看似这些线程是同时发生的,这就是并发原理。
线程创建的三种方式:
(1)继承Thread类,重写run方法,调用start方法,启动线程,进入就绪状态
(2)实现Runnable类,重写run方法,传给Thread对象
(3)创建Callable子类对象,重写call方法,传给FutureTask对象,FutureTask对象传给Thread对象,调用start方法,启动线程。
方法1:MyTask类继承Thread类,重写run()方法
public class ThreadDemo01 {
public static void main(String[] args) {
//创建线程对象,启动线程
Thread t1 = new MyTask();
Thread t2 = new MyTask();
t1.start();
t2.start();
System.out.println("main方法over");
}
}
/*方法1:通过继承Thread,完成线程的定义*/
class MyTask extends Thread{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println(this.getName()+":1");
}
}
}
方法2:通过实现Runnable接口,重写run()方法,创建一个指定线程任务的对象,传给Thread对象
public class TheadDemo02 {
public static void main(String[] args) {
Runnable r1 = new MyTask1();
Thread t1 = new Thread(r1);
Runnable r2 = new MyTask1();
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("main方法over");
}
}
/*方法2:实现接口,完成任务的定义*/
class MyTask1 implements Runnable{
//重写run方法,定义任务体
@Override
public void run() {
//随机一个整数输出500次
int num = (int)(Math.random()*500);
for (int i = 0; i < 500; i++) {
System.out.println(num);
}
}
}
方法3:通过匿名内部类获取线程对象
public class ThreadDeno03 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("helloworld");
}
System.out.println(this.getName()+"结束");
}
};
Thread t2 = new Thread(new Runnable(){
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("no zuo no die");
}
System.out.println("t2结束");
}
});
t1.start();
t2.start();
System.out.println("main线程结束");
}
}
方法4:使用FutureTask类创建对象,使用Callable创建子类对象,重写call方法(相当于run()方法),再将Callable对象传给FutureTask对象,再将FutureTask对象传给Thread对象,调用start方法,启动线程
public class ThreadDemo04 {
public static void main(String[] args) {
FutureTask<String> task = new FutureTask<String>(new Callable<String>(){
@Override
public String call() throws Exception {
int count = 0;
for (int i = 0; i < 100; i++) {
if (i%2!=0) {
count+=i;
}
}
System.out.println("count:"+count);
return count+"";
}
});
Thread t1 = new Thread(task);
t1.start();
}
}
使用Runnable接口比较继承Thread类的好处:
(1)将线程对象和任务对象分开,降低了耦合度,便于维护
(2)避免了java中单继承的限制
(3)适合相同的任务代码块处理同一个资源
使用FutureTask类:call方法带有返回值
线程API:
常用的构造器
Thread(Runnable r);
创建一个指定任务的线程对象
Thread(Runnable r,String name)
创建一个指定任务,并且指定名称的线程对象
Thread(String name)
创建一个指定名称的线程对象
线程的优先级:
1-10,10为最高级别,1为最低级别,5为默认级别
Thread.MIN_PRIORITY--最小优先级
Thread.MAX_PRIORITY--最大优先级
Thread.NORM_PRIORITY--默认优先级
PS:先用线程优先级只能提高cpu分配给优先级高的时间片段的概率,但是不是绝对的
守护线程:
线程分为两类,一类是普通线程(前台线程),一类是守护线程(后台线程),当线程只剩下守护线程后,所有的线程都结束
设置守护线程的方法:线程对象调用
方法:
void setDaemon(boolean flag);
设置为true时是守护线程。
static void sleep(long time);
使当前进程放弃时间片段,进入阻塞状态,超时后会进入就绪状态,如果没有超时而是被打断会出现检查性异常,
InterruptException
interrupt()
打断阻塞状态下的线程对象
void join()
将当前线程A加入到线程B中,线程B进入阻塞状态,直到线程A结束,线程B进入就绪状态,等待被分配时间片段
static void yield()
线程对象让步的功能:让出时间片段,此线程进入就绪状态。
同步锁:
当多个线程操作临界资源时,可能会出现线程安全隐患问题。
临界资源可能是:
(1)某一个静态变量
(2)某一个实例变量
如果想解决这样的问题,需要使用同步操作。
异步操作:多线程的并发操作,相当于各干各的
同步操作:相当于一个做完,另一个再做
线程在内部提供了一个内置的锁机制保证原子性,用于线程进行同步操作。
锁需要两点:
(1) 锁是一个对象
(2)如果想进行同步,多个线程操作的必须是同一个锁
synchronized(锁对象的引用){
需要同步的代码块
}
锁机制:
当一个线程进入同步的代码块后,就会获得锁对象的使用权,其他线程如果执行到此处,会发现锁对象被占用,只能处于等待状态(锁池);当线程执行完同步代码块后或者出现异常,都会自动释放锁。
合适的锁对象:
必须是一个引用类型,而且必须使多个线程都可以看到这个锁对象,因此this对象比较适合
同步代码块的范围:
(1)可以是方法内的一部分代码,可以是全部代码(相当于给方法上了锁)
(2)给方法上添加修饰词synchronized,锁对象为this
如果一个类的所有成员方法都用了同步关键字,当某一个线程操作了其中某一个方法,另外的线程即时操作的不是这个方法,也会
进入锁池状态
(3)静态方法上也可以添加synchronized关键字,锁对象为类对象 类名.class
调用方法:类名.class,每一种类都有唯一的类对象
wait()/notify()/notifyAll():
上述方法都是Object类型提供的,用来调控线程状态的。
使用位置必须是同步块中,如果不是同步块中会报异常
wait():
当前获取锁对象的线程准备释放锁,给其他线程获取锁的机会。
wait(long time):
当前获取锁对象的线程如果没有被通知,在延迟time毫秒后释放锁对象,自动释放
wait(long time,int naons):
功能一样,只不过比上一个方法延迟的时间更加精确
notify()/nitifyAll();
当前获取锁对象的线程准备释放锁,使用此方法通知处于使用wait()等待的线程,让它们开始准备竞争锁对象。
notify只会随机通知等待线程中的其中一个
notifyAll():
通知所有等待的线程来竞争锁。
同步代码块内:
(1)尽可能的不要使用sleep方法和yield方法,因为比较占cpu资源
(2)同步块越小越好,省cpu的资源
线程池:
(1)如果每个任务都需要创建线程对象,内存开销大
(2)方便管理线程对象(任务是Runnable,线程是线程)
线程池的原理:
就是一些线程的集合,线程的状态不是死亡状态,可以从外面接收任务,当线程池接收到外面的任务时,线程池的任务
管理器会查看是否有空闲线程,如果有,就将任务分配给它,如果没有,任务就处在等待队列中
线程池的类型:ExecutorService
另外一个类Executors里提供了多个静态方法来获取线程池里的对象
方法1:
newSingleThreadExecutor():
获取单个线程的线程池对象,内部维护了一个无界队列用于存储任务
方法2:
newFixedThreadPool(int nThreads)
创建一个固定数量线程的线程池,维护一个无界队列(存任务)
方法3:
newCachedThreadPool()
可以根据需求来创建新线程的线程池对象,如果有可重用的,会优先使用。
方法4:
newScheduledThreadPool(int corePoolSize)
创建一个线程池,可以进行设置延迟