【Java SE】多线程

2023-05-24,,

1.1 线程的生命周期

![](file://D:\资料\学习笔记\Java\多线程\1.png?msec=1648087619803)

方法名 说明
yield()
stop()
sleep()
wait() 阻塞
suspend() 挂起
notify()/notifyAll() 唤醒
resume() 取消挂起

1.2 线程的安全问题

1.2.1 通过同步机制解决线程安全问题

方式一:同步代码块

synchronized(同步监视器) {
//需要被同步的代码
}
class Window implements Runnable {

    private int tickets = 100;
private Object obj = new Object();//多线程共用同一把锁,即是同步监视器 @Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
System.out.println("售票,票号为:" + tickets);
tickets--;
} else {
break;
}
}
}
}
}

说明:操作共享数据的代码,即为需要被同步的代码。

同步监视器,俗称锁。任何类的对象,都可以充当锁。但要求多个线程必须共用同一把锁。在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。在继承Thread创建多线程的方式中,可以使用当前类(Bank.class)充当同步监视器,类本身也是一个对象。

![](file://D:\资料\学习笔记\Java\多线程\2.png?msec=1648087619803)

方式二:同步方法

1.同步方法解决实现Runnable接口的线程创建方式的线程安全问题

@Override
public void run() {
while(true) {
show();
}
} private synchronized void show() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--; }

此时同步方法隐藏使用this作为同步监视器(锁)

2.同步方法解决实现继承Thread的线程创建方式的线程安全问题

@Override
public void run() {
while(true) {
show();
}
} private static synchronized void show() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--;
}
}

此时同步监视器为当前的类

1.2.2 线程同步解决单例模式懒汉式的线程安全问题

方式一:效率稍差

public class Bank {
private static Bank bank = null; private Bank() { } public static Bank getBank() {
synchronized (Bank.class) {
if (bank == null)
bank = new Bank();
return bank;
}
}
}

方式二:效率更高

public class Bank {
private static Bank bank = null; private Bank() { } public static Bank getBank() {
if(bank == null) {
synchronized (Bank.class) {
if (bank == null)
bank = new Bank();
}
}
return bank;
}
}

1.3 线程的死锁问题 DeadLock

不同线程分别占用对方需要同步的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁,不会出现异常,不会出现提示,只是所有的线程都处于堵塞状态,无法继续。

public class theadsTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer(); new Thread() {
@Override
public void run() {
synchronized (s1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("a");
s2.append("1");
synchronized (s2) {
s1.append("b");
s2.append("2"); System.out.println(s1);
System.out.println(s2);
}
}
}
}.start(); new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
synchronized (s1) {
s1.append("d");
s2.append("4"); System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}

解决方法

专门的算法、原则

尽量减少共享资源的使用

尽量避免嵌套同步

1.4 Lock锁解决线程安全问题 jdk5.0新增

private ReentrantLock lock = new ReentrantLock();

    @Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}finally {
lock.unlock();
}
}
}

lock方法默认this为同步监视器,因此线程创建方式只能使用实现Runnable接口的方式,继承的方式需要加static。synchronized在执行完同步代码后会手动释放同步监视器,lock方式需要手动启动同步(lock()),同时结束同步也需要

优先使用顺序:Lock->同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体之外)

1.4 线程的通信

@Override
public void run() {
while(true) {
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++; try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
break;
}
}
}
}

线程一首先获得同步监视器(锁),执行输出后wait()进入了阻塞状态,释放了手中的锁,随后线程二执行notify()唤醒线程一,线程二获得锁进入执行输出(线程一此时没有锁不能够执行),随后线程二wait()进入堵塞状态释放手中的锁,进程一获得锁后执行notify唤醒线程二......以此实现交叉输出。

线程通信方法 说明
wait() 当前线程进入堵塞状态并释放同步监视器
notify() 唤醒一个wait的线程,若多个线程处于堵塞状态则唤醒优先级高的哪一个。
notifyAll() 唤醒所有的线程。

三个方法必须使用在同步代码块或者同步方法中,且三个方法的调用者必须是同步代码块或者同步方法的同步检测器,因此synchronized (this)参数不能为类或者obj。否则会出现IllegalMonitorStateException,或者写为obj.wait()。此外三个方法定义在java,lang.object中。

面试题 sleep() 和 wait()方法的异同

相同点:都能够使线程堵塞。

不同点:①Thread中定义的sleep,Object中定义的wait。

②wait只能使用在同步代码块和同步方法中,由同步检测器调用。

③wait会释放线程的同步检测器,sleep则不会。

生产者消费者问题

package com.hikaru.exer;

class Clerk {
private static int num = 0; public synchronized void product() {
if(num < 20) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName() + "正在生产第" + num + "产品...");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public synchronized void consume() {
if(num > 0) {
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "正在消费第" + num + "产品...");
num--;
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
} @Override
public void run() {
System.out.println(this.getName() + "开始生产产品...");
while(true) {
clerk.product();
}
}
} class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
} @Override
public void run() {
System.out.println(this.getName() + "开始消费产品...");
while(true) {
clerk.consume();
}
}
} public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk);
Customer c1 = new Customer(clerk); p1.setName("生产者一");
c1.setName("消费者一"); c1.start();
p1.start();
}
}

1.5 通过实现Callable接口新增线程 jdk5.0新增

优点:①call方法相比run()方法,可以有返回值

②方法可以抛出异常

③支持泛型的返回值

④需要借助FutureTask类,比如获取返回结果

FutureTask

FutureTask是Future的唯一实现类,可以对Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask同时实现了Runnable、Callable接口,它既可作为Runnable被线程执行,又可作为Future得到Callable的结果。

        NumThread numThread = new NumThread();//创建Callable接口实现类
FutureTask futureTask = new FutureTask(numThread); Thread t1 = new Thread(futureTask);//作为Runnable被线程执行
t1.start(); try {
System.out.println(futureTask.get());//作为Future得到Callable的结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

1.6 使用线程池创建线程

经常创建和销毁线程、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。提前创建好多个线程放入线程池中,使用时直接获取,用完放回线程池。能够提高响应速度,降低资源消耗,便于资源管理。

public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new NumThread());//适用于Runnable
// service.submit();//适用于Callable
service.shutdown();
}

ExecutorServic

真正的连接池接口。常见子类ThreadPoolExecutor

execute(Runnable command) 执行命令,没有返回值,一般用来执行Runnable
submit(Callable task) 执行命令,有返回值,一般用来执行Callable
shutdown 关闭连接池

Executors

工具类、线程池的工厂类,用于创建返回不同类型的线程池

【Java SE】多线程的相关教程结束。

《【Java SE】多线程.doc》

下载本文的Word格式文档,以方便收藏与打印。