本文总结自,B站-遇见狂神说
1. 进程与线程
程序
指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。
进程(Process)
是程序的一次过程,它是一个动态的概念。是系统资源分配的单位。
线程
- 一个进程可以包含若干个线程
- 一个进程中至少有一个线程(不然没有意义)
- 线程是CPU调度和执行的单位
多线程
真正的多线程是指有多个CPU,即多核。如果是模拟出来的多线程(即一个PCU的情况下),在同一时间点,CPU只能执行一个代码,因为切换的很快,所以产生同时执行的错觉。
2. 核心内容
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。
- 主线程
main()
,为系统的入口,用于执行整个程序。 - 在一个进程中,如果开辟了多个线程,线程 的运行由调度器安排,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在抢夺资源的问题,需要加入并发控制;
- 线程会带来额外开销,如CPU调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
3. 三种创建方式
继承Thread类
public class ThreadExtends extends Thread {
@Override
public void run(){
// run方法线程体
for (int i = 0; i <= 20; i++) {
System.out.println("run方法线程体--"+ i);
}
}
public static void main(String[] args) {
// main线程,主线程
ThreadExtends t = new ThreadExtends();
// 开启线程(和主线程同时跑)
t.start();
// 先跑子线程
// t.run();
for (int i = 0; i <= 20; i++) {
System.out.println("主线程--"+ i);
}
}
}
结果:
...
run方法线程体--6
run方法线程体--7
run方法线程体--8
主线程--2
run方法线程体--9
主线程--3
...
案例:网图下载
实现多线程下载图片【导包:commons-io-2.7.jar】
public class ThreadDown_Extends extends Thread {
/**
* 网络图片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全参构造方法
* @param url
* @param name
*/
public ThreadDown_Extends(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下载图片线程的执行体
*/
@Override
public void run(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
}
下载类
public class WebDownloader{
/**
* 下载方法
* @param url
* @param name
*/
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloder方法出现问题...");
}
}
}
测试类
public class TestThread {
public static void main(String[] args) {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times+".png";
testExtends(url, name);
}
/**
* 测试创建多线成方法:继承线程类
*/
private static void testExtends(String url, String name) {
ThreadDown_Extends td1 = new ThreadDown_Extends(url, name);
ThreadDown_Extends td2 = new ThreadDown_Extends(url, name);
ThreadDown_Extends td3 = new ThreadDown_Extends(url, name);
td1.start();
td2.start();
td3.start();
}
}
实现Runable接口
public class ThreadRunnable implements Runnable {
@Override
public void run(){
// run方法线程体
for (int i = 0; i <= 20; i++) {
System.out.println("run方法线程体--"+ i);
}
}
public static void main(String[] args) {
// main线程,主线程
ThreadRunnable r = new ThreadRunnable();
// 调用开启多线程(两个线程同时跑)
new Thread(r).start();
for (int i = 0; i <= 20; i++) {
System.out.println("主线程--"+ i);
}
}
}
案例:网图下载
public class ThreadDown_Runnable implements Runnable {
/**
* 网络图片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全参构造方法
* @param url
* @param name
*/
public ThreadDown_Runnable(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下载图片线程的执行体
*/
@Override
public void run(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
}
下载类【与继承Thread类案例相同】
测试类
public class TestThread {
public static void main(String[] args) {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times+".png";
testRunnable(url, name);
}
/**
* 测试创建多线成方法:实现Runnable接口
*/
private static void testRunnable(String url, String name) {
// 创建Runnable接口的实现类对象
ThreadDown_Runnable r = new ThreadDown_Runnable(url, name);
// 创建线程对象,通过线程对象来开启我们的线程,【代理】
/*Thread t = new Thread(r);
t.start();*/
// 简洁版
new Thread(r).start();
}
}
案例:龟兔赛跑
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 模拟兔子睡觉
- 最终乌龟赢得比赛
代码:
package cn.luis.race;
/**
* @ClassName Race
* @Description TODO :龟兔赛跑,(兔子休息,乌龟赢得比赛)
* @Author L
* @Date 2020.06.15 0:13
* @Version 1.0
* @Remark
**/
public class Race implements Runnable {
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 模拟兔子睡觉(名为兔子的进程和每走十步,兔子睡一下)
if ("流氓兔".equals(Thread.currentThread().getName()) && i % 10 == 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断比赛是否结束
boolean flag = gameOver(i);
// 如果比赛结束了
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + " -- 跑了 --" + i + " --步");
}
}
/**
* 判断是否完成比赛
* @param step
* @return
*/
private boolean gameOver(int step) {
// 判断是否有获胜者
if (winner != null) {
// 存在
return true;
}
if (step >= 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is : " + winner);
return true;
}
return false;
}
/**
* 主方法
* @param args
*/
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "小乌龟").start();
new Thread(race, "流氓兔").start();
}
}
结果:
...
小乌龟 -- 跑了 --97 --步
小乌龟 -- 跑了 --98 --步
小乌龟 -- 跑了 --99 --步
Winner is : 小乌龟
实现Callable接口
-
实现此接口需要返回值类型
-
从写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
-
提交执行
Future<Boolean> r1 = service.submit(c1);
-
获取结果
boolean rs1 = r1.get();
-
关闭服务
service.shutdownNow();
案例:网图下载
package cn.luis.callable;
import cn.luis.down.WebDownloader;
import java.util.concurrent.Callable;
/**
* @ClassName ThreadDown
* @Description TODO 实现Callable接口:实现多线程下载图片
* @Author L
* @Date 2020.06.14 10:39
* @Version 1.0
* @Remark
**/
public class ThreadDown_Callable implements Callable<Boolean> {
/**
* 网络图片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全参构造方法
* @param url
* @param name
*/
public ThreadDown_Callable(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下载图片线程的执行体
*/
@Override
public Boolean call(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下载了文件名为:"+name);
return true;
}
}
测试类:
package cn.luis.callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @ClassName TestThread
* @Description TODO 测试类
* @Author L
* @Date 2020.06.14 11:45
* @Version 1.0
* @Remark TODO callable接口好处:1.可以定义返回值 2.可以抛出异常
**/
public class TestThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times + ".png";
testCallable(url, name);
}
/**
* 测试创建多线程方法:实现Callable接口
*/
private static void testCallable(String url, String name) throws ExecutionException, InterruptedException {
// 创建Callable接口的实现类对象
ThreadDown_Callable c1 = new ThreadDown_Callable(url, name);
ThreadDown_Callable c2 = new ThreadDown_Callable(url, name);
ThreadDown_Callable c3 = new ThreadDown_Callable(url, name);
// 创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> r1 = service.submit(c1);
Future<Boolean> r2 = service.submit(c2);
Future<Boolean> r3 = service.submit(c3);
// 获取结果(可以抛出异常)
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
// 打印结果
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
// 关闭服务
service.shutdownNow();
}
}
4. Lambda表达式
-
λ
希腊字母中排序第十一位的字母。 -
避免匿名内部类定义过多,只展示核心逻辑。
-
函数式(接口)编程思想:接口只包含唯一一个抽象方法
代码:
接口
public interface ILike {
void like();
}
实现类
public class Like implements ILike {
@Override
public void like() {
System.out.println("i like lambda 1!");
}
}
测试类
public class testLambda {
// 第二种:静态内部类
static class Like2 implements ILike {
@Override
public void like() {
System.out.println("i like lambda 2!");
}
}
public static void main(String[] args) {
// 第一种:接口引用指向实现类
ILike like = new Like();
like.like();
// // 第二种:静态内部类的使用
like = new Like2();
like.like();
// 第三种:局部内部类
class Like3 implements ILike {
@Override
public void like() {
System.out.println("i like lambda 3!");
}
}
like = new Like3();
like.like();
// 第四种:匿名内部类,没有类的名字,必须借助接口或者父类
like = new ILike() {
@Override
public void like() {
System.out.println("i like lambda 4!");
}
};
like.like();
// 第五种:lambda表达式
like = () -> System.out.println("i like lambda 5!");
like.like();
}
}
表达式简化
-
参数类型,要么都写要么都不写
(a,b)-> {...}
-
省略大括号,只有一条语句时才可以
(a,b)-> System.out.println("aaa");
-
省略参数小括号,只有一个参数时才可以
c -> System.out.println("aaa");
5. 静态代理
要求:
- 真实对象和代理对象都要实现同一接口
- 代理对象必须要代理真实角色(将真实角色传入)
优点:
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
案例:婚庆公司和结婚
接口
public interface Marry {
void happyMarry();
}
新郎
public class Xinlang implements Marry {
@Override
public void happyMarry() {
System.out.println("小明要结婚啦!");
}
}
婚庆公司:帮助你结婚(是个代理角色)
public class HunQing implements Marry {
/**
* 代理谁 --> 真实目标角色
*/
private Marry target;
public HunQing(Marry target) {
this.target = target;
}
/**
* 相当于增强了方法(功能多了)
*/
@Override
public void happyMarry() {
before();
// 真实对象调用方法
this.target.happyMarry();
after();
}
/**
* 婚后
*/
private void after() {
System.out.println("婚后--收尾款");
}
/**
* 婚前
*/
private void before() {
System.out.println("婚前--布置婚礼殿堂");
}
}
测试类
public class StaticProxy {
public static void main(String[] args) {
new HunQing(new Xinlang()).happyMarry();
// 线程底部原理与结婚案例相同
new Thread(() -> System.out.println("I love You!")).start();
}
}
6. 线程状态
7. 线程操作
线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停单签正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程【废弃】 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
- 利用次数,不建议死循环
- 建议使用标志位
- 不要使用JDK不建议的方法,如:stop,destory
代码:
public class TestStop implements Runnable {
/**
* 1.设置标志位
*/
private boolean flag = true;
/**
* 重写线程执行方法
*/
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("子线程 -- " + i++);
}
}
/**
* 2.设置一个公开的方法停止线程,转换标志位
*/
public void stop() {
this.flag = false;
}
/**
* 主方法
*
* @param args
*/
public static void main(String[] args) {
// 常见Runnable接口实现类
TestStop stop = new TestStop();
// 模拟多线程
for (int i = 0; i < 10; i++) {
System.out.println("主线程 -- " + i);
// 满足条件时停止子线程
if (i == 9) {
stop.stop();
System.out.println("线程该停止了!");
}
// 开启多线程
new Thread(stop).start();
}
}
}
结果:
主线程 -- 0
主线程 -- 1
主线程 -- 2
主线程 -- 3
线程该停止了!
主线程 -- 4
线程休眠
格式
Thread.sleep(1000);
代码:
package cn.luis.state;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 线程休眠
* 1. 模拟网络延时:买票
* 2. 倒计时
**/
public class TestSleep {
public static void main(String[] args) {
/* down1();
down2();*/
time();
}
/**
* 打印当前系统时间
*/
private static void time() {
while (true) {
try {
Thread.sleep(1000);
Date date = new Date();
System.out.println(new SimpleDateFormat("YYYY:HH:mm:ss").format(date));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 倒计时2
*/
private static void down2() {
int num = 5;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if (num <= 0) {
break;
}
}
System.out.println("时间到!");
}
/**
* 倒计时1
*/
public static void down1() {
for (int i = 5; i > 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
System.out.println("时间到!");
}
}
线程礼让
格式
Thread.yield();
代码:
礼让不一定成功!
public class TestYield {
public static void main(String[] args) {
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()+"线程开始执行!");
// 礼让
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行!");
}
}
结果:(礼让成功)
a线程开始执行!
b线程开始执行!
a线程停止执行!
b线程停止执行!
线程强制执行
相当于插队
格式:(中断异常)
Thread.join();
代码:
public class TestJoin implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("线程VIP来啦!");
}
}
public static void main(String[] args) {
TestJoin join = new TestJoin();
// 启动子线程
Thread t = new Thread(join);
t.start();
// 主线程
for (int i = 0; i < 5; i++) {
if (i == 2) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main" + i);
}
}
}
结果:
main0
main1
线程VIP来啦!
线程VIP来啦!
线程VIP来啦!
main2
main3
main4
观测线程状态
格式:
Thread.State state = thread.getState();
代码:
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程终止啦~");
});
// 观察状态
Thread.State state = thread.getState();
// NEW
System.out.println(state);
// 观察启动后
// 启动线程
thread.start();
// RUN
System.out.println(thread.getState());
// 只要线程不终止,就一直输出状态
while(state != Thread.State.TERMINATED){
Thread.sleep(100);
// 更新线程状态
state = thread.getState();
// 输出状态
System.out.println(state);
}
// 线程中断或者结束后不能再次启动
thread.start();
}
}
结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
...
TIMED_WAITING
线程终止啦~
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:804)
at cn.luis.state.TestState.main(TestState.java:44)
线程优先级
并不是一定按照优先级执行!
格式:
thread.setPriority(1);
thread.setPriority(Thread.MAX_PRIORITY);
代码:
public class TestPriority {
public static void main(String[] args) {
// 主线程默认优先级
System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
MyPriority priority = new MyPriority();
Thread t1 = new Thread(priority);
Thread t2 = new Thread(priority);
Thread t3 = new Thread(priority);
Thread t4 = new Thread(priority);
Thread t5 = new Thread(priority);
Thread t6 = new Thread(priority);
// 先设置优先级,再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(6);
t4.start();
t5.setPriority(8);
t5.start();
// 10
t6.setPriority(Thread.MAX_PRIORITY);
t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
}
}
结果:
main -- 5
Thread-5 -- 10
Thread-4 -- 8
Thread-0 -- 5
Thread-3 -- 6
Thread-2 -- 4
Thread-1 -- 1
8. 守护线程
格式:
thread.setDaemon(true);
代码;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Life life = new Life();
Thread thread = new Thread(god);
// 默认是false表示是用户线程,正常的线程都是用户线程
thread.setDaemon(true);
// 上帝守护线程启动(上帝线程会一直运行,直到life线程挂了,虚拟机不用等待守护线程执行完毕)
thread.start();
// 生命线程启动
new Thread(life).start();
}
}
/**
* 上帝
*/
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守护线程...");
}
}
}
/**
* 生命
*/
class Life implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("用户线程正在活跃...!");
}
System.out.println("用户线程挂啦!");
}
}
结果:(虚拟机无需等待守护线程停止)
守护线程...
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程正在活跃...!
用户线程挂啦!
9. 线程同步
同一进程的多个线程共享同一块存储空间,在方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
。
当一个线程获得对象的排它锁,独占资源,其他线程必须等待它用完后释放锁,有可能存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁和释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 若一个优先级高的线程等待优先级低的线程释放锁,会导致优先级倒置没因其性能问题。
同步方法
同步方法:synchonized默认锁的对象是this,也就是类本身
案例:买票
class BuyTicket implements Runnable {
private int ticketNum = 5;
// 标志位
boolean flag = true;
@Override
public void run() {
// 买票
while (flag) {
buy();
}
}
/**
* 同步锁方法,锁的是this
*/
private synchronized void buy() {
// 判断是否有票
if (ticketNum <= 0) {
flag = false;
return;
}
// 模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName() + "拿到-- " + ticketNum-- + " --票");
}
}
测试类:
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小明").start();
new Thread(buyTicket,"小李").start();
new Thread(buyTicket,"黄牛").start();
}
}
结果:
小明拿到-- 5 --票
小明拿到-- 4 --票
黄牛拿到-- 3 --票
小李拿到-- 2 --票
黄牛拿到-- 1 --票
同步代码块
Obj:同步监视器,也就是代码块里要操作的对象,【可以锁任何对象,锁增删改的对象就好】
案例:银行取钱
账户类
class Account {
/**
* 余额
*/
int nowMoney;
/**
* 构造方法
*
* @param nowMoney
*/
public Account(int nowMoney) {
this.nowMoney = nowMoney;
}
}
银行:模拟取款
class Bank extends Thread {
/**
* 账户
*/
Account account;
/**
* 取了多少钱
*/
int quMoney;
public Bank(Account account, int quMoney, String name) {
super(name);
this.account = account;
this.quMoney = quMoney;
}
/**
* 同步方法:synchonized默认锁的对象是this,也就是类本身
* 同步块: Obj:同步监视器,也就是代码块里要操作的对象,【可以锁任何对象,锁增删改的对象就好】
*/
@Override
public void run() {
synchronized (account) {
// 判断有没有钱
if (account.nowMoney - quMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够!");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 手里的钱
//System.out.println(this.getName()+Thread.currentThread().getName());
System.out.println(this.getName() + "手里的钱:" + this.quMoney);
// 卡内余额 = 余额 - 取出的钱
account.nowMoney = account.nowMoney - quMoney;
System.out.println("卡内余额为:" + account.nowMoney);
}
}
}
测试类:
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100);
Bank you = new Bank(account, 70, "二萌");
Bank her = new Bank(account, 40, "阿雷");
you.start();
her.start();
}
}
结果:
二萌手里的钱:70
卡内余额为:30
阿雷钱不够
线程不安全的集合:ArrayList
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
// 模拟延时
Thread.sleep(100);
// 主线程
System.out.println(Thread.currentThread().getName()+"线程,集合中存储了多少个线程:"+list.size());
}
}
结果:
main线程,集合中存储了多少个线程:10
Lock锁【JDK1.5】
通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当
ReentrantLock类实现了Lock接口,可重入锁,与synchonized有相同的并发性和内存语义。
格式:
private final ReentrantLock lock = new ReentrantLock();
可以显式加锁、释放锁:【必须在try—catch–finally代码块中】
try {
// 加锁
lock.lock();
...
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
代码:
package cn.luis.lock;
import java.util.concurrent.locks.ReentrantLock;
class BuyTicket implements Runnable {
int ticketNum = 5;
/**
* 定义Lock锁
* ReentrantLock:可重入锁
*/
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (ticketNum > 0) {
// 模拟延时(睡眠)
Thread.sleep(200);
// 打印线程名称
System.out.println(Thread.currentThread().getName() + " -- 拿到了第-- " + ticketNum-- + " --张票");
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
}
测试类:
public class TestLock {
public static void main(String[] args) {
BuyTicket b = new BuyTicket();
new Thread(b).start();
new Thread(b).start();
new Thread(b).start();
}
}
结果:
Thread-0 -- 拿到了第-- 5 --张票
Thread-2 -- 拿到了第-- 4 --张票
Thread-1 -- 拿到了第-- 3 --张票
Thread-0 -- 拿到了第-- 2 --张票
Thread-0 -- 拿到了第-- 1 --张票
死锁
产生原因
- 多个线程各自占有一下共享资源,并且互相等待其他线程线程占有的资源才能运行,导致两个或多个线程都在等待对方释放资源,都停止执行的情况
- 某一个同步块同时拥有两个以上对象的锁,就可能发生此问题。
死锁避免方法
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程以获得资源,再未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要破坏其中的任意一个或多个即可!
synchnoized与Lock的对比
- Lock是显式锁(手动开启和关闭锁)
- Lock只有代码块锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好、扩展型好(提供更多的子类)
- synchonized是隐式锁,出了作用域自动释放
- synchonized有代码块锁和方法锁
优先使用顺序
- Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)
10. 线程通信 – 生产者和消费者问题
线程间通信方法
- 均是
Object
类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常:lllegalMonitorStateException
方法名 | 作用 |
---|---|
wait() | 一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一对象上所有调用wait方法的线程,优先级高的线程优点调度 |
并发协作模式”生产者消费者模式“
管程法 – 利用缓冲区解决
- 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
- 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
- 缓冲区:消费者不能直接使用生产者的数据
生产者将生产好的数据放入缓冲区,消费者冲缓冲区中拿出数据
代码:
信号灯法 – 标志位
生产者:演员
class Actor extends Thread {
Movie movie;
public Actor(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
this.movie.play("夏洛特烦恼");
} else {
this.movie.play("唐人街探案");
}
}
}
}
消费者:观众
class watcher extends Thread {
Movie movie;
public watcher(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
movie.watch();
}
}
}
产品:电影
class Movie {
/**
* 拍摄电影时,观众等待
*/
/**
* 放映电影时,演员等待
*/
/**
* 表演的节目
*/
String movie;
/**
* 标志位
*/
boolean flag = true;
/**
* 表演方法
*/
public synchronized void play(String movie) {
// 判断演员等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + movie);
System.out.println("==========");
// 通知观众观看
// 通知唤醒
this.notifyAll();
this.movie = movie;
this.flag = !this.flag;
}
/**
* 观看
*/
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("观众看了:" + movie);
System.out.println("~~~~~~~~~~");
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
测试类:
public class TestPC2 {
public static void main(String[] args) {
Movie movie= new Movie();
new Actor(movie).start();
new watcher(movie).start();
}
}
结果:
演员表演了:夏洛特烦恼
==========
观众看了:夏洛特烦恼
~~~~~~~~~~
演员表演了:唐人街探案
==========
观众看了:唐人街探案
~~~~~~~~~~
演员表演了:夏洛特烦恼
==========
观众看了:夏洛特烦恼
~~~~~~~~~~
演员表演了:唐人街探案
==========
观众看了:唐人街探案
~~~~~~~~~~
11. 线程池 【JDK5】
经常创建和销毁使用量特别大的资源,如并发情况下的线程,对性能影响很大。
解决
- 提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中。
JDK5.0起提供了线程池相关API:ExecutorService
和Excecutors
ExecutoeService
方法
ExecutoeService
:真正的线程池接口,常见子类ThreadExecutor
方法
- 执行Runnable,任务/命令,无返回值
void execute(Runnable command);
- 执行Callable接口,任务/命令,有返回值
<T> Future<T> sunbmit(Callable<T> task);
Excecutors
工具类
线程池的工厂类,用于创建并返回不同类型的线程池
优点
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程)
- 便于线程管理
- corePoolSize:核心池的大小
- maxmumPooSize:最大线程数
- keeoAliveTime:线程无任务是最多保持多长时间后会终止
使用步骤:
-
创建服务,创建线程池(参数为线程池大小)
ExecutorService service = Executors.newFixedThreadPool(10);
-
执行线程类,方法:
// runable接口是execute方法 service.execute(new MyThread()); // callable接口是submit方法 Future<Boolean> r1 = service.submit(c1);
-
关闭连接池
service.shutdownNow();
代码:
线程类
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
创建线程池
package cn.luis.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
// 1.创建服务,创建线程池[参数为线程池大小]
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.执行Runable接口实现类的线程【execute方法】,callable接口是submit方法
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3.释放连接
service.shutdownNow();
}
}
结果:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4