SetPriority:
在java中线程的优先级是从1-10,默认值为5,优先级越高,抢占到CPU的概率越高。
SetDaemon:
设置为守护线程,当其他非守护线程执行完毕之后,守护线程会陆续结束(代码可能不会执行完毕)
yield:
让出CPU的执行权,但是可能连续随机执行到这个线程
join:
把指定线程插入到当前线程之前,在指定线程执行完毕之后继续执行当前线程
线程的生命周期(一)
在sleep结束之后,会进入就绪状态而不是直接运行下面的代码
同步代码块
把操作共享数据的代码锁起来
锁默认打开,有一个线程进去了,锁自动关闭
里面的代码全部执行完毕,线程出来,锁自动打开
synchronized(锁对象){
代码块
}
需要传入一个锁对象,这个锁对象必须的唯一的,可以为static创建的对象(具体是什么锁)
当需要循环时,需要放在循环内部,确保代码能被执行完毕
一般会把当前类的字节码对象传入锁对象(MyThread.class)
同步方法
用synchronized修饰的方法
同步方法锁对象不能自己指定,如果是非静态方法,则锁对象为this,如果是静态方法,则锁对象是当前类的字节码文件对象
技巧:可以先创建同步代码块,在抽取成方法。
StringBuilder和StringBuffer的区别:
前者是线程不安全的,后者是线程安全的,所有的方法都有synchronized修饰。
Lock锁
在我们使用锁的时候,上锁和释放锁都是自动的,所以在JDK5之后提供了一个新的锁对象Lock
我们可以利用Lock接口的实现类对象ReentrantLock来自己上锁和释放锁
Lock lock = new ReentrantLock();
可以加上static修饰,来保证多个对象共享同一个锁
一定要保证单个线程在执行完毕后释放锁资源,不然会导致其他线程无法结束!
解决方案:上锁后的所有代码放到try中,解锁方法放在finally中
!死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
假设有两把锁,A和B,线程1先上A锁再上B锁,线程2先上B锁再上A锁,那么在执行过程中,就有可能A锁被线程1占有,B锁被线程2占有,进入死锁状态。
如何避免:在写代码的过程中避免两个锁的嵌套!
生产拿着和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
两个线程交替运行,其中一条线程作为生产者来生产数据,另一条线程作为消费者来消费数据。
光是这两者无法保证线程交替运行,需要引入第三者来控制线程的执行。
当数据未准备就绪时,消费线程处于Wait状态,直到被生产线程唤醒notify
消费者:判断桌子上有无食物,如果没有就等待
生产者:判断桌子上是否有食物,有则等待,否则制作食物,把食物放到桌子上,叫醒等待的消费者开吃。
常见方法:
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程
代码实现:
测试类
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求: 完成生产者和消费者(等待唤醒机制)的相关代码 * 实现线程轮流交替执行的效果 * */
//创建线程对象
Cook c = new Cook();
Foodie f = new Foodie();
//设置名字
c.setName("厨师");
f.setName("吃货");
//开启线程
c.start();
f.start();
}
}
厨师类
public class Cook extends Thread {
@Override
public void run() {
/*
* 1.循环 * 2.同步代码块 * 3.判断:到了末尾 * 4.判断:没到末尾-核心逻辑 * */ while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
if (Desk.foodFlag == 1) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//做面
System.out.println("厨师正在做面条");
//修改食物状态
Desk.foodFlag = 1;
//唤醒消费者
Desk.lock.notifyAll();
}
}
}
}
}
}
吃货类
public class Foodie extends Thread {
@Override
public void run() {
/*
* 1.循环 * 2.同步代码块 * 3.判断:到了末尾 * 4.判断:没到末尾-核心逻辑 * */
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
//先判断桌子上是否有面条
if (Desk.foodFlag == 0) {
try {
//如果没有就等待
Desk.lock.wait();//底层逻辑:让当前线程和锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//修改桌子的状态
Desk.count--;
Desk.foodFlag = 0;
//开吃
System.out.println("正在吃面条,还能吃" + (Desk.count) + "碗!");
//吃完唤醒厨师继续
Desk.lock.notifyAll();//唤醒这把锁绑定的所有线程
}
}
}
}
}
}
桌子类
public class Desk {
/*
* 作用: * 控制生产者和消费者的执行 * * */
// 桌子上是否有面条,0/1。
// 如果使用boolean类型,只能控制两条线程。 // 为了通用性,定义为int类型。 public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
等待唤醒机制实现方式二(阻塞队列方式实现)
Java阻塞队列
类似于厨师与吃货中间的管道,做好的面条都放到管道中,我们可以规定这个管道的长度(放多少碗),这个管道就是阻塞队列。
什么是阻塞:
厨师put数据时。放不进去,会等着。
吃货take数据时。取出第一个数据,如果取不到也会等待。
代码实现
测试类
public class BlockQueueDemo {
/*
* * 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码 * 细节: * 生产者和消费者必须使用同一个阻塞队列 * */ public static void main(String[] args) {
//1.在测试类中创建阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建厨师和吃货的对象
CookForBQD c = new CookForBQD(queue);
FoodieForBQD f = new FoodieForBQD(queue);
//3.命名
c.setName("厨师");
f.setName("吃货");
//4.开始
c.start();
f.start();
/*
* 在阻塞队列put和take方法的底层都实现了lock锁 * 因为打印语句在锁的外面,所以会出现连续打印的现象,但是不会对共享数据造成影响。 * */ }
}
厨师类
public class CookForBQD extends Thread{
ArrayBlockingQueue<String> queue;
//为了使用同一个阻塞队列,在创建时传递阻塞队列的对象
public CookForBQD(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断把面条放入队列
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
吃货类
public class FoodieForBQD extends Thread{
ArrayBlockingQueue<String> queue;
//为了使用同一个阻塞队列,在创建时传递阻塞队列的对象
public FoodieForBQD(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断吃面条
try {
queue.take();
System.out.println("吃货吃了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程的六种状态(生命周期)
在java中没有定义运行状态,只是一种方便称呼的叫法,所谓运行状态就是CPU在执行线程。
java.lang.Thread.State
枚举类列举了线程的六种状态
NEW
使用new Thread()
创建一个线程,并且没有调用该线程的start()
方法,此时该线程处于NEW状态
RUNNABLE
调用start()
方法后,该线程进入RUNNABLE状态
RUNNABLE对应操作系统的Running和Ready两种状态,分别表示正在运行和等待分配CPU资源
BLOCKED
运行到synchronized
代码块但未获得对应的锁,线程进入BLOCKED阻塞状态
获得对应的锁后重新进入Runnable状态
WAITING
等待状态,一般用于等待其他线程执行特定的操作
线程调用了没有参数的Object.wait()
、Thread.join()
方法(其实Thread.join()方法就是调用了Object.wait()方法)、LockSupport.park()
方法会进入这个状态
TIMED_WAITING
超时等待状态,线程等待一个时间的流逝,经过这段时间后,该线程由系统唤醒,也可以提前唤醒。
线程调用带参数的Object.wait()
、Thread.join()
、Thread.sleep()
方法、LockSupport.parkNanos()
、LockSupport.parkUntil()
方法进入该状态
TERMINATED
线程结束
run()
执行完毕或遇到了没有捕获异常