线程
线程和进程的关系
进程就是一个可以独立运行的程序单位,它是线程集合,进程就是由一个或者多个线程构成的,而线程是进程中实际运行单位,是操作系统进行运算调度的最小单位。线程是进程中的一个最小运行单元。
并发和并行的区别?
并发是指程序同时进行,互不干扰,好比两个人同时用两把铁锹挖坑,一段时间后,会得到两个坑。
并行是指程序同一时间段同时进行,宏观上是两个程序同时运行,实则是程序在轮流使用cpu资源,只有正在使用资源的程序A才在运行,一段时间后,程序B拿到cpu使用权,则B开始运行,以此往复,看似同时,实则分时。就好比两个人拿一把铁锹挖两个坑。
线程创建的方式?
通常使用的两种:
1.继承Thread类
步骤1:定义一个类继承Thread
步骤2:重写run方法
步骤3:在main方法中创建子类对象,这个子类对象就是线程对象
步骤4:调用start方法启动这个线程让线程执行,同时JVM去调用run方法
2.实现Runnable接口
步骤1:定义一个类实现Runnable接口
步骤2:实现接口中的抽象方法
步骤3:在run方法中定义线程对象完成的任务
步骤4:在main方法中,创建实现类对象,并把这个对象作为参数传递给Thread
步骤5:调用Thread对象的start方法
线程的生命周期?
线程的生命周期分为五部分:新生状态、就绪状态、运行状态、阻塞状态、死亡状态。
当用户new完对象时,线程进入新生状态。当对象调用start方法时,线程进入就绪状态。线程调度分为分时调度和抢占式调度,java中使用的是抢占式调度了。当线程抢占到cpu资源时,线程进入运行状态。当调用方法打断线程时,线程进入阻塞状态。当线程运行完任务或者发生异常,又或者被调用stop方法时,线程进入死亡状态。
线程中常用的方法?
long getId() 返回此线程的标识符。
String getName() 返回此线程的名称。
int getPriority() 返回此线程的优先级
void run() 线程要完成任务
void start() 启动线程
void setName(String name) 将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority) 更改此线程的优先级(1~10,默认为5级)。
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程(非守护线程全部终止后,它就会停止)。
setName(): 为线程设置一个名称。 Thread-0 Thread-1 Thread-2 ....
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
yield(): 释放手中的资源,但是依旧会和别的线程抢占。
锁(同步线程)
锁的作用?
锁的作用其实就是为了保证线程的安全性。Java可以创建多个线程,在处理多线程问题时,必须注意这样的一个问题 ,当两个或者多个线程访问"同一个"变量,并且一个线程需要修改这个变量。应对这样的问题必须进行处理否则可能发生混乱。
比如:一个工资管理负责人正在修改员工的工资表,而一些员工也正在领取工资,如果允许这样操作,将出现问题。
在处理线程同步时,要做的第1件事情就是把修改数据的方法使用关键字 synchronized 来修饰,一个方法使用 synchronized 关键字修饰以后,当一个线程A使用这个方法的时候,其他线程想要使用这个方法则必须等待,直到线程A使用完这个方法。
设置锁的方法?
java中同步线程方法:
1.使用Synchronized关键字修饰方法
使用Synchronized关键字修饰的对象就相当于有了一把内置锁,调用被修饰的方法时会获得这把锁,其他的线程想要使用该方法,必须等内置锁释放,这样的方法就避免了两个线程同时使用公共资源的情况。但是由于每次进入都会产生内置锁,所以多个线程想要访问时必须排队等候,因此造成程序效率较低。
2.使用有Synchronized修饰的语句块
Synchronized(Object){
}
类似第一种方法,但是和第一种的区别是,它只将可能出现线程不安全的代码锁住,而不是锁住整个方法,一定程度上是对第一种方法的优化,提高了代码的执行效率。
3.使用特殊域变量volatile
volatile保证了不同线程对共享变量进行操作时的可见性,就是说当一个线程对一个变量的值进行了修改时,这个改变后的值对其他线程是立即可见的(因此在判断stop==!stop的情况下,在读取线程A两个值的过程中,线程B修改了stop的值,导致A出现判断stop==!stop为true的情况)
4.使用Lock
实际和第一种的情况一样,但是区别在于,这一种的钥匙比较明显。当我使用完后,可以通过调用Lock的unlock方法释放掉锁定锁。
5.原子变量AtomicInteger
为什么需要线程同步,原因普通变量的操作不是原子的。 什么是原子操作呢? 原子操作就是指读取变量值,修改变量值,保存变量值都看成一个整体 这些操作要么同时完成,要么同时不完成。原子性: 不可拆分。
死锁
什么是死锁?
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁的原因?
竞争资源引起进程死锁 。例如:线程A锁住左筷子并等待右筷子,线程B锁住右筷子并等待左筷子
这样两个线程就发生了死锁。
synchronized(左筷子){
synchronized(右筷子){
}
}
synchronized(右筷子){
synchronized(左筷子){
}
}
如何避免死锁?
如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能 性。 尽量不要嵌套使用 synchronized 关键字。破坏任意一个,就可以解决死锁:
互斥使用:一个资源只能分配给一个线程
不可剥夺:资源只能由占有者释放,申请者不能强制剥夺
请求保持:线程申请资源时,保持对原有资源的占有
循环等待:存在一个进程等待队列:{P1 , P2 , … , Pn}, 其中P1等待P2占有的资源,P2等待P3 占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路
总结
一、多个线程对象在使用共享的资源对象时,需要使用同步。
线程同步:
1> 同步方法
public synchronized void m(){ //锁的是this,调用此方法的对象
}
public static synchronized void m(){ // 类名.class
}
2> 同步代码块
synchronized(对象){
//把可能产生线程安全的代码放入里面
}
方式1: 继承Thread类
方式2: 实现Runnable接口
方式3: 实现Callable接口
方式4: 利用线程池创建
方式1:同步方法
方式2:同步代码块
方式3:Lock 锁机制,lock()和 unlock()
方式4:利用阻塞队列
方式5:ThreadLocal,将共享的资源,每一个线程都会拥有一个共享资源的副本,
每个线程都可以操作自己的那个副本,相互不影响
方式6:原子性操作类,AtomicInteger 等等
启动一个线程调用start(),再由JVM调用run()方法 ,run()只是定义线程的任务。
区别1: sleep是Thread类静态方法 wait是Object类非静态方法
区别2: sleep不会释放对象锁,放弃CPU的使用权 ,在休眠时间内,不能唤醒,休眠时间一到,自动重新进入就绪状态。 wait方法会释放对象锁,wait方法只能使用 synchronized 方法或者代码块中使用,会放弃CPU的使用权, 若在同一对象的其他线程没有调用notify或者notifyAll 永远处于等待状态。
区别3: sleep可以在任何地方使用wait只能在同步的代码块中使用
如果线程A锁住了obj1并且等待obj2,而线程B锁住了obj2并且等待obj1,这样线程A和线程B就处于死锁状态。当线程任务出现了多个同步(同步嵌套)这样容易引用一种现象--死锁,程序无限等待。
经典案例: 生产者 ---- 消费者