基本概念
- CPU核心数:表示CPU在同一个瞬时时间可以处理的任务数
- 通过主频CPU来对进程频繁的切换
- 多线程:一个进程可以分化为并行执行的多个线程
多线程优点
- 提高应用程序响应,增强用户体验
- 提高CPU利用率
- 改善程序结构,将较长的进程划分成多个线程,独立运行,利于理解和修改
- 比如程序中代码比较长而且前后执行没有因果关系,就可以将没有因果关系的部分放到各自的线程中执行,优化代码结构
多线程使用场景
- 程序需要同时主席那个两个或者多个任务
- 程序需要实现一些需要等待的任务时,用户输入、文件读写操作、网络操作、搜索等
- 需要一些后台运行的程序时:因为多线程是进程的支流,当分支之后就会各不影响,假设在进程上跑的是主程序 ,当其中的第三行代码是开启线程的,那么开启线程之后线程运行的代码就和主程序并行(和主程序不相干)
多线程的创建和启动
- Thread类特性
- 每个线程都通过某个特定的Thread对象的run实现,run方法被称作线程体
- 通过Thread对象的start()方法来调用这个线程
创建线程
/**
* 继承Thread
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("子线程运行的主逻辑");
for (int i = 0; i < 5; i++) {
System.out.println("这是多线程的逻辑: "+ i);
}
}
}
/**
* 实现Runnable接口
*/
public class MyRunnable implements Runnable {
public int count = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
/**
* 多次运行发现,主线程和子线程两者运行互不影响,各自保持自己的输出顺序,这个就是多线程的异步性,同步存在顺序性
* 主线程:0
* 子线程运行的主逻辑
* 主线程:1
* 这是多线程的逻辑: 0
* 主线程:2
* 这是多线程的逻辑: 1
* 主线程:3
* 主线程:4
* 这是多线程的逻辑: 2
* 这是多线程的逻辑: 3
* 这是多线程的逻辑: 4
*/
myThread.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程:" + i);
}
// 使用Runnable实现
MyRunnable myRunnable = new MyRunnable();
Thread runnableThread1 = new Thread(myRunnable, "t-1");
runnableThread1.start();
/**
* 这是Runnable多线程的逻辑: 0
* 这是Runnable多线程的逻辑: 1
* 这是Runnable多线程的逻辑: 2
* 这是Runnable多线程的逻辑: 3
* 这是Runnable多线程的逻辑: 4
*/
Thread runnableThread2 = new Thread(myRunnable, "t-2");
runnableThread2.start();
}
两种实现多线程的方式的联系与区别
- 联系:
- 区别:
- 继承Thread:线程代码存放Thread子类run方法中
- 实现Runnable:线程代码存放在接口的子类的run方法
- 使用Runnable接口的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类对象,适合多个相同线程来处理同一份资源
MyRunnable myRunnable = new MyRunnable();
Thread runnableThread1 = new Thread(myRunnable, "t-1");
Thread runnableThread2 = new Thread(myRunnable, "t-2");
线程相关方法
- void start() : 启动线程,并执行对象的run()方法
- void run():线程在被调度时执行的操作
- String getName():返回线程名称
- void setName(String name):设置该线程的名称
- static currentThread():返回当前线程
- setPriority(int newPriority):设置线程优先级,优先级使用数字1~10表示,数字越大优先级越高会增加被执行的概率,默认优先级为5
class TestRun implements Runnable {
public int count = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
}
}
}
public static void main(String[] args) {
TestRun testRun1 = new TestRun();
TestRun testRun2 = new TestRun();
Thread thread1 = new Thread(testRun1);
Thread thread2 = new Thread(testRun2);
thread1.setPriority(10);
thread1.start();
thread2.start();
// thread1.setName("Thread1"); // 设置线程名称
System.out.println(thread1.getName()); // Thread-0 如果创建线程时没有指定名称,系统分配给线程的默认名称
System.out.println(thread2.getName()); // Thread-1
}
- static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或优先级更高的线程,若队列中没有同优先级的线程,忽略此方法
class TestRun implements Runnable {
public int count = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
Thread.yield(); // 线程让步
}
count++;
System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
}
}
}
- join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到调用join()方法加入的join()线程执行完为止
TestRun testRun1 = new TestRun();
TestRun testRun2 = new TestRun();
Thread thread1 = new Thread(testRun1);
Thread thread2 = new Thread(testRun2);
thread1.start();
thread2.start();
System.out.println(thread1.getName()); // Thread-0 如果创建线程时没有指定名称,系统分配给线程的默认名称
thread1.join(); // 相当于把thread1的run方法放到这里执行,同步顺序,先不执行main函数的方法,先执行join进来的代码,join线程执行完毕之后,再执行main方法阻塞的代码
/**
* 阻塞main方法,Thread-0一定在两次打印名字之间输出
* Thread-0
* Thread-0:这是Runnable多线程的逻辑: 1
* Thread-0:这是Runnable多线程的逻辑: 2
* Thread-0:这是Runnable多线程的逻辑: 3
* Thread-1:这是Runnable多线程的逻辑: 1
* Thread-0:这是Runnable多线程的逻辑: 4
* Thread-1:这是Runnable多线程的逻辑: 2
* Thread-0:这是Runnable多线程的逻辑: 5
* Thread-1:这是Runnable多线程的逻辑: 3
* Thread-1
*/
System.out.println(thread2.getName()); // Thread-1
}
- sleep(long millis):使当前活动的线程在指定的时间内放弃对CPU的控制,使得其他线程可以执行,时间到后重新排队
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000); // 当前线程sleep 1 秒,相当于当前循环没给1秒执行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
}
}
thread1.start();
thread2.start();
thread1.stop();
/**
* Thread-0
* Thread-1
* Thread-1:这是Runnable多线程的逻辑: 1
* Thread-1:这是Runnable多线程的逻辑: 2
* Thread-1:这是Runnable多线程的逻辑: 3
* Thread-1:这是Runnable多线程的逻辑: 4
* Thread-1:这是Runnable多线程的逻辑: 5
*/
System.out.println(thread1.isAlive()); // false 判断线程是否还活着
线程的生命周期
- JDK中用Thread.State枚举表示了线程的状态,在线程的完整的生命周期中都要经历下面五种状态
- 新建:线程实例的创建
- 就绪:调用start()方法,将进入阻塞队列进行等待,直到CPU分配给时间片
- 运行:run方法开始执行,即就绪的线程被调度并获得CPU资源时,进入运行状态
- 阻塞:当被挂起或者执行I/O操作时,需要将CPU让出,阻塞自己的执行,即run方法暂停执行
- 终止:线程完成了全部的工作或执行stop()、断电、杀掉进程被强制终止
线程同步
- 线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法,导致共享数据的错误
- 解决方法,先让一个线程整体执行完方法再让另一个线程执行
- 通过synchronized来实现,可以直接在方法上加这个关键字
- 在普通方法上加synchronized锁的是整个的对象,不是某一个方法
// 定义一个对象
public class Test2 {
public static void main(String[] args) {
Acount acount = new Acount();
User user_wx = new User(acount, 2000);
User user_pay = new User(acount, 2000);
Thread wxThread = new Thread(user_wx, "wx");
Thread payThread = new Thread(user_pay, "pay");
wxThread.start();
payThread.start();
}
}
class Acount {
public int money = 3000;
public synchronized void drawing(int mOut) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
public synchronized void drawing1(int mOut) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
}
class User implements Runnable {
private Acount mAcount;
private int money;
public User(Acount acount, int mmoney) {
this.mAcount = acount;
this.money = mmoney;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("wx")) {
mAcount.drawing(money);
} else{
mAcount.drawing1(money);
}
/**
* 和原来执行的结果一致,所以锁住的是Account这个对象
* pay 操作账户原有金额: 3000
* pay 取款:2000
* pay 还剩:1000
* 对不起,无法取款:1000
*/
}
}
// 定义两个对象
public class Test2 {
public static void main(String[] args) {
Acount acount = new Acount();
Acount acount1 = new Acount();
User user_wx = new User(acount, 2000);
User user_pay = new User(acount1, 2000);
Thread wxThread = new Thread(user_wx, "wx");
Thread payThread = new Thread(user_pay, "pay");
wxThread.start();
payThread.start();
}
}
class Acount {
public static int money = 3000; // 静态变量内存中共享money
public synchronized void drawing(int mOut) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
/**
* pay 操作账户原有金额: 3000
* wx 操作账户原有金额: 3000
* pay 取款:2000
* wx 取款:2000
* pay 还剩:1000
* wx 还剩:-1000 */
}
public synchronized void drawing1(int mOut) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
}
class User implements Runnable {
private Acount mAcount;
private int money;
public User(Acount acount, int mmoney) {
this.mAcount = acount;
this.money = mmoney;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("wx")) {
mAcount.drawing(money);
} else{
mAcount.drawing1(money);
}
}
}
- 所以说在方法前加synchronized针对同一个对象好用,不同对象不好用
- 但是如果是static方法加synchronized,对于所有对象都是同一个锁
// 如果是静态的方法加synchronized,则对于所有的对象都是同一个锁
public static synchronized void drawing2(int mOut) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
@Override
public void run() {
Acount.drawing2(money);
/**
* wx 操作账户原有金额: 3000
* wx 取款:2000
* wx 还剩:1000
* 对不起,无法取款:1000
*/
}
- 总结
- synchronized修饰方法锁对象,因为所有对象是共享同一个static变量的
- 静态synchronized修饰方法锁class,对所有对象都生效
synchronized锁代码块
- 不同的对象使用同一个锁,synchronized(this)是对象锁
public void drawing3(int mOut) {
// 用this表示当前对象, 该对象的代码块被加了同步锁
synchronized (this) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
}
public void drawing4(int mOut) {
// 表示当前对象的代码块被加了同步锁
synchronized (this) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
}
// 同一对象没问题
// 不同对象锁不住
- synchronized (acount)根据不同的对象使用不同的锁,是对象锁,针对同一个对象有效
public void drawing5(int mOut, Acount acount) {
// 表示从方法中传递进来的对象的同步锁,不同的对象就有不同的同步锁
synchronized (acount) {
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
String name = Thread.currentThread().getName();
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
}
}
// 不同对象
User user_wx = new User(acount, 2000);
User user_pay = new User(acount1, 2000);
mAcount.drawing5(money, mAcount);
wx 操作账户原有金额: 3000
wx 取款:2000
wx 还剩:1000
pay 操作账户原有金额: 3000
pay 取款:2000
pay 还剩:-1000
// 相同对象
User user_wx = new User(acount, 2000);
User user_pay = new User(acount, 2000);
mAcount.drawing5(money, mAcount);
pay 操作账户原有金额: 3000
pay 取款:2000
pay 还剩:1000
对不起,无法取款:1000
和上述this相同
- 总结:
- 普通方法加同步锁->锁当前方法对应的对象,当前对象所有加了同步锁的方法公用一个同步锁,例一
- 静态方法加同步锁->所有当前的对象都使用同一把锁
- synchronized(this)->所有当前对象的synchronized(this)同步的代码块都是公用一个同步锁
- synchronized(object)->同synchronized(this)
- 如果针对对象加同步锁加载方法上
- 如果针对某一段代码加同步锁,那么就在代码块上加同步锁
死锁
- 不同的线程分别占用对方的同步资源且互相等待对方释放同步资源,造成死锁
死锁解决
- 银行家算法预测
- 安全序列
- 尽量减少同步资源的定义,尽量避免锁未释放的场景
线程通信
- wait():把当前线程挂起进入就绪状态
- notify():唤醒在阻塞队列中线程优先级最高的线程
- notifyAll():换醒所有阻塞队列中的线程
- 上述三个方法只能用在有同步锁的方法或者有同步锁代码块中
synchronized (acount) {
String name = Thread.currentThread().getName();
if (name.equals("wx")) {
try {
acount.wait(); // 当前线程进入就绪状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (money < mOut) {
System.out.println("对不起,无法取款:" + money);
} else {
System.out.println(name + " 操作账户原有金额: " + money);
System.out.println(name + " 取款:" + mOut);
money = money - mOut;
System.out.println(name + " 还剩:" + money);
}
if (name.equals("pay")) {
acount.notify(); // 唤醒当前优先级最高的线程进入就绪状态
}
/**
* 先执行pay,后执行wx
* pay 操作账户原有金额: 3000
* pay 取款:2000
* pay 还剩:1000
* 对不起,无法取款:1000
*/
}
}
生产者和消费者
public static void main(String[] args) {
Clerk clerk = new Clerk();
// 生产者, 生产时不消费
new Thread(() -> {
synchronized (clerk) {
// 一直生产
while (true) {
if (clerk.productNum == 0) {
System.out.println("产品为0, 开始生产");
while(clerk.productNum<4) {
clerk.productNum++;
System.out.println("库存:" + clerk.productNum);
}
System.out.println("生产结束,产品数:" + clerk.productNum);
// 唤醒消费者
clerk.notify();
} else {
try {
// 生产者阻塞
clerk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "Product").start();
new Thread(() -> {
synchronized (clerk) {
// 一直消费
while (true) {
if (clerk.productNum == 4) {
System.out.println("产品为4, 开始消费");
while(clerk.productNum>0) {
clerk.productNum--;
System.out.println("库存:" + clerk.productNum);
}
System.out.println("消费结束,产品数:" + clerk.productNum);
// 唤醒生产者
clerk.notify();
} else {
try {
// 消费者阻塞
clerk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
},"Consumer").start();
}
class Clerk {
public int productNum = 0;
}
产品为4, 开始消费
库存:3
库存:2
库存:1
库存:0
消费结束,产品数:0
产品为0, 开始生产
库存:1
库存:2
库存:3
库存:4
生产结束,产品数:4