概念
首先我们需要明确几个概念:程序、进程、线程
- 程序是为完成特定任务,用某个编程语言编写的一组指令的集合,通俗讲是磁盘上一段静态的代码。
- 进程就是正在运行中的程序,进程有它自身的启动、运行、消亡的生命周期;进程是操作系统资源分配的单位,一个进程包含一个或多个线程。
- 线程是CPU调度的最小单元,是进程的一个执行路径;线程共享进程的内存资源,他们可以访问进程中相同的对象或者变量,每个进程都会有一个方法区和堆,多个线程共享同一进程下的方法区和堆。多线程的优势就是可以并发执行进程的多个线程,提高CPU利用率,比如当进程正在等待用户输入的过程中,分配的CPU时间片就可以执行其他后台线程。
多线程的创建
两种实现多线程的方式:
- 自己定义Thread类的子类,重写父类的run方法
- 自己定义Runnable接口的实现类,重写接口的run方法,通过Thread类的构造方法将实现Runnable接口的对象传进去。
//方式1 定义Thread的子类,重写其run方法
public class TestThread extends Thread{
public void run(){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
}
}
//方式2 定义一个类实现Runnable接口,实例化对象,传入Thread的构造方法
public class TestThread2 implements Runnable{
public void run(){
Thread.yield();
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws Exception{
Thread t0 = new Thread(new TestThread2(),"t0");
}
}
然后我们在主线程中实例化这两个Thread对象,调用其start()方法,就在当前进程中开启了3个线程,
多线程之间并行执行,共享进程的CPU时间篇,不再是顺序执行。
public static void main(String[] args) throws Exception{
Thread t1 = new TestThread();
t1.setName("t-1");
Thread t2 = new Thread(new TestThread2(),"t-2");
t1.start();
t2.start();
t1.setPriority(10);
System.out.println(t2.getPriority());
System.out.println(t1.getName());
System.out.println("--------------------------");
System.out.println("--------------------------");
t1.join();
System.out.println("--------------------------");
System.out.println(t1.isAlive());
}
补充一下Thread的常用方法,分为静态方法和常规方法,静态方法一般在定义的线程内部调用,常规方法一般是主线程调用,用于控制多线程的并发执行。
- static currThread:返回当前线程;
- static sleep:当前线程睡眠指定多少毫秒
- static yield:当前线程让步,让同优先级或者更高优先级的线程执行,如果没有忽略此方法
- start:启动线程,线程处于就绪状态,准备争抢分配给当前进程的CPU时间篇
- join:当主线程调用某个线程对象的join方法时,主线程执行到这个位置后被阻塞,直到join线程执行完毕为止;
- stop: 强制结束某个线程的生命周期
- isAlive:返回目标线程是否活跃
- setPriority:设定目标线程分配CPU时间片的优先级
- setName:设定线程名称
线程同步和死锁
什么是线程同步,线程同步就是在多线程访问共享数据时,控制其访问顺序的机制,避免出现多线程访问后数据出现不一致的情况;下面是线程同步问题的演示程序
//账户类,用于模拟两个线程的共享资源
class Account{
private static int MONEY = 3000;
public void drawing(int money){
System.out.println(Thread.currentThread().getName()+ "取款之前还有余额"+MONEY);
if (MONEY <= 1000){
System.out.println("钱取完了,别取了");
}else{
System.out.println(Thread.currentThread().getName()+ "取款金额"+money);
MONEY = MONEY - money;
System.out.println(Thread.currentThread().getName()+ "取款后账户余额"+MONEY);
}
}
}
//定义的实现Runnable接口的线程类
class User implements Runnable{
Account account;
int money;
public User(Account account, int money){
this.account = account;
this.money = money;
}
public void run(){
account.drawing(money);
}
}
//注意看,两个线程共享了一个account对象,只要线程存在共享数据,就有可能发生线程同步问题
public static void main(String[] args) throws Exception{
Account account = new Account();
Thread t0 = new Thread(new User(account,2000),"t0");
Thread t1 = new Thread(new User(account,2000),"t1");
t0.start();
t1.start();
}
我们看一下程序运行结果,可以看到代码中的条件逻辑判断并没有拦截后进来的线程访问。通俗地说,线程同步问题就是一个线程在访问共享资源时没有执行完逻辑就让出CPU时间片,让另一个线程也进来访问,下次再访问的时候是从上次退出的位置继续执行,就造成了这个结果。

如何解决线程同步问题,解决策略就是当一个线程在访问共享资源时,其他线程排队等候,直到当前线程访问完成后,其他线程才能访问,也就是说每次只能有一个线程访问共享资源,这样就不会出现线程同步问题;因此,java提供了共享锁synchronized实现了这个策略,共享锁synchronized可以用来修饰共性资源的常规方法、静态方法和代码块;
- 修饰常规方法 synchronized修饰常规方法和代码块,作用类似,锁的是整个对象,也称为对象锁,在当前线程访问加锁的方法时,就会拿走这个对象锁,其他线程访问这个对象时因为锁被当前线程拿走,被迫处于阻塞状态,只能等当前线程执行完成把锁归还后才能访问。
//synchronized 关键字放到访问权限修饰符后面
public synchronized void drawing(int money){
System.out.println(Thread.currentThread().getName()+ "取款之前还有余额"+MONEY);
if (MONEY <= 1000){
System.out.println("钱取完了,别取了");
}else{
System.out.println(Thread.currentThread().getName()+ "取款金额"+money);
MONEY = MONEY - money;
System.out.println(Thread.currentThread().getName()+ "取款后账户余额"+MONEY);
}
}
- 修饰代码块 被synchronized修饰的代码块也称同步代码块,作用跟synchronized修饰常规方法一样,锁住的是整个对象。不同处在于同步代码块更灵活、细粒度,只在关键代码位置加锁,线程并行效率更高一些。
public void drawing(int money){
synchronized (this){
System.out.println(Thread.currentThread().getName()+ "取款之前还有余额"+MONEY);
if (MONEY <= 1000){
System.out.println("钱取完了,别取了");
}else{
System.out.println(Thread.currentThread().getName()+ "取款金额"+money);
MONEY = MONEY - money;
System.out.println(Thread.currentThread().getName()+ "取款后账户余额"+MONEY);
}
}
- 修饰静态方法 前两种是对象锁,锁住的是当前对象;当synchronized修饰类的静态方法时,表明加的是类锁,类锁的作用范围是当前类的静态属性和静态方法,当一个线程执行synchronized的静态方法时,拿走类锁,这个类的所有静态属性和静态方法都不能被其他线程访问,直到当前线程访问完成归还类锁为止。注意,类锁和对象锁不是一把锁,当他们同时存在时,一个线程拿走类锁,不阻塞另一个线程拿走对象锁,各管各的。
public static synchronized void drawing(int money){
System.out.println(Thread.currentThread().getName()+ "取款之前还有余额"+MONEY);
if (MONEY <= 1000){
System.out.println("钱取完了,别取了");
}else{
System.out.println(Thread.currentThread().getName()+ "取款金额"+money);
MONEY = MONEY - money;
System.out.println(Thread.currentThread().getName()+ "取款后账户余额"+MONEY);
}
}
下面再说一下死锁问题,死锁也是关于线程同步的问题,当我们为避免出现共享资源访问前后数据出现不一致的问题而加锁时,就有可能出现死锁的现象,死锁就是当多个线程分别占用其他线程需要的资源锁,都在等对方释放锁时,所有线程都处于阻塞状态的问题。
//双方都在等对方释放自己需要的锁,而自己则占着别人需要的锁
public class TestThread3 implements Runnable{
@Override
public void run() {
synchronized ("小明"){
System.out.println("你好小明..........");
synchronized ("小红") {
System.out.println("你好小红..........");
}
}
}
}
class TestThread4 implements Runnable{
@Override
public void run() {
synchronized ("小红"){
System.out.println("你好小红..........");
synchronized ("小明") {
System.out.println("你好小明..........");
}
}
}
}
怎么解决死锁问题
- 加锁顺序尽量保持一致
- 尽量减少同步资源的定义,避免锁未释放的场景
线程间通信
什么是线程间通信,线程间通信就是多线程在访问同一个公共资源过程中,互相之间传递信息、同步动作协作完成任务的过程。线程通信的核心目标是确保线程能够按照预定的顺序执行,并且能够安全地访问共享资源。也就是说线程间通信是线程同步的方式或者说过程;而线程同步是线程间通信的结果。object类提供了wait,notify,notifyAll方法实现线程通信,多线程之间安全有序的访问公共资源,避免出现数据不一致的问题。
- wait 当前线程释放锁资源、进入阻塞状态,直到被其他线程notify唤醒
- notify 唤醒处于wait阻塞状态优先级最高的线程,但并不是使其处于就绪状态,而是先进入Block状态,抢锁成功后,才能进去运行状态。
- notifyAll 唤醒所有处于wait阻塞状态的线程
- 需要注意的是这三个线程通信方法只能在synchronized关键字修饰的方法或者同步代码块内调用,否则会报异常。
//生产者消费者模型是典型的线程间通信案例
//clerk表示多线程的公共资源
class Clerk{
public static int number = 20;
}
//生产者线程
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
synchronized (this.clerk){
while (true){
if (clerk.number == 20){
try {
clerk.wait();//库存满了阻塞线程,让出clerk同步锁
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
System.out.println("生产者开始生产");
while (clerk.number < 20){
clerk.number++;
System.out.println("库存:" + clerk.number);
}
System.out.println("生产者结束生产");
clerk.notify();//生产完成激活消费者线程,使其处于就绪状态
}
}
}
}
}
//消费者线程
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
synchronized (this.clerk){
while (true){
if (clerk.number == 0){
try {
clerk.wait();//库存为0,让出同步锁,处于阻塞状态
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
System.out.println("消费者开始消费");
while (clerk.number > 0){
clerk.number--;
System.out.println("库存:" + clerk.number);
}
System.out.println("消费者结束消费");
clerk.notify();//库存为0,消费完成,激活处于阻塞状态的生产者线程
}
}
}
}
}
线程的生命周期

5841

被折叠的 条评论
为什么被折叠?



