1 简介
程序-静态概念
进程-程序执行一次的过程
Process:进程
Thread:线程
2 线程的创建
2.1 Thread类
新建类继承Thread类并重写run方法,在主线程中使用start函数开启子线程。
public class Main extends java.lang.Thread {
public Main() {
}
@Override
public void run() {
super.run();
}
public static void main(String[] args) {
}
}
Demo示例
run()方法中写了线程要执行的函数,在主线程中调用start方法开启线程函数
public class Main extends java.lang.Thread {
public Main() {
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("Thread:" + i);
}
}
public static void main(String[] args) {
Main main = new Main();
main.start();
for (int i = 0; i < 20; i++) {
System.out.println("Main:" + i);
}
}
}
如果调用run方法而不是start方法,那么程序会先执行完run方法再执行主线程的程序
2.2 网图下载
百度搜索commons-io.jar包,apache开发,导入到项目中,并将其作为library添加
编写下载工具类WebDownloader
class WebDownloader{
public void downloader(String url, String name) throws IOException {
FileUtils.copyURLToFile(new URL(url), new File(name));
}
}
编写多线程类,重写run方法,并在主线程中生成多个线程实现文件下载
public class Main extends java.lang.Thread {
private String url;
private String name;
public Main(String url, String name) {
this.url = url;
this.name = name;
}
public Main() {
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
try {
webDownloader.downloader(url, name);
System.out.println("下载了文件名为:"+name);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Main main1 = new Main("https://cdn1.zzzmh.cn/blog/image/posts/full/86.jpg", "1.jpg");
Main main2 = new Main("https://www.mrhelloworld.com/resources/articles/articles_header/2020/06/03/header.jpg", "2.jpg");
Main main3 = new Main("https://img-blog.csdnimg.cn/img_convert/cd58f38c70d92834e645c3adeb1138fe.png", "3.jpg");
main1.start();
main2.start();
main3.start();
}
}
2.3 Runnable接口
类实现Runnable接口,并重写run方法,在主线程中新建Thread对象并将此类对象作为参数传入并调用thread对象的start方法
public class Main implements Runnable{
public static void main(String[] args) {
Main main = new Main();
new Thread(main).start();
for (int i = 0; i < 20; i++) {
System.out.println("Main:" + i);
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("Thread:" + i);
}
}
}
个人感觉:与2.1相比只是Thread类的初始化方式不同,
推荐使用Runnable接口,因为Java单继承,继承自Thread类的话限制了灵活性
2.4 多人买火车票-并发
Thread.currentThread().getName():获取线程名字
public class Main implements Runnable{
private int ticketNums = 10;
public static void main(String[] args) {
Main main = new Main();
new Thread(main, "111").start();
new Thread(main, "222").start();
new Thread(main, "333").start();
}
@Override
public void run() {
while (true){
if(ticketNums <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + " The " + ticketNums-- + " ticket!");
}
}
存在问题:同一张票会被多个人抢到!!!
2.5 龟兔赛跑
线程函数:跑,并且在跑完后判断是否游戏结束
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
boolean flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + " run " + i + " step!");
}
}
设置赢家标志位winner,判断比赛结束函数:
private boolean gameOver(int steps){
if(winner != null){
return true; // 已有赢家
}{
if(steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
}
// 没跑到100步,那游戏就不暂停
return false;
}
编写主线程:
public static void main(String[] args) {
Main main = new Main();
new Thread(main, "Rabbit").start();
new Thread(main, "Turtle").start();
}
如何控制两者有不同的速度?怎么让兔子睡觉?
修改run方法,如果当前线程为兔子的话,就Thread.sleep(0
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 兔子睡觉
if(Thread.currentThread().getName() == "Rabbit" && i%10 == 0){
try {
Thread.sleep(10); // 睡觉
System.out.println("Rabbit Sleep");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
boolean flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + " run " + i + " step!");
}
}
2.6 Callable接口(了解)
**好处:**可以有返回值
类实现Callable接口并重写call方法替代run方法
// 下载工具类
class WebDownloader{
public void downloader(String url, String name) throws IOException {
FileUtils.copyURLToFile(new URL(url), new File(name));
}
}
public class Main implements Callable
@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
try {
webDownloader.downloader(url, name);
System.out.println("下载了文件名为:"+name);
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
在主线程中,生成子线程对象,开启服务,提交子线程任务,并获取结果,然后关闭服务
public static void main(String[] args) throws ExecutionException, InterruptedException {
Main main1 = new Main("https://cdn1.zzzmh.cn/blog/image/posts/full/86.jpg", "1.jpg");
Main main2 = new Main("https://www.mrhelloworld.com/resources/articles/articles_header/2020/06/03/header.jpg", "2.jpg");
Main main3 = new Main("https://img-blog.csdnimg.cn/img_convert/cd58f38c70d92834e645c3adeb1138fe.png", "3.jpg");
// 创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交执行
Future submit1 = executorService.submit(main1);
Future submit2 = executorService.submit(main2);
Future submit3 = executorService.submit(main3);
// 获取结果
boolean rs1 = (boolean) submit1.get();
boolean rs2 = (boolean) submit2.get();
boolean rs3 = (boolean) submit3.get();
executorService.shutdownNow();
}
3 静态代理
静态代理与Thread
例子:婚庆公司代理你的婚礼,本质上是你结婚,但婚庆公司负责婚前和婚后
interface Marry{
void HappyMarry();
}
// 真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("You Marry!");
}
}
代理类:
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before(); // 代理内容
this.target.HappyMarry(); // 真实操作
after(); // 代理内容
}
private void before(){
System.out.println("yaoxi");
}
private void after(){
System.out.println("not yaoxi");
}
}
代理类实现代理流程:
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
- 代理对象和真实角色要实现同一接口
- 代理对象代理真实角色
好处:代理对象可以做真实对象做不了的事情
4 Lambda表达式
为什么在多线程里讲?
因为Runnable接口中只有一个run方法,非常适合lambda表达式
4.1 函数式接口
接口中只有一个抽象方法就叫函数式接口,可以用Lambda表达式
public interface Runnable{
public abstract void run();
}
4.2 普通思路
定义一个函数式接口:
interface ILike{
void lambda();
}
定义接口实现类:
class Like implements ILike{
@Override
public void lambda() {
System.out.println("this is a lambda");
}
}
普通思路调用方法:
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like = new Like();
like.lambda();
}
}
4.3 静态内部类优化
public class Main{
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("this is a lambda2");
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
}
}
4.4 局部内部类优化
类的定义放在函数中
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("this is a lambda3");
}
}
like = new Like3();
like.lambda();
}
}
4.5 匿名内部类
调用的时候现场写代码
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
like = new ILike() {
@Override
public void lambda() {
System.out.println("this is a lambda4");
}
};
like.lambda();
}
}
4.6 lambda表达式简化
将匿名内部类中lambda函数名后面的部分保留,剩下的删掉,即为lambda表达式
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like;
like = () -> {
System.out.println("this is a lambda5");
};
like.lambda();
}
分析:怎么实现的?
因为函数式接口中只有一个方法,所以要实现只能实现这一个接口,所以这种写法默认实现这个方法,所以前面的部分予以省略,like的类型已经指定接口信息
4.7 带参lambda表达式
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like;
like = (int a) -> {
System.out.println("this is lambda " + a);
};
like.lambda(33);
}
}
// 定义函数式接口
interface ILike{
void lambda(int a);
}
4.7.1 简化1:去掉参数类型
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like;
like = (a) -> {
System.out.println("this is lambda " + a);
};
like.lambda(33);
}
}
// 定义函数式接口
interface ILike{
void lambda(int a);
}
4.7.2 简化2:去掉参数括号(适用于单个参数)
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like;
like = a -> {
System.out.println("this is lambda " + a);
};
like.lambda(33);
}
}
// 定义函数式接口
interface ILike{
void lambda(int a);
}
4.7.3 简化3:去掉lambda表达式的花括号(只适用于一行代码)
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
ILike like;
like = a -> System.out.println("this is lambda " + a);
like.lambda(33);
}
}
// 定义函数式接口
interface ILike{
void lambda(int a);
}
5 停止线程
- 建议利用次数使线程正常停止
- 建议使用标志位让线程停止
- 不要用stop或者destroy等过时方法停止线程
public class Main implements Runnable{
// 设置标志位
private boolean flag = true;
public static void main(String[] args) throws ExecutionException, InterruptedException {
Main main = new Main();
new Thread(main).start();
for (int i = 0; i < 10000; i++) {
if(i == 9000){
// 自己编写的stop函数更换标志位
main.stop();
System.out.println("Thread Stop!");
}
}
}
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run : " + i++);
}
}
public void stop(){
this.flag = false;
}
}
使用外部标志位flag控制run方法,在外部通过控制条件实现flag标志位的改变
6 线程休眠
Thread.sleep() :放大问题的发生性
作用:模拟网络延时,或者倒计时
使用sleep函数打印当前系统时间
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Date startTime = new Date(System.currentTimeMillis());
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}
}
}
7 线程礼让
重新竞争CPU,具体结果看CPU心情,有可能礼让后,保持原执行顺序不变。
Thread.yield()方法让当前线程礼让一下
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyYield myYield = new MyYield();
new Thread(myYield, "A").start();
new Thread(myYield, "B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "Start");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "End");
}
}
8 Join
先执行调用join的线程,然后执行其他的线程
public class Main implements Runnable{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Main main = new Main();
Thread thread = new Thread(main);
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 200){
thread.join(); // thread 线程插队,该线程执行完后,其他线程再执行
}
System.out.println("Main : " + i);
}
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("Thread " + i);
}
}
}
当主线程执行200次循环时,不管子线程执行到什么地步,因子线程join,主线程让步于子线程,等子线程执行完再执行完剩下的步骤
9 观测线程状态
Thread.getState()获取线程状态
public class Main{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("666");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while(state != Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
}
10 线程优先级
优先级高的线程通常会被优先执行,但优先级高的不是100%在优先级低的前被执行,先设置优先级,后start启动
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyPriority myPriority1 = new MyPriority();
Thread thread1 = new Thread(myPriority1);
Thread thread2 = new Thread(myPriority1);
Thread thread3 = new Thread(myPriority1);
Thread thread4 = new Thread(myPriority1);
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
thread1.start();
thread2.setPriority(8);
thread2.start();
thread3.setPriority(3);
thread3.start();
thread4.setPriority(7);
thread4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
11 守护线程
线程分为用户线程和守护线程
守护线程作用:
负责垃圾回收等。。
不需要管守护线程,它会随着主线程结束而自动处理
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); // 设置为守护线程
thread.start();
new Thread(you).start(); // 用户线程
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("God protect you!");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("Happy every day");
}
System.out.println("Goodbye!");
}
}
12 线程同步安全
多线程操作统一资源时,需要线程同步,规定各线程的执行顺序。使用锁保证了安全性,也损失了一定的性能。
12.1 线程不安全1:买票
买票:
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
BuyTicket buyTicket = new BuyTicket();
Thread thread1 = new Thread(buyTicket, "A");
Thread thread2 = new Thread(buyTicket, "B");
Thread thread3 = new Thread(buyTicket, "C");
thread1.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10;
// 线程停止方式
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private void buy() throws InterruptedException {
// 是否有票
if(ticketNums <= 0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " buys " + ticketNums--);
}
}
有人会拿到-1,或者有重票,这就是线程不安全的表现
原因:并发,没有让线程排队
12.2 线程不安全2:取钱
分析思路:账户里有100,双方均看见里面有100,均可以从里面取50,如果一方取50,另一方取100,在各自看来这是合法的操作,如果两者同时取款,那将取出150万,明显余额不够取,此时为线程不安全。
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Account account = new Account("基金", 100);
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
Drawing you = new Drawing(account, 50, "你");
girlFriend.start();
you.start();
}
}
class Account{
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
// 银行 模拟取款
class Drawing extends Thread{
Account account;
// 取了多少钱
int drawingMoney;
//现有的钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name); // 调用父类构造方法,初始化名字
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName() + " no enough money!");
return;
}
// 双方均等待,均看到账户里有100
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + " rest money is: " + account.money);
System.out.println(this.getName() + "now money is: " + nowMoney); // this.getName()获取线程名字
}
}
会出现账户余额为-50的情况,明显有问题
12.3 线程不安全3:集合
创建20000个线程,将各线程的名字存到list中,主线程停等2秒,足够线程创建完成
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 20000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(2000);
System.out.println(list.size());
}
}
发现:list中的个数不足20000,因为并发写入时,有线程同时向同一位置写入,导致个数不足,是线程不安全的表现
13 线程同步-解决线程不安全
synchronized
13.1 买票
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
BuyTicket buyTicket = new BuyTicket();
Thread thread1 = new Thread(buyTicket, "A");
Thread thread2 = new Thread(buyTicket, "B");
Thread thread3 = new Thread(buyTicket, "C");
thread1.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10;
// 线程停止方式
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private synchronized void buy() throws InterruptedException {
// 是否有票
if(ticketNums <= 0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " buys " + ticketNums--);
}
}
在buy方法的定义中加了synchronized
13.2 取钱
synchronized该锁谁?默认锁this,实际该锁被操作的资源 ,所以,此处该使用synchronized块
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Account account = new Account("基金", 100);
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
Drawing you = new Drawing(account, 50, "你");
girlFriend.start();
you.start();
}
}
class Account{
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
// 银行 模拟取款
class Drawing extends Thread{
Account account;
// 取了多少钱
int drawingMoney;
//现有的钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name); // 调用父类构造方法,初始化名字
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
// 锁account
synchronized (account){
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName() + " no enough money!");
return;
}
// 双方均等待,均看到账户里有100
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + " rest money is: " + account.money);
System.out.println(this.getName() + "now money is: " + nowMoney); // this.getName()获取线程名字
}
}
}
synchronized块,括号中指明要同步的对象,本例中需要锁的是account,保证让其他线程依次访问account中的余额
13.3 集合
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 20000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(2000);
System.out.println(list.size());
}
}
在run方法中添加synchronized块,如果在for循环外添加,不work
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<String> list = new ArrayList<String>();
synchronized (list){
for (int i = 0; i < 20000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
}
Thread.sleep(2000);
System.out.println(list.size());
}
}
output:19998
14 死锁
互相等待,想拿对方锁住的资源
public class Main {
public static void main(String[] args) {
MakeUp a = new MakeUp(0, "A");
MakeUp b = new MakeUp(1, "B");
a.start();
b.start();
}
}
class LipStick{
}
class Mirror{
}
class MakeUp extends Thread{
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice;
String name;
public MakeUp(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 互相持有对方的锁
private void makeup() throws InterruptedException {
if(choice == 0){
synchronized (lipStick){
// 获得口红的锁
System.out.println(this.name + " 获得了口红的锁");
Thread.sleep(1000);
synchronized (mirror){
// 1s后想拿镜子
System.out.println(this.name + " 1s后拿到镜子的锁");
}
}
}else {
synchronized (mirror){
// 获得镜子的锁
System.out.println(this.name + " 获得了镜子的锁");
Thread.sleep(1000);
synchronized (lipStick){
// 1s后想拿口红
System.out.println(this.name + " 1s后拿到口红的锁");
}
}
}
}
}
此时,A等B释放镜子锁,才能执行完毕,而B等A释放口红锁才能执行完毕
该如何解决?
修改代码,用完就放锁
public class Main {
public static void main(String[] args) {
MakeUp a = new MakeUp(0, "A");
MakeUp b = new MakeUp(1, "B");
a.start();
b.start();
}
}
class LipStick{
}
class Mirror{
}
class MakeUp extends Thread{
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice;
String name;
public MakeUp(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 互相持有对方的锁
private void makeup() throws InterruptedException {
if(choice == 0){
synchronized (lipStick) {
// 获得口红的锁
System.out.println(this.name + " 获得了口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){
// 1s后想拿镜子
System.out.println(this.name + " 1s后拿到镜子的锁");
}
}else {
synchronized (mirror){
// 获得镜子的锁
System.out.println(this.name + " 获得了镜子的锁");
Thread.sleep(1000);
}
synchronized (lipStick){
// 1s后想拿口红
System.out.println(this.name + " 1s后拿到口红的锁");
}
}
}
}
各自把用完的资源释放,就不影响后续运行
15 Lock锁
显式定义同步锁,取代synchronized
Lock本身是个接口,而ReentrantLock类(可重入锁)实现了这个接口
不加锁时:
public class Main {
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy, "A").start();
new Thread(buy, "B").start();
new Thread(buy, "C").start();
}
}
class Buy implements Runnable{
private int ticketNums = 10;
@Override
public void run() {
while (true){
if(ticketNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
}else{
break;
}
}
}
}
上例会出现线程不安全情况
使用lock锁
锁通常被定义为私有常量,并写在try语句中,在finally语句中写解锁语句
public class Main {
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy, "A").start();
new Thread(buy, "B").start();
new Thread(buy, "C").start();
}
}
class Buy implements Runnable{
private int ticketNums = 10;
// 可重入锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if(ticketNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
}else{
break;
}
}finally {
lock.unlock();
}
}
}
}
synchronized可以锁方法
Lock锁性能更好
16 线程通信-生产者与消费者
16.1 管程法
利用生产者与消费者之间的缓冲区解决通信问题
public class Main {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Consumer(synContainer).start();
}
}
// 生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
// 生产
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Produce " + i + " chicken");
}
}
}
// 消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.pop();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Consume " + i + " chicken");
}
}
}
// 产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
// 缓冲区
class SynContainer{
// 容器大小
Chicken[] chickens = new Chicken[10];
// 计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Chicken chicken) throws InterruptedException {
// 容器满,等待消费
if(count == chickens.length){
// 通知消费者消费,生产暂停
wait();
}
//容器未满,push产品
chickens[count] = chicken;
count++;
// 通知消费
notifyAll();
}
// 消费者消费产品
public synchronized Chicken pop() throws InterruptedException {
if(count == 0){
// 等待生产
wait();
}
count--;
Chicken chicken = chickens[count];
// 通知生产者生产
notifyAll();
return chicken;
}
}
需要暂停的时候使用wait等待另一方操作,在本方操作完毕后使用notify方法通知对方可以操作
16.2 信号灯法
使用标志位实现
public class Main {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生产者 -- 演员
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
try {
this.tv.play("AAAAA");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
try {
this.tv.play("BBBBBBBBB");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
// 消费者 -- 观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
// 产品 -- 节目
class TV{
// 演员表演,观众等待 true
// 观众观看,演员等待 false
String voice;
boolean flag = true;
// 表演
public synchronized void play(String voice) throws InterruptedException {
// 演员需要等待
if(!flag){
this.wait();
}
System.out.println("Player:" + voice);
// 通知观众
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
public synchronized void watch() throws InterruptedException {
if(flag){
this.wait();
}
System.out.println("Watch:" + this.voice);
// 看完,通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
使用flag控制各自该做的事情,使用flag判断什么时候用wait什么时候用notify
17 线程池
经常创建销毁线程会消耗很大的资源,线程池能够提高性能
public class Main {
public static void main(String[] args) {
// 创建服务,创建线程池
// newFixedThreadPool : 参数 :线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 关闭链接
service.shutdownNow();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}