一. 线程安全
在实际程序运行中,有些程序是看做一个整体,必须一个线程运行结束,另外一个线程才能执行,不然会出现数据不安全的情况,比如茅坑一样,要一个一个来,不能一个还没有结束又来一个。
我们把这种线程一个一个执行的情况,叫做线程同步,也叫做线程安全。
可以使用synchronized来完成,通过对一个对象加锁的形式,来获取代码块执行的权限。如果因为没有获取对象锁而无法执行代码块,则进入阻塞状态。
1.同步代码块
使用synchronized修饰的代码块
synchronized(被加锁的对象){
//代码块
}
被加锁的对象,可以被任意线程拥有,前提是该对象没有被其他线程拥有
只有得到该对象,才能执行代码块的代码
被加锁的对象,在拥有他的线程执行完代码块后,就会释放该对象的锁
package com.qf;
public class Demo01 {
public static void main(String[] args) {
TicketWin win = new TicketWin();
Thread t1 = new Thread(win, "窗口一");
Thread t2 = new Thread(win, "窗口二");
Thread t3 = new Thread(win, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
class TicketWin implements Runnable {
int count = 1;
//创建一个对象
Object o = new Object();
// 窗口一买了第7张票
// 窗口三买了第8张票
@Override
public void run() {
while (true) {
/*
* synchronized 对看做一个整体的代码进行加锁
* 在加锁时,需要对一个对象加锁,意思是必须拥有该对象,才能执行代码块中的代码
* 怎么才能拥有该对象的锁,该对象没有被任何线程拥有时,你就可以获取拥有
*/
synchronized (o) {
if (count >100) {
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
count++;
}
}
}
}
2. 同步方法
synchronized可以修饰方法,多个线程,执行同一个对象的加锁方法时,线程是安全。
public synchronized void/返回值 方法名(参数列表) {
//方法体
}
package com.qf;
public class Demo02 {
public static void main(String[] args) {
/*
* 加锁方法
* 1. 多个线程,共有同一个的对象
* 2. 执行加锁方法
*/
TicketSeller seller = new TicketSeller();
TicketWin02 t1 = new TicketWin02(seller);
TicketWin02 t2 = new TicketWin02(seller);
TicketWin02 t3 = new TicketWin02(seller);
t1.start();
t2.start();
t3.start();
}
}
class TicketWin02 extends Thread {
TicketSeller seller;
public TicketWin02(TicketSeller seller) {
this.seller = seller;
}
@Override
public void run() {
while(seller.count <=100) {
seller.sell();
}
}
}
//售票员
class TicketSeller {
int count =1;
public synchronized void sell() {
if (count >100) {
return;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+count+"张票");
count++;
}
}
3. 死锁
两个线程互有对方需要的锁对象,但是都无法使用当前拥有的锁
package com.qf;
public class Demo03 {
public static void main(String[] args) {
Boy b = new Boy();
Girl g = new Girl();
b.start();
g.start();
}
}
class MyLock {
static Object a = new Object();
static Object b = new Object();
}
class Boy extends Thread {
@Override
public void run() {
synchronized (MyLock.a) {
System.out.println("男孩拿到了a");
//获取到b对象,才能执行这两行代码,Boy线程才能释放a对象
synchronized (MyLock.b) {
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃东⻄了...");
}
}
}
}
class Girl extends Thread {
@Override
public void run() {
synchronized (MyLock.b) {
System.out.println("女孩拿到了b");
synchronized (MyLock.a) {
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃东⻄了...");
}
}
}
}
二.线程通信
线程通信的意思是线程之间是可以进行通信的,也就是一个线程可以唤醒其他线程。
- 一些线程因为某些原因执行了wait方法而进入了等待队列,就是进入无法执行,等待某个条件下被唤醒重新执行的队列。
- 在进入等待队列时,会释放锁。
- 在某些线程执行了一定操作后,可以使用notifyAll方法,唤醒哪些进入等待队列的线程,重新回到就绪状态,继续执行。
主要方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CghURmAU-1650639147999)(image-20220421152117483.png)]
1. 生产者消费者模式
具体的实例:
一个盘子,可以存放包子,也可以被获取包子,且最多可以盛放6个包子
厨师可以往盘子里放包子
当盘子里有6个包子时,厨师需要等待顾客拿了包子再继续放
- 等待 wait()方法
- 继续放 被顾客线程唤醒 notifyAll()方法
顾客可以从盘子里拿包子
当盘子里没有包子时,顾客需要等待厨师放了包子后继续拿
- 等待 wait()方法
- 继续拿 被厨师线程唤醒 notifyAll()方法
package com.qf;
//包子类
public class Bun {
private Integer id; //编号
private String productName; //生产者
public Bun() {
}
public Bun(Integer id, String productName) {
super();
this.id = id;
this.productName = productName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
@Override
public String toString() {
return "Bun [id=" + id + ", productName=" + productName + "]";
}
}
package com.qf;
/*
* 盘子类
* 用来放包子,最多可以放6个
*
* 可以放包子
* 可以拿包子
*
*/
public class Plate {
//最多放6个
Bun[] arr = new Bun[6];
//包子个数
int count = 0;
//放包子
public synchronized void put(Bun bun) {
//判断有没有放满
while(count >= 6) { // 即使被唤醒,也需要重新判断有没有满
try {
this.wait(); //放满了就等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有放满,继续放包子
arr[count] = bun;
count++;
System.out.println(Thread.currentThread().getName()+"放了"+bun);
//唤醒哪些因为盘子中没有包子,而等待的消费者
this.notifyAll();
}
//拿包子
public synchronized void get() {
//判断盘子里有没有包子
while(count <=0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有包子,那包子 count=5
count--; //个数减一
Bun bun = arr[count]; //获取到最后一个包子
arr[count] = null;
System.out.println(Thread.currentThread().getName()+"拿了"+bun);
//唤醒厨师,放包子
this.notifyAll();
}
}
package com.qf;
//厨师放包子
public class Chef extends Thread{
Plate plate; //往这个盘子里放包子
public Chef(Plate plate, String name) {
super(name);
this.plate = plate;
}
@Override
public void run() {
//每个厨师放30个包子
for(int i=1; i<=30; i++) {
plate.put(new Bun(i, getName()));
}
}
}
package com.qf;
//顾客拿包子
public class Customer extends Thread {
Plate plate; //往这个盘子里取包子
public Customer(Plate plate, String name) {
super(name);
this.plate = plate;
}
@Override
public void run() {
for(int i=1; i<=30; i++) {
plate.get();
}
}
}
package com.qf;
public class Demo04 {
public static void main(String[] args) {
Plate plate = new Plate();
Chef c1 = new Chef(plate, "小锋");
Chef c2 = new Chef(plate, "小伟");
Customer cu1 = new Customer(plate, "欢欢");
Customer cu2 = new Customer(plate, "乐乐");
c1.start();
c2.start();
cu1.start();
cu2.start();
}
}
三.匿名内部类
语法:
接口 变量 = new 接口(){
//实现接口的抽象方法
}
变量.方法名();
可以使用这种方式,更加方便的定义接口一种实现类的对象
package com.qf;
public class Demo05 {
public static void main(String[] args) {
A a = new AImpl();
a.test();
A aa = new A(){
@Override
public void test() {
System.out.println("匿名内部类--- 实现test方法");
}
};
aa.test();
}
}
interface A {
void test();
}
class AImpl implements A {
@Override
public void test() {
System.out.println("AImpl 实现 test方法");
}
}
package com.qf;
public class Demo05_2 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
private int count = 100;
@Override
public void run() {
while(true) {
synchronized (this) {
if (count <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖了" + count + "张票");
count--;
}
}
}
};
Thread t1 = new Thread(runnable,"窗口1");
Thread t2 = new Thread(runnable,"窗口2");
Thread t3 = new Thread(runnable,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
四.线程池
1. 固定长度线程池
ExecutorService es = Executors.newFixedThreadPool(3);
package com.qf;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
/*
*
* 创建指定长度的线程池
* newFixedThreadPool(线程的个数)
Fixed (固定的)
*/
ExecutorService es = Executors.newFixedThreadPool(3);
/*
* 定义线程被选中,执行的业务逻辑
*/
Runnable able = new Runnable() {
@Override
public void run() {
for(int i=0; i<30; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
};
/*
* submit表示开启一个线程
* 参数为一个Runnable的实现类对象,目的是定义该线程被调用,执行的逻辑
*/
for(int i=1; i<=3; i++) {
es.submit(able);
}
//关闭线程池
es.shutdown();
}
}
2. 可变长度线程池
ExecutorService es = Executors.newCachedThreadPool();
package com.qf;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo02 {
public static void main(String[] args) {
/*
*
* 创建动态长度的线程池
* newCachedThreadPool(线程的个数)
* Cached (缓存)
*/
ExecutorService es = Executors.newCachedThreadPool();
/*
* 定义线程被选中,执行的业务逻辑
*/
Runnable able = new Runnable() {
@Override
public void run() {
for(int i=0; i<30; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
};
/*
* submit表示开启一个线程
* 参数为一个Runnable的实现类对象,目的是定义该线程被调用,执行的逻辑
*/
for(int i=1; i<=4; i++) {
es.submit(able);
}
//关闭线程池
es.shutdown();
}
}
3. 单个线程线程池
ExecutorService es = Executors.newSingleThreadExecutor();
package com.qf;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo03 {
public static void main(String[] args) {
/*
*
* 创建单个线程的线程池
* newSingleThreadExecutor(线程的个数)
*
*/
ExecutorService es = Executors.newSingleThreadExecutor();
/*
* 定义线程被选中,执行的业务逻辑
*/
Runnable able = new Runnable() {
@Override
public void run() {
for(int i=0; i<30; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
};
/*
*
* 一个线程被用了四次
* 而不是创建了四个线程
*/
for(int i=1; i<=4; i++) {
es.submit(able);
}
//关闭线程池
es.shutdown();
}
}
五.有返回值的线程
1. Callable
package com.qf;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo04 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/*
*
*
* 我们想执行一个线程,得到一个数据
* 通过直接创建Thread的对象形式完成
* Callable (可调用的)
*
* - 开启一个线程计算1到100的和
*
*/
//1. 定义一个Callable对象,call方法定义线程执行的业务逻辑
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=100; i++) {
sum+=i;
}
return sum;
}
};
//2. Callable对象转化为可执行的任务
FutureTask<Integer> task=new FutureTask<>(call);
//3. 执行该任务, 启动线程
Thread t = new Thread(task);
t.start();
//4. 通过任务,就可以获取返回值 当通过get获取结果时,一定在线程执行结束后
Integer result = task.get();
System.out.println(result);
}
}
2. Future
package com.qf;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo05 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/*
*
*
* 我们想执行一个线程,得到一个数据
* 通过线程池实现
*
* - 开启一个线程计算1到100的和
*
*/
//1. 定义一个Callable对象,call方法定义线程执行的业务逻辑
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=50; i++) {
sum+=i;
}
return sum;
}
};
Callable<Integer> call2 = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=51; i<=100; i++) {
sum+=i;
}
return sum;
}
};
//创建可变线程的线程池
ExecutorService es = Executors.newCachedThreadPool();
//使用线程中的对象开启线程 1-50
Future<Integer> task = es.submit(call);
Integer result1 = task.get();
// 51-100
Future<Integer> task2 = es.submit(call2);
Integer result2 = task2.get();
System.out.println(result1+result2);
es.shutdown();
}
}