1 Lock锁
-
概述:
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是并不能直观看到线程在哪里获取了锁,在哪里释放了锁,所以为了更清晰的展示如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,可以使用此对象,来表示锁的创建和释放。 -
特点:
(1)Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 -
构造方法:
ReentrantLock()
:创建一个ReentrantLock的实例
Lock锁的默认方式、同步代码块、同步方法都为非公平锁
非公平锁的特点:多个线程需要执行,需要先获取锁对象,将来cpu去哪一个线程哪一个线程就获取锁,没有方法控制cpu去哪执行,所以cpu可能一直待在一个线程上执行,就导致某一条线程一直执行,其他线程无法执行。
Lock锁可以设置为公平锁(在创建对象时,传递一个参数为true):
多个线程需要执行,需要先获取锁对象,如果cpu去某一个线程A执行,那么其他线程就需要等待锁,等待执行,如果A线程执行之后,就会释放锁对象,下次cpu会选择去等待的线程中执行,而不是一直被某一个线程霸占,就说明每一条线程都有执行的机会。
-
加锁解锁方法:
void lock()
:获得锁
void unlock()
:释放锁
代码
package demos2;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {public static void main(String[] args) {
SellTickets st = new SellTickets();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SellTickets implements Runnable{
int tickets = 100;
//获取了一个Lock锁对象
//如果传入的参数为false,或者不传入参数,都是为非公平锁
//如果掺入的参数为true,表示当前锁是一个公平锁
ReentrantLock r = new ReentrantLock(true);
@Override
public void run() {
while(true){
try{
//在此获取锁对象
r.lock();
if(tickets <= 0){
break;
}
tickets = tickets - 1;
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + tickets + "张票");
}finally {
//在此释放锁对象
r.unlock();
}
}
}
}
2 死锁(了解)
死锁概述:
线程死锁是指由两个或者多个线程互相持有并且需要对方的的资源,导致这些线程处 于等待状态,无法前往执行
锁对象:x y
线程A:线程A想要执行,需要持有锁x和锁y 线程A拥有锁x ,需要y
线程B:线程B想要执行,需要持有锁x和锁 线程B拥有锁y,需要x
图示:
代码
package demos2;
public class Demo02 {
public static void main(String[] args) {
String s1 = "左筷子";
String s2 = "右筷子";
Thread t = new Thread(){
public void run(){
synchronized (s1){
System.out.println("张举获取到了" + s1 + ",需要" + s2);
synchronized (s2){
System.out.println("张举获取到了" + s2 + ",已经拿到一双筷子, 可以疯狂开吃!!!");
}
}
}
};
Thread t2 = new Thread(){
public void run(){
synchronized (s2){
System.out.println("王楠获取到了" + s2 + ",需要" + s1);
synchronized (s1){
System.out.println("王楠获取到了" + s1 + ",已经拿到一双筷子, 可以疯狂开吃!!!");
}
}
}
};
t.start();
t2.start();
}
}
3 线程的生命周期
- 生命周期:表示一个物体从创建到消亡的过程
- 线程从创建到销毁的过程就是线程的生命周期
在线程的生命周期中,有很多的状态来描述线程 - 状态的罗列:
新建态:线程对象刚刚创建
就绪态:线程已经启动,等待cpu的执行
运行态:正在被运行的状态
阻塞态:通过某些情况,导致线程无法执行(cpu来临也无法执行)
等待锁对象 线程休眠(sleep wait) 死锁 IO
死亡态(消亡态):线程正常执行结束 ,线程被意外终止 - 图示:
3.1 在Java程序中如何获取线程的状态
- 概述:
刚刚所讲的状态,都是理论分析的有关线程的状态。在程序中可以通过一个方法,来获取具体的线程状态。 - 获取线程状态的方法:
getState()
:
返回返回值类型为一个内部类:Thread.State
,该内部类是通过枚举来定义的。在枚举类型中,定义了六个对象,程序中描述线程的状态都是通过这个六个对象来定义的。 - 状态对象罗列:
项目 | Value |
---|---|
NEW | 新建态 |
RUNNABLE | 线程的运行态和就绪态 |
BLOCKED | 阻塞态:由IO数据或者等待锁产生的阻塞态 |
WAITING | 阻塞态:通过调用wait方法产生的阻塞态 |
TIMED_WAITING | 阻塞态:使用sleep()休眠产生的阻塞态 |
TERMINATED:死亡态
代码
package demos2;
public class Demo03 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
public void run(){
for(int i = 1;i <= 10;i++){
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//线程刚创建,没启动//新建态
Thread.State s1 = t.getState();
System.out.println(s1);
t.start();
Thread.sleep(10);
//1、新线程启动之后,就立即获取状态:就绪态
//2、新线程启动之后,先运行再获取:运行态
//3、新线程运行结束了,获取:死亡态
//4、新线程正在启动,获取:阻塞态
System.out.println(t.getState());
}
}
4 线程池
-
概念:用来存储线程的一个容器
-
使用线程池的原因:
如果java不支持线程池的:想要使用线程完成一个任务:需要创建一个线程对象,将任务提交给线程执行。线程将任务完成之后,该线程对象就会被销毁。如果程序中有很多耗时比较短的任务,就降低了任务执行的效率,而且浪费线程资源。
如果需要完成一个任务,这个任务中有一些意外问题导致线程会意外终止,就导致任务无法正常完成。
有线程池的使用:
想要完成一个任务,只需要从线程池中获取即可,节约时间;线程池中的线程做完任务之后,不会被销毁,而是在线程池中继续维护,等待执行下一个任务,节约线程资源。
如果线程池中的线程再执行任务时,被任务意外终止,线程池会继续提供一下个线程完成该任务,可以保证任务一定完成。
4.1 线程池的使用
- 步骤:
(1)获取线程池对象
(2)创建任务类对象
(3)将任务对象提交给线程池
(4)关闭线程池对象 - 获取线程池对象:
Executors:工具类 获取线程池的工具类
函数 | Value |
---|---|
newSingleThreadExecutor() | 获取一个具有单个线程的线程池对象 |
newFixedThreadPool(int n) | 获取一个有n条线程的线程池对象 |
newCachedThreadPool() | 获取一个线程池对象,根据提交的任务分配线程 |
- 将任务提交给线程池对象:
submit(Runnable r)
(1)如果线程池中的线程比较少,任务比较多,多余的任务需要等待其他线程结束 之后再执行。
(2)如果线程池中的线程比较多,任务比较少,多余的线程等待提交任务
(3)如果线程池中的线程和任务一样多,多个线程都并发执行任务 - 关闭线程池的方法:
shutdown()
:将已经提交的任务全部完成之后,再关掉线程池
shutdownNow()
:将正在执行的任务全部完成之后,就立即关闭线程池
代码
package demos2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo04 {
public static void main(String[] args) {
//获取一个线程池对象
//1、当前线程池中只有一条线程
// ExecutorService single = Executors.newSingleThreadExecutor();
//1、获取一个线程池,该线程池中有执行条线程
ExecutorService three = Executors.newFixedThreadPool(3);
//1、获取一个线程池,该线程池中的线程个数,根据提交的任务分配
// ExecutorService cached = Executors.newCachedThreadPool();
//2、准备任务
Runnable r1 = new Runnable() {
@Override
public void run() {
for(int i = 1;i <= 10;i++){
System.out.println(i + "..." + Thread.currentThread().getName());
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
for(int i = 100;i <= 110;i++){
System.out.println(i + "----" + Thread.currentThread().getName());
}
}
};
Runnable r3 = new Runnable() {
@Override
public void run() {
for(int i = 1000;i <= 1010;i++){
System.out.println(i + "*******" + Thread.currentThread().getName());
}
}
};
Runnable r4 = new Runnable() {
@Override
public void run() {
for(int i = 1000;i <= 1010;i++){
System.out.println(i + "^_^_^_^_^_^_^_^_^_^_^" + Thread.currentThread().getName());
}
}
};
//3、提交任务给线程池
three.submit(r1);
three.submit(r2);
three.submit(r3);
three.submit(r4);
//4、关闭线程池
// three.shutdown();//将提交的任务做完之后,再关闭
three.shutdownNow();//将正在执行的任务做完之后,就立即关闭
}
}