程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
进程:进程是执行程序的一个过程,它是一个动态的概念。是系统资源分配的单位。
线程:一个进程中包含多个线程,一个进程中至少有一个线程。线程是CPU调度和执行的单位。
调度器
安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的并发控制
例子:
/**
* @author [email protected]
* @description 继承Thread来创建线程
* @date 2022/1/5 11:56
*/
public class createThread01 {
public static void main(String[] args) {
// 实例化一个线程对象并调用start()方法执行
ThreadDemo01 threadDemo01 = new ThreadDemo01();
threadDemo01.start();
}
}
class ThreadDemo01 extends Thread {
// 线程创建方式一:继承Thread类
// 重写run()方法
@Override
public void run() {
System.out.println("线程run方法正在执行");
}
}
多个线程启动测试:
public class createThread01 {
public static void main(String[] args) {
// 实例化一个线程对象并调用start()方法执行
ThreadDemo01 threadDemo01 = new ThreadDemo01();
ThreadDemo02 threadDemo02 = new ThreadDemo02();
threadDemo01.start();
threadDemo02.start();
}
}
class ThreadDemo01 extends Thread {
// 线程创建方式一:继承Thread类
// 重写run()方法
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程001run方法正在执行");
}
}
}
class ThreadDemo02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程002run方法正在执行");
}
}
}
执行结果:
总结:我们发现了001和002线程在交叉执行,当两个线程都在执行的时候其实是两个线程在并行交替执行。
例子:
/**
* @author [email protected]
* @description 线程创建方式二:实现Runnable接口来创建线程
* @date 2022/1/5 15:36
*/
public class createThread02 {
public static void main(String[] args) {
// 实例化一个Runnable线程对象
TestRunnable01 testRunnable01 = new TestRunnable01();
// 实例化一个线程Thread,将Runnable对象放进线程Thread去执行
Thread thread = new Thread(testRunnable01);
// 调用方法执行线程
thread.start();
}
}
class TestRunnable01 implements Runnable {
// 线程创建方式二:实现Runnable接口
// 重写run()方法
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程001run方法正在执行");
}
}
}
以上两种方式的比较:
继承 Thread 类
实现接口 Runnable
避免单继承局限性,方便同一个对象被多个线程
同一个对象被多个线程使用测试例子:
/**
* @author [email protected]
* @description 多个线程同时操作同一个对象,买火车票例子
* @date 2022/1/5 15:51
*/
public class createThread03 {
public static void main(String[] args) {
TestThread01 threadThree = new TestThread01();
new Thread(threadThree,"小明").start();
new Thread(threadThree,"老师").start();
new Thread(threadThree,"老黄牛").start();
}
}
class TestThread01 implements Runnable {
private Integer ticketNums = 10;
@Override
public void run() {
while(true){
if (ticketNums<=0){
System.out.println(Thread.currentThread().getName()+", 没有买到票");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"——>拿到了第"+ticketNums--+"张票");
}
}
}
输出结果:
小明——>拿到了第10张票
老黄牛——>拿到了第10张票
老师——>拿到了第10张票
小明——>拿到了第9张票
老师——>拿到了第8张票
老黄牛——>拿到了第7张票
小明——>拿到了第5张票
老师——>拿到了第6张票
老黄牛——>拿到了第6张票
老师——>拿到了第2张票
老黄牛——>拿到了第4张票
小明——>拿到了第3张票
老师——>拿到了第1张票
小明——>拿到了第1张票
老黄牛——>拿到了第0张票
老黄牛, 没有买到票
小明, 没有买到票
老师, 没有买到票
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
实现Callable接口,重写call方法
图片下载测试例子:
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
// 图片下载
downloader(url, name);
return true;
}
// 下载图片的方法
public void downloader(String url, String name) {
System.out.println("正在下载图片url="+url+", name="+name);
System.out.println("下载图片"+name+"完成");
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testThread1 = new TestCallable("http://url1", "1.png");
TestCallable testThread2 = new TestCallable("http://url2", "2.png");
TestCallable testThread3 = new TestCallable("http://url3", "3.png");
TestCallable testThread4 = new TestCallable("http://url4", "4.png");
//创建执行服务(线程池):
ExecutorService service = Executors.newFixedThreadPool(4);
//提交执行:
Future<Boolean> r1 = service.submit(testThread1);
Future<Boolean> r2 = service.submit(testThread2);
Future<Boolean> r3 = service.submit(testThread3);
Future<Boolean> r4 = service.submit(testThread4);
// 获取结果:
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
boolean rs4 = r4.get();
// 返回结果打印
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
System.out.println(rs4);
//关闭服务:
service.shutdownNow();
}
}
输出结果:
正在下载图片url=http://url2, name=2.png
下载图片2.png完成
正在下载图片url=http://url3, name=3.png
下载图片3.png完成
正在下载图片url=http://url1, name=1.png
下载图片1.png完成
正在下载图片url=http://url4, name=4.png
下载图片4.png完成
true
true
true
true
一个结婚代理场景:
根据上面说到的3个角色创建分别创建以下代码
1、定义一个结婚接口
public interface MarryService {
// 定义一个结婚接口
void doMarry();
}
2、定义一个真实的结婚对象(人),实现结婚接口
public class Preson implements MarryService{
private String name;
public Preson(String name) {
this.name = name;
}
@Override
public void doMarry() {
System.out.println(this.name + "-->正在开心的结婚");
}
}
3、定义一个婚庆公司,实现一个结婚接口,传入一个具体的结婚对象(人),做一些结婚对象无法做的事情。
public class MarryCompany implements MarryService{
private Preson preson;
public MarryCompany(Preson preson) {
this.preson = preson;
}
@Override
public void doMarry() {
before();
this.preson.doMarry();
after();
}
private void before () {
System.out.println("准备结婚了,开始布置婚礼现场");
}
private void after () {
System.out.println("准备完毕,找新郎结尾款");
}
}
4、测试代理结婚场景
public class TestStaticProxy {
public static void main(String[] args) {
// 定义一个结婚的人叫张三
Preson preson = new Preson("张三");
// 将张三交给婚庆公司
MarryCompany marryCompany = new MarryCompany(preson);
// 婚庆公司安排结婚
marryCompany.doMarry();
}
}
// 输出
准备结婚了,开始布置婚礼现场
张三-->正在开心的结婚
准备完毕,找新郎结尾款
5、李四看到张三结婚了,觉得他家婚礼办得不错,觉得他结婚也去找那一家婚庆公司
public class TestStaticProxy {
public static void main(String[] args) {
Preson preson = new Preson("张三");
MarryCompany marryCompany = new MarryCompany(preson);
marryCompany.doMarry();
Preson preson2 = new Preson("李四");
MarryCompany marryCompany2 = new MarryCompany(preson2);
marryCompany2.doMarry();
}
}
// 输出
准备结婚了,开始布置婚礼现场
张三-->正在开心的结婚
准备完毕,找新郎结尾款
准备结婚了,开始布置婚礼现场
李四-->正在开心的结婚
准备完毕,找新郎结尾款
静态代理模式总结:
对比Thread:
public class TestStaticProxy {
public static void main(String[] args) {
// 定义一个结婚的人叫张三
Preson preson = new Preson("张三");
// 将张三交给婚庆公司
MarryCompany marryCompany = new MarryCompany(preson);
// 婚庆公司安排结婚
marryCompany.doMarry();
// ****对比Thread****//
// 实例化一个Runnable线程对象<==>定义一个结婚的人叫张三
TestRunnable01 testRunnable01 = new TestRunnable01();
// 实例化一个线程Thread,将Runnable对象放进线程Thread去执行<==>将张三交给婚庆公司
Thread thread = new Thread(testRunnable01);
// 调用方法执行线程<==>婚庆公司安排结婚
thread.start();
}
}
总结:
Lambda 表达式属于函数式编程的概念
避免匿名内部类定义过多
去掉了一堆没有意义的代码,只留下核心逻辑
理解 Functional Interface(函数式接口)是学习 Java 8 Lambda 表达式的关键所在
函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和 java.util.Comparator都是典型的函数式接口
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现
函数式接口的定义:
任何接口,如果只包含唯一的一个方法,那么它就是函数式接口
public interface Runnable{
public abstract void run();
}
对于函数式接口,可以通过 Lambda 表达式来创建该接口的对象
//1.定义一个函数式接口
interface ILike{
void like();
}
//6.用lambda简化,-->函数式接口
like = ()->{
System.out.println("i like lambda5");
};
lamdba表达式语法,包含三部分:
(parameters) -> expression 或者 (parameters) -> { statements; }
lamdba表达式的简化过程:
/**
* @description Lamdba表达式的简化演变过程
* @date 2022/1/6 9:57
*/
public class TestLamdba {
// 3、定义静态内部类
static class ILikeImpl2 implements ILike {
@Override
public void lambda() {
System.out.println("我喜欢lamdba02");
}
}
public static void main(String[] args) {
// 实现类输出
ILike iLike = new ILikeImpl();
iLike.lambda();
// 静态内部类输出
iLike = new ILikeImpl2();
iLike.lambda();
// 4、定义一个局部内部类
class ILikeImpl3 implements ILike {
@Override
public void lambda() {
System.out.println("我喜欢lamdba03");
}
}
// 局部内部类输出
iLike = new ILikeImpl3();
iLike.lambda();
// 5、定义一个匿名内部类
iLike = new ILike() {
@Override
public void lambda() {
System.out.println("我喜欢lamdba04");
}
};
// 匿名内部类输出
iLike.lambda();
// 6、lamdba简化匿名内部类
iLike = () -> {
System.out.println("我喜欢lamdba05");
};
iLike.lambda();
// 7、lamdba表达式只有一行代码的最终简化
iLike = () -> System.out.println("我喜欢lamdba06");
iLike.lambda();
}
}
// 1、定义一个函数式接口
@FunctionalInterface
interface ILike {
void lambda ();
}
// 2、实现类
class ILikeImpl implements ILike {
@Override
public void lambda() {
System.out.println("我喜欢lamdba01");
}
}
// 输出
我喜欢lamdba01
我喜欢lamdba02
我喜欢lamdba03
我喜欢lamdba04
我喜欢lamdba05
我喜欢lamdba06
带参数的lamdba表达式简化:
public class TestLambda {
public static void main(String[] args) {
ILove love = null;
/*
ILove love = (String name) -> {
System.out.println("I love you, " + name + "!");
};
love.love("王麻子");
//简化Lambda表达式,1.简化类型
love = (name) -> {
System.out.println("I love you, " + name + "!");
};
//简化Lambda表达式,2.去掉括号
love = name -> {
System.out.println("I love you, " + name + "!");
};
*/
//3. 简化花括号
love = name -> System.out.println("I love you, " + name + "!");
love.love("521");
//总结:
//lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
//前提是接口为函数式接口
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号,
}
}
interface ILove {
void love(String name);
}
测试代码
// 测试线程停止
public class ThreadStop implements Runnable{
// 标志位
private Boolean flag = true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("run.....Thread"+(i++));
}
}
public void stop () {
this.flag = false;
}
public static void main(String[] args) {
// 启动线程
ThreadStop testStop = new ThreadStop();
new Thread(testStop).start();
// 循环100次在900的时候停止线程
for (int i = 0; i <1000 ; i++) {
System.out.println("main"+i);
if (i==900){
testStop.stop();
System.out.println("线程停止了!");
}
}
}
}
public class TestSleep1 {
public static void main(String[] args) {
tenDown();
//打印当前系统时间
Date startTime=new Date(System.currentTimeMillis());//获取当前系统时间
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime=new Date(System.currentTimeMillis());//更新时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//模拟倒计时
public static void tenDown(){
int num=10;
while (true){
try {
Thread.sleep(1000);
if (num<=0){
break;
}else{
System.out.println("倒计时!!!"+num--+"秒");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试线程礼让
public class TestYield implements Runnable {
public static void main(String[] args) {
TestYield yield=new TestYield();
new Thread(yield,"a").start();
new Thread(yield,"b").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
//测试join方法,想象为插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip线程来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//线程启动
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join();//插队
}
System.out.println("main线程" + i);
}
}
}
Thread.State,线程状态有哪些?
一个线程可以给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态
示例代码
public class TestThreadState {
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("//");
});
// 线程新建,状态 NEW
System.out.println(thread.getState());
// 启动线程,状态 RUNNABLE
thread.start();
System.out.println(thread.getState());
// 运行状态,只要线程不终止就输入线程状态 TIMED_WAITING
while (thread.getState()!= Thread.State.TERMINATED){
Thread.sleep(100);
System.out.println(thread.getState());
}
}
}
// 输出
NEW
RUNNABLE
TIMED_WAITING
......
TIMED_WAITING
//
TERMINATED
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10。先设置优先级,再start线程!!!
使用getPriority().setPriority(int xxx) 改变或获取优先级,设置优先级后并不是一定会按照优先级执行,而是大概率会按照优先级执行
代码:
public class TestPriority {
public static void main(String[] args) {
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
//先设置线程优先级
t1.setPriority(1);
t1.start();
t2.setPriority(3);
t2.start();
t3.setPriority(6);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);// 优先级=10
t4.start();
t5.setPriority(Thread.MIN_PRIORITY);// 优先级=1
t6.setPriority(9);
t6.start();
System.out.println("main");
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---线程被执行了!---"+Thread.currentThread().getPriority());
}
}
// 输出
main
Thread-3---线程被执行了!---10
Thread-5---线程被执行了!---9
Thread-2---线程被执行了!---6
Thread-1---线程被执行了!---3
Thread-0---线程被执行了!---1
测试守护线程代码
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you=new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认为flase 为用户线程, true为守护线程
thread.start();
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你-------");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i <36500 ; i++) {
System.out.println("开心着活着每一天------");
}
System.out.println("----goodbye!Beautiful World!!!------");
}
}
线程同步:
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可
使用锁会出现的问题:
不安全的买票
//线程不安全,买票重复
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();
}
}
class BuyTicket implements Runnable {
//票
private int ticket = 10;
//外部停止标志
boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if (ticket <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "买到" + ticket--);
}
}
// 输出
他买到10
你买到8
我买到9
你买到7
他买到7
我买到7
我买到6
他买到6
你买到5
他买到4
你买到3
我买到4
他买到2
你买到0
我买到1
不安全的取钱
//取钱线程不安全
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
class Account {
//账户名称
String name;
//账户余额
int money;
public Account(int money, String name) {
this.name = name;
this.money = money;
}
}
class Drawing extends Thread {
Account account;//银行账户
int drawingMoney;//取了多少钱
int nowMoney;//剩余多少钱
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
super.setName(name);
}
//取钱
@Override
public void run() {
if (account.money - drawingMoney < 0) {
System.out.println("卡内余额不足,取不了!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额–你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
//Thread.currentThread().getName() == this.getName()
System.out.println(this.getName() + "手里的钱" + nowMoney);
}
}
// 输出
结婚基金余额为:-50
你手里的钱50
结婚基金余额为:-50
girlFriend手里的钱100
ArrayList线程不安全的,我没连续添加1000个数据发现最后只存在996个,有的添加在同一个位置被覆盖了
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<String>();
for (int i = 0; i <1000 ; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(list.size());
}
}
// 输出
996
以上三个例子都是线程不安全的场景,那么我可以使用线程的同步机制synchronized
来修改上面的测试代码
修改买票代码:在29行加上synchronized
关键子修饰方法,将方法锁住,每次只有一个线程执行该方法
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();
}
}
class BuyTicket implements Runnable {
//票
private int ticket = 10;
//外部停止标志
boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 这里的锁住的是方法,其实是把方法所在的类给锁住了BuyTicket给锁住了和传this参数效果一样
public synchronized void buy() throws InterruptedException {
if (ticket <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + "买到" + ticket--);
}
}
// 输出
他买到10
他买到9
他买到8
你买到7
你买到6
我买到5
我买到4
我买到3
我买到2
我买到1
修改取钱代码:锁定代码37行添加synchronized代码块
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
class Account {
//账户名称
String name;
//账户余额
int money;
public Account(int money, String name) {
this.name = name;
this.money = money;
}
}
class Drawing extends Thread {
Account account;//银行账户
int drawingMoney;//取了多少钱
int nowMoney;//剩余多少钱
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
super.setName(name);
}
//取钱
@Override
public void run() {
synchronized (account) {
if (account.money - drawingMoney < 0) {
System.out.println("卡内余额不足,取不了!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额–你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
//Thread.currentThread().getName() == this.getName()
System.out.println(this.getName() + "手里的钱" + nowMoney);
}
}
}
// 输出
结婚基金余额为:50
你手里的钱50
卡内余额不足,取不了!
在锁定取钱代码的时候需要注意,不能再public void run()方法这里进锁定
,比如:public synchronized void run()。这里默认锁定的是Drawing类(银行),我们应该对银行里面的账户Account进行锁定。
锁定集合添加元素代码
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<String>();
for (int i = 0; i <1000 ; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(list.size());
}
}
// 输出
1000
总结:在使用synchronized修饰代码或方法的时候,默认和synchronized (this) 是一样的,都是默认对当前代码和方法所在的类进行锁定。如果需要对具体的某一个类对象进行锁定,需要使用synchronized代码块(同步代码块)对需要加锁的代码加锁并传入锁定对象。
同步代码块:
Synchronized(Obj){}
同步监视器的执行过程:
copyOnWriteArrayList是一个线程安全的集合,包路径在java.util.concurrent.CopyOnWriteArrayList
public class safeList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
copyOnWriteArrayList.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(copyOnWriteArrayList.size());
}
}
// 输出
1000
在线程中多个线程同时操作多个资源会出现死锁的情况
测试死锁代码:
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(0, "小红");
Makeup makeup2 = new Makeup(1, "小白");
makeup1.start();
makeup2.start();
}
}
/**
* 口红
*/
class Lipstick {
}
/**
* 镜子
*/
class Mirror {
}
class Makeup extends Thread {
/**
* 需要的资源只有一份,用static来保证
* 因为有static 所以两个线程拿到的才是同一个口红和镜子
*/
static Lipstick lipstick = new Lipstick();
static Mirror Mirror = new Mirror();
/**
* 选择
*/
int choice;
/**
* 使用
*/
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 化妆,互相持有对方的锁,就是需要拿到对方的资源.
*/
private void makeup() throws InterruptedException {
if (choice == 0) {
//获得口红的锁
synchronized (lipstick) {
System.out.println(this.girlName + "获得了口红的锁");
Thread.sleep(1000);
synchronized (Mirror) {
System.out.println(this.girlName + "获得了镜子的锁");
}
}
} else {
synchronized (Mirror) {
System.out.println(this.girlName + "获得了镜子的锁");
Thread.sleep(2000);
synchronized (lipstick) {
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
}
}
// 输出
小红获得了口红的锁
小白获得了镜子的锁
以上例子化妆操纵都在等待对方释放锁,而导致了死锁。要解决这个问题我们只需要将 private void makeup()做一下改动
改动代码:
private void makeup() throws InterruptedException {
if (choice == 0) {
//获得口红的锁
synchronized (lipstick) {
System.out.println(this.girlName + "获得了口红的锁");
Thread.sleep(1000);
}
synchronized (Mirror) {
System.out.println(this.girlName + "获得了镜子的锁");
}
} else {
synchronized (Mirror) {
System.out.println(this.girlName + "获得了镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick) {
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
// 输出
小红获得了口红的锁
小白获得了镜子的锁
小白获得了口红的锁
小红获得了镜子的锁
产生死锁的四个必要条件:
注意: 这四个必要条件只要破解其中一个或者多个条件就可以破解死锁
示例:
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket extends Thread{
private int ticketNums=10;
//定义lock锁
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) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
}finally {
lock.unlock();//解锁
}
}
}
}
应用场景:生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
如果仓库中没有产品,则将生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费,直到仓库中再次放入产品为止。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.
在生产者消费者问题中,仅有synchronized是不够的
**synchronized :**可阻止并发更新同一个共享资源,实现了同步,不能用来实现不同线程之间的消息传递(通信)
并发写作模型"“生产者/消费者模式”"–>管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
案例:
//测试:生产者消费者模型---》管程法
//消费者,生产者,商品,缓冲区
public class TestPC {
public static void main(String[] args) {
SyncContainer syncContainer = new SyncContainer();
new Customer(syncContainer).start();
new Productor(syncContainer).start();
}
}
//消费者
class Customer extends Thread {
//创建缓冲区
private SyncContainer syncContainer;
public Customer(SyncContainer syncContainer) {
this.syncContainer = syncContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Product pop = syncContainer.pop();
System.out.println("消费了第" + pop.getId() + "号产品");
}
}
}
//生产者
class Productor extends Thread {
//创建新的缓冲区
private SyncContainer syncContainer;
public Productor(SyncContainer syncContainer) {
this.syncContainer = syncContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡!");
syncContainer.push(new Product[i]);
}
}
}
//商品
class Product {
int id;//产品编号
public Product(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//缓冲区
class SyncContainer {
//定义容器大小
Product[] products = new Product[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Product[] product) {
//如果容器满了,就等待消费者消费
if (count == product.length) {
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,我们就需要丢入产品
products = product;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Product pop() {
if (count == 0) {
//通知生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Product product = products[count];
//通知生产者消费
this.notifyAll();
return product;
}
}
注意:上面的管程法案例只适用于只有一个生产者和消费者的情况,如果出现多个生成者或者多个消费者会出现下标越界的问题。
解释:
// 解释1
//对于数组下标越界问题的解释:
//1、狂神的这个例子只适用于一个生产者一个消费者的情况,因为消费者wait()释放对象锁权限让出CPU只会是那唯一的生产者重新拿到锁权限进行生产,这样的话不消费就生产,不生产就消费,是不会出现下标越界的,而如果不止一个消费者就会有下标越界的问题,原因如2;
//2、首先要说明,wait()表示持有对象锁的线程准备释放对象锁权限和让出 cpu 资源并进入等待状态。如果有多个消费者,比如X1,X2,X3,假如此时X1,X2,X3都处于wait状态,这时容量为0了生产者拿到锁,生产者生产了1个资源让出锁,X1拿到锁消费完之后容量又刚好为0,然后X1释放锁notifyAll通知JVM去唤醒所有竞争Container对象锁的线程,如果这个锁被X2拿到,那么就会导致0--出现数组下标越界的问题,解决方案暂时只想到把消费的if(index <=0)换成while就是让消费者线程被唤醒的时候不要立刻执行下面的代码,而是再去判断当前容量。
while (index <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 解释2
// 在多线程中要测试某个条件的变化时(尤其是用于线程通信的条件判断)不要选择if,而是选择while去判断。因为notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行(需要注意的是,执行wait()的线程被notify唤醒的时候,只是让while循环继续往下走,如果用if的话,继续往下走意味着跳出if语句块),所以必须使用while循环阻塞。在本例中,如果管道中有一只鸡,那么第一个消费者被唤醒去消费,消费了一只鸡之后,释放消费的锁,因为唤醒使用的是notifyAll,所以是唤醒所有等待的线程再去由CPU决定把锁给谁,如果此时不是生产者拿到了锁而是第二个消费者拿到锁,那么使用if的话则不会再做循环判断,会继续消费,从而造成数组下标越界。
案例:
public class TestPC2 {
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) {
this.tv.play("快乐大本营播放中");
} else {
this.tv.play("抖音播放中");
}
}
}
}
//消费者-->观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//节目
class TV {
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice; //表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + voice);
//通知观众观看
this.notify();//通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + voice);
//通知演员表演
this.notify();
this.flag = !this.flag;
}
优点:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理,通过参数配置可管理…
线程池的案例:
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
ExecutorService service= Executors.newFixedThreadPool(10);
//newFixedThreadPool 参数为:线程池大小
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
本博客为狂神说java多线程学习笔记
学习地址:https://www.bilibili.com/video/BV1V4411p7EF