Java-多线程基础总结
文章目录
一、线程的创建方式
1)继承Thread
类
2)实现Runnable
接口
3)实现Callable
接口
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("thread-1");
}
}
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println("thread-2");
}
}
//实现Callable接口方式
class MyThread3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("thread-3");
return 123;
}
}
public class TestCreate {
public static void main(String[] args) {
new Thread(new MyThread1()).start();
new MyThread2().start();
// 实现Callable接口
FutureTask<Integer> task = new FutureTask<>(new MyThread3());
new Thread(task).start();
// 获取线程的返回值
try {
Integer i = task.get();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
二、线程的状态
1)状态转换图
线程有6个状态:
状态 | ######## | 描述 |
---|---|---|
NEW | 新创建 | 当使用new操作创建一个线程时(new Thread(x) ),此时处于新建态 |
Runnable | 就绪·(可运行) | 调用start()方法后,进入可运行状态 |
Blocked | 阻塞 | 竞争不到对象锁而进入阻塞,获得锁进入可运行态 |
Waiting | 等待 | 一个线程等待另一个线程通知调度器一个条件时,它使自己进入等待状态【生产者-消费者】,比如调用 wait方法 或 join方法 ,或等待Lock或Condition,就会出现这种情况 |
Timed waiting | 计时等待 | 这一状态将一直保持到超时期满或接到适当的通知 |
Terminated | 被终止 | run 方法正常结束,或run 方法出现异常而结束 |
1)状态流程
2)常见方法
方法名 | 作用 | |
---|---|---|
静态方法 | Thread.sleep(int time) | 使当前线程进入休眠,不会放对象锁 |
静态方法 | Thread.yield() | 使当前线程让出CPU—>进入就绪态 |
实例方法 | t1.join() | 类似插队,其他线程需要等待t1结束后,才能继续 |
实例方法 | t1.get/setPriority(int n) | 用于获取当前和设置线程的优先级 |
实例方法 | t1.getState() | 获取线程状态 |
实例方法 | t1.setDaemon(true) | 设置当前为守护线程 |
三、线程同步
1)同步方法
- 使用
synchronized
关键字,它包括两种用法:synchronized 方法和synchronized块.
同步方法:
public synchronized void method(){}
-
同步方法锁的是对象本身
this
-
缺陷:若将一个大的方法申明为
synchronized
将会影响效率
2)同步代码块
-
同步块:
synchronized(obj){}
-
obj
称之为同步监视器- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
-
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码.
- 第二个线程访问,发现同步监视器被锁定,无法访问.
- 第一个线程访问完毕,解锁同步监视器.
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
示例:银行取钱
/**
* 2夫妻同时到银行取钱
*
* 同步块:
* @author a_apple
* @create 2020-05-16 21:25
*/
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Darwing extends Thread{
Account account;
//取了多少钱
int drawMoney;
//手里多少钱
int nowMoney;
public Darwing(String name, Account account, int drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
// synchronized方法:默认锁的是this。这里如果锁run()方法的话,锁的对象就是Drawing对象,相当于锁住银行了
@Override
public void run() {
takeMoney();
}
public void takeMoney(){
// 锁的对象是变化的量,需要增删改的量
synchronized (account){
//余额不足
if(account.money<drawMoney){
System.out.println(this.getName()+"余额不足...");
return;
}
try {
//将2个线程都堵在这里,放大问题
// you,girlFriend线程都发现有100,但是钱被girl取了,变为0
// 当you醒过来以为还有100,就也取了50
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//取钱
account.money -= drawMoney;
nowMoney += drawMoney;
System.out.println(this.getName()+"取了:"+drawMoney);
System.out.println(account.name+"余额有:"+account.money);
System.out.println(this.getName()+"手里有:"+nowMoney+"\n");
}
}
}
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "结婚基金");
Darwing you = new Darwing("you",account,50);
Darwing girl = new Darwing("girl",account,100);
you.start();
girl.start();
}
}
3)死锁
形成条件:线程互相持有对方需要的资源—>循环等待
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何解决:破坏其中一个或多个条件即可
/**
* 测试死锁
* 多个线程互相拥有对方需要的资源,形成僵持
* @author a_apple
* @create 2020-05-16 22:54
*/
class Player extends Thread {
private String lock1;
private String lock2;
Player(String lock1, String lock2, String name) {
super(name);
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
//确保2线程都启动
Thread.sleep(1000);
noDeadLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//死锁:持有锁,并想获得对方的锁
void deadLock() throws InterruptedException {
synchronized (lock1) {
System.out.println(this.getName() + " have " + lock1);
System.out.println(this.getName() + " need " + lock2);
//保证2线程都启动
Thread.sleep(1000);
synchronized (lock2) {
System.out.println(this.getName() + " get " + lock2);
}
}
}
//解决死锁:持有锁,等待对方放弃锁
void noDeadLock() throws InterruptedException {
synchronized (lock1) {
System.out.println(this.getName() + " have " + lock1);
System.out.println(this.getName() + " need " + lock2);
Thread.sleep(1000);
}
//放弃lock1,请求lock2
synchronized (lock2) {
System.out.println(this.getName() + " get " + lock2);
}
}
}
public class TestDeadLock {
private static String lock1 = new String("lock-V");
private static String lock2 = new String("lock-H");
public static void main(String[] args) {
Player a = new Player(lock1, lock2, "A");
Player b = new Player(lock2, lock1, "B");
a.start();
b.start();
}
}
4)Lock锁
- JDK1.5 开始提供的显示锁。使用Lock对象
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象ReentrantLock
(可重入锁)类实现了Lock,它拥有与synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
,可以显式加锁、释放锁。
示例:买票
/**
* 使用Lock锁
* @author a_apple
* @create 2020-05-21 9:53
*/
class Tickets2{
// 票的数量
private int num = 20;
private Lock lock = new ReentrantLock();
// 买票的方法
public void sale(){
lock.lock();
try{
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票");
}finally {
lock.unlock();
}
}
}
public class SaleTicket2 {
public static void main(String[] args) throws InterruptedException {
Tickets2 res = new Tickets2();
// 确保线程启动
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
for (int i = 0; i < 8; i++) {
res.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 8; i++) {
res.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 8; i++) {
res.sale();
}
},"C").start();
}
}
输出:
A卖出了第20张票
A卖出了第19张票
A卖出了第18张票
A卖出了第17张票
A卖出了第16张票
A卖出了第15张票
A卖出了第14张票
A卖出了第13张票
B卖出了第12张票
B卖出了第11张票
B卖出了第10张票
B卖出了第9张票
B卖出了第8张票
B卖出了第7张票
B卖出了第6张票
B卖出了第5张票
C卖出了第4张票
C卖出了第3张票
C卖出了第2张票
C卖出了第1张票
四、线程通信
多个线程互相协作,共同完成任务。
涉及的方法
wait() | 使当前线程进入阻塞,并释放锁 |
---|---|
wait(int time) | 等待指定的毫秒数 |
notify() | 唤醒一个等待该(对象锁)线程并使该线程开始执行 |
notifyAll() | notifyAll 会唤醒所有等待该(对象锁)线程, |
注意:
wait()、notify()、notifyAll()
是继承自Object的本地final方法- 上面的方法需要配合
synchronized
关键字使用,即放在同步方法或代码块中使用。【说明当前线程已经获得锁
1)生产者-消费者问题
- 生产者—>缓冲区 缓冲区满–>等待消费
- 消费者<—缓冲区 缓冲区空–>等待生产
package pers.xu.multithread.kuangshen.pc;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 生产者--消费者
*
* 生产者--->缓冲区 缓冲区满-->通知消费者消费
* 消费者<---缓冲区 缓冲区空-->通知生产者生产
*
* 实例:生产者,消费者,产品,缓冲区
* @author a_apple
* @create 2020-05-17 10:03
*/
class Product{
//产品编号
int i;
public Product(int i) {
this.i = i;
}
}
class ProductBuffer{
// 缓冲区大小 5
List<Product> buffer = new ArrayList<>();
private int maxCapacity = 5;
// 将产品放入缓冲区
public void push(Product product){
// 实际操作的是buffer,所以这里使用buffer锁
synchronized (buffer){
// 缓冲区满-->放弃锁-->阻塞
while (maxCapacity == buffer.size()){
try {
System.out.println("缓冲区已满...");
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//放入
buffer.add(product);
System.out.println(Thread.currentThread().getName()+"生产了:"+product.i);
//通知消费
buffer.notifyAll();
}
}
// 从缓冲区获取商品 synchronized锁的this==缓冲区
public Product pop(){
synchronized (buffer){
// 缓冲区空-->等待
while(buffer.size()==0){
try {
//进入阻塞-->放弃buffer锁
System.out.println("---->缓冲区空了<----");
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//随机取出一个
Product product = buffer.remove(new Random().nextInt(buffer.size()));
System.out.println(Thread.currentThread().getName()+"-->消费了:"+product.i);
//通知生产
buffer.notifyAll();
//返回产品
return product;
}
}
}
public class TestPC {
public static void main(String[] args) {
ProductBuffer buffer = new ProductBuffer();
// 生产者-1
new Thread(()->{
for (int i = 1; i <= 10; i++) {
//放入10个产品
buffer.push(new Product(i));
//sleep(1000): 每生产一个就停一下
}
},"A").start();
// 生产者-2
new Thread(()->{
for (int i = 11; i <= 20; i++) {
//放入10个产品
buffer.push(new Product(i));
}
},"C").start();
// 消费者-1
new Thread(()->{
for (int i = 1; i <= 10; i++) {
buffer.pop();
}
},"B").start();
// 消费者-2
new Thread(()->{
for (int i = 11; i <= 20; i++) {
buffer.pop();
}
},"D").start();
}
}
结果:
A生产了:1
A生产了:2
A生产了:3
A生产了:4
A生产了:5
缓冲区已满...
B-->消费了:1
B-->消费了:2
B-->消费了:5
B-->消费了:3
B-->消费了:4
---->缓冲区空了<----
C生产了:11
C生产了:12
C生产了:13
C生产了:14
C生产了:15
缓冲区已满...
B-->消费了:12
B-->消费了:15
B-->消费了:13
B-->消费了:14
B-->消费了:11
A生产了:6
A生产了:7
A生产了:8
A生产了:9
A生产了:10
D-->消费了:9
D-->消费了:10
D-->消费了:6
D-->消费了:7
D-->消费了:8
---->缓冲区空了<----
C生产了:16
C生产了:17
C生产了:18
C生产了:19
C生产了:20
D-->消费了:20
D-->消费了:19
D-->消费了:16
D-->消费了:18
D-->消费了:17