Java基础——多线程
一、线程简介
1、程序
为了完成特定任务、用某种语言编写的一组指定的集合、即指一段静态的代码。
2、进程
程序的一次执行过程,或是正在运行的一个程序。
3、线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
二、线程实现
1、继承Thread类
1)、如何实现
1.自定义线程类继承Thread类
2.重写run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程
//1、自定义线程类继承Thread类
public class ExtendsThread extends Thread{
//2、重写run()方法,编写线程执行体
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是重写Thread类的run()方法" + i);
}
}
public static void main(String[] args) {
//3、创建线程对象,调用start()方法启动线程
ExtendsThread extendsThread = new ExtendsThread();
extendsThread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是主函数的for循环"+i);
}
}
}
/*
运行结果(没有复制运行结果,口头描述):两个for循环互相穿插内容
*/
2)、案例
利用多线程完成网络图片的下载
public class ExtendsThreadExer01 extends Thread{
private String url;//下载地址
private String name;//文件名称
public ExtendsThreadExer01() {
}
public ExtendsThreadExer01(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载的文件名为:"+name);
}
public static void main(String[] args) {
String url1 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg9.51tietu.net%2Fpic%2F2019-091311%2Frrwzhatwryfrrwzhatwryf.jpg&refer=http%3A%2F%2Fimg9.51tietu.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668228&t=d433c5e8c068d9469833cbf5d2f4e468";
String url2 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp02%2F1Z91921132U2R-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668315&t=2d695729676eea08db30f150fd302874";
String url3 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.goupu.com.cn%2Fupload%2F201706%2F04%2F115016541.png&refer=http%3A%2F%2Fimg.goupu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668393&t=d35df7425c058bd7038df5e836ac1ef9";
ExtendsThreadExer01 test1 = new ExtendsThreadExer01(url1,"01.jpg");
ExtendsThreadExer01 test2 = new ExtendsThreadExer01(url2,"02.jpg");
ExtendsThreadExer01 test3 = new ExtendsThreadExer01(url3,"03.jpg");
//期望:01——02——03
test1.start();
test2.start();
test3.start();
}
}
//下载器
class WebDownLoader{
//下载方法
public void downLoader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
运行结果:下载的文件名为:03.jpg
下载的文件名为:02.jpg
下载的文件名为:01.jpg
*/
2、实现Runnable接口
1)、如何实现
1.自定义线程类,实现Runnable接口
2.实现run()方法,编写线程执行体
3.创建线程对象,通过Thread类对象代理,调用start()方法启动线程
//1、自定义线程类,实现Runnable接口
public class ImplementsRunnable implements Runnable{
//2、实现run()方法,编写线程执行体
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("重写Runnable接口的for循环"+i);
}
}
public static void main(String[] args) {
//3、创建线程对象,通过Thread类对象代理,调用start()方法启动线程
ImplementsRunnable implementsRunnable = new ImplementsRunnable();
new Thread(implementsRunnable).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主函数的for循环"+i);
}
}
}
/*
运行结果(没有复制运行结果,口头描述):两个for循环互相穿插内容
*/
2)、小结
继承Thread类 | 实现Runnable接口 |
---|---|
子类继承Thread类具备多线程能力 | 实现接口Runnable具有多线程能力 |
启动线程:子类对象.start() | 启动线程:传入目标对象+Thread对象.start() |
不建议使用:避免OOP单继承局限性 | 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用 |
3)、案例——龟兔赛跑
public class RaceRunnable implements Runnable{
private static String winner;
@Override
public void run() {
for(int i = 0; i <= 15; i++) {
//如果线程是兔子,让此线程sleep
if(Thread.currentThread().getName().equals("兔子") && i%15==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean isOver = isGameOver(i);
if(isOver){
break;
}
System.out.println(Thread.currentThread().getName() + "跑到了第"+ i + "公里");
}
}
//判断比赛是否结束,谁是胜利者
public boolean isGameOver(int kiloMetres){
//判断是否有胜利者
if(winner != null){
return true;
}else{
if (kiloMetres >= 150) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
RaceRunnable race = new RaceRunnable();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
3、实现Callable接口
1)、如何实现
1、实现Callable接口,需要返回值类型
2、重写call方法,需要抛出异常
3、创建目标对象
4、创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
5、提交执行结果:Future result1= ser.submit(t1);
6、获取结果:boolean r1 = result1.get();
7、关闭服务:ser.shutdownNow();
//1、实现Callable接口,需要返回值类型
public class ImplementsCallable implements Callable<Boolean> {
private String url;//下载地址
private String name;//文件名称
public ImplementsCallable() {
}
public ImplementsCallable(String url, String name) {
this.url = url;
this.name = name;
}
//2、重写call方法,需要抛出异常
@Override
public Boolean call() throws Exception {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载的文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
String url1 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg9.51tietu.net%2Fpic%2F2019-091311%2Frrwzhatwryfrrwzhatwryf.jpg&refer=http%3A%2F%2Fimg9.51tietu.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668228&t=d433c5e8c068d9469833cbf5d2f4e468";
String url2 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp02%2F1Z91921132U2R-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668315&t=2d695729676eea08db30f150fd302874";
String url3 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.goupu.com.cn%2Fupload%2F201706%2F04%2F115016541.png&refer=http%3A%2F%2Fimg.goupu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668393&t=d35df7425c058bd7038df5e836ac1ef9";
//3、创建目标对象
ImplementsCallable test1 = new ImplementsCallable(url1,"01.jpg");
ImplementsCallable test2 = new ImplementsCallable(url2,"02.jpg");
ImplementsCallable test3 = new ImplementsCallable(url3,"03.jpg");
//4、创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
ExecutorService ser = Executors.newFixedThreadPool(3);
//5、提交执行结果:Future<Boolean> result1= ser.submit(t1);
Future<Boolean> result1 = ser.submit(test1);
Future<Boolean> result2 = ser.submit(test2);
Future<Boolean> result3 = ser.submit(test3);
//6、获取结果:boolean r1 = result1.get()
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
//7、关闭服务:ser.shutdownNow();
ser.shutdownNow();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
}
}
4、Lambda表达式
1)、函数式接口
函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但仅能声明一个抽象方法。
2)、Lambda表达式
①、接口中的抽象方法不含参数
public class LambdaTest01 {
//3、静态内部类
static class AntiFans2 implements Ikun{
@Override
public void isIkun() {
System.out.println("我是二号小黑子");
}
}
public static void main(String[] args) {
Ikun k = new AntiFans1();
k.isIkun();
k = new AntiFans2();
k.isIkun();
//4、局部内部类
class AntiFans3 implements Ikun{
@Override
public void isIkun() {
System.out.println("我是三号小黑子");
}
}
k = new AntiFans3();
k.isIkun();
//5、匿名内部类
k = new Ikun() {
@Override
public void isIkun() {
System.out.println("我是四号小黑子");
}
};
k.isIkun();
//6、Lambda表达式
k = ()->{
System.out.println("我是五号小黑子");
};
k.isIkun();
}
}
//2、实现类
class AntiFans1 implements Ikun{
@Override
public void isIkun() {
System.out.println("我是一号小黑子");
}
}
//1、定义一个函数式接口
interface Ikun{
void isIkun();
}
/*
运行结果:我是一号小黑子
我是二号小黑子
我是三号小黑子
我是四号小黑子
我是五号小黑子
*/
②、接口中的抽象方法含有参数
public class LambdaTest02 {
//3、静态内部类
static class Fruit2 implements BuyFruit{
@Override
public void buyFruit(String name, double price) {
int weight = 2;
System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
System.out.println("一共花了"+(price*weight)+"元");
System.out.println("***************************************");
}
}
public static void main(String[] args) {
BuyFruit buy = new Fruit1();
buy.buyFruit("西瓜",1.5);
buy = new Fruit2();
buy.buyFruit("苹果",2.68);
//4、局部内部类
class Fruit3 implements BuyFruit{
@Override
public void buyFruit(String name, double price) {
int weight = 3;
System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
System.out.println("一共花了"+(price*weight)+"元");
System.out.println("***************************************");
}
}
buy = new Fruit3();
buy.buyFruit("香蕉",1.98);
//5、匿名内部类
buy = new BuyFruit() {
@Override
public void buyFruit(String name, double price) {
int weight = 4;
System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
System.out.println("一共花了"+(price*weight)+"元");
System.out.println("***************************************");
}
};
buy.buyFruit("榴莲",19.98);
//6、Lambda表达式
buy = (name,price)->{
int weight = 5;
System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
System.out.println("一共花了"+(price*weight)+"元");
System.out.println("***************************************");
};
buy.buyFruit("樱桃",25.68);
}
}
//2、实现类
class Fruit1 implements BuyFruit{
@Override
public void buyFruit(String name, double price) {
int weight = 1;
System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
System.out.println("一共花了"+(price*weight)+"元");
System.out.println("***************************************");
}
}
//1、实现一个函数式接口
interface BuyFruit{
void buyFruit(String name,double price);
}
/*
运行结果:买了1斤西瓜,每一斤1.5元
一共花了1.5元
***************************************
买了2斤苹果,每一斤2.68元
一共花了5.36元
***************************************
买了3斤香蕉,每一斤1.98元
一共花了5.9399999999999995元
***************************************
买了4斤榴莲,每一斤19.98元
一共花了79.92元
***************************************
买了5斤樱桃,每一斤25.68元
一共花了128.4元
***************************************
*/
③、小结
1.前提是接口为函数式接口
2.Lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
3.多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。
三、线程状态
线程共五大状态,即创建状态、就绪状态、运行状态、阻塞状态、死亡状态。
1、停止线程
1.建议线程正常停止——>利用次数,不建议死循环
2.建议使用标志位——>设置一个标志位
3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class StopTest implements Runnable{
//设置一个标志位
private boolean isFlag = true;
@Override
public void run() {
int i = 0;
while (isFlag){
System.out.println(Thread.currentThread().getName()+"正在运行!"+i++);
}
}
//设置一个停止函数
public void stop(){
this.isFlag = false;
}
public static void main(String[] args) {
StopTest test = new StopTest();
new Thread(test).start();
for (int i = 0; i < 500; i++) {
System.out.println("线程正在运行"+i);
if(i==300){
//调用stop方法切换标志位,让线程停止
test.stop();
System.out.println("线程停止了");
}
}
}
}
2、线程休眠
1.sleep(时间)指定当前线程阻塞的毫秒数
2.sleep存在异常InterruptedException
3.sleep时间达到后线程进入就绪状态
4.sleep可以模拟网络延时,倒计时等
5.每个对象都有一个锁,sleep不会释放锁
1)、模拟倒计时
public class SleepTest {
public static void main(String[] args) {
tenDown();
}
//模拟倒计时
public static void tenDown(){
int down = 10;
while(true){
try {
Thread.sleep(1000);
System.out.println(down--);
if(down<=0){
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
运行结果:10
9
8
7
6
5
4
3
2
1
*/
2)、模拟网络延时
public class SleepTest02 {
public static void main(String[] args) {
//获取当前系统时间
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();
}
}
}
}
/*
运行结果:09:30:27
09:30:28
09:30:29
09:30:30
09:30:31
09:30:32
*/
3、线程礼让
1.方法:线程礼让
void yield()
2.礼让不一定成功!看cpu心情
public class YieldTest {
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开始线程
B结束线程
A结束线程
运行结果二: A开始线程
A结束线程
B开始线程
B结束线程:
*/
4、线程插入
线程插入
void join()
public class JoinTest implements Runnable{
public static void main(String[] args) {
JoinTest joinTest = new JoinTest();
Thread thread = new Thread(joinTest);
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 300){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main线程"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 700; i++) {
System.out.println("我是尊贵的Vip用户,请让我插个队"+i);
}
}
}
5、线程状态观测
1.NEW:尚未启动的线程
2.RUNNABLE:在JAVA虚拟机中执行的线程
3.BLOCKED:被阻塞等待监视器锁定的线程
4.WAITING:正在等待另一个线程执行特定动作的线程
5.TIMED_ WAITING:正在等在另一个线程
6.TERNINATED:已退出的线程
public class StateTest {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("****************************");
});
//观察NEW状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动RUNNABLE状态
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(500);
//更新线程状态
System.out.println(state);
//输出线程状态
state = thread.getState();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观察已退出TERNINATED的线程
state = thread.getState();
System.out.println(state);
}
}
/*
运行结果:NEW
RUNNABLE
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
****************************
TIMED_WAITING
TERMINATED
*/
6、线程优先级
1.线程的优先级范围从1-10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRORRITY = 5;
2.优先级的设定建议在start()调用前
3.方法
获取优先级 :gePriority()
设置优先级 :setPriority()
4.总结:优先级低只是意味着获得调度的概率低,并不是不会被调用。
public class PriorityTest {
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);
System.out.println(Thread.currentThread().getName() + "的优先级——>" + Thread.currentThread().getPriority());
t1.setPriority(1);
t1.start();
t2.setPriority(10);
t2.start();
t3.setPriority(5);
t3.start();
t4.setPriority(8);
t4.start();
t5.setPriority(3);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "的优先级——>" + Thread.currentThread().getPriority());
}
}
/*
运行结果一: main的优先级——>5
Thread-0的优先级——>1
Thread-1的优先级——>10
Thread-4的优先级——>3
Thread-3的优先级——>8
Thread-2的优先级——>5
运行结果二: main的优先级——>5
Thread-1的优先级——>10
Thread-3的优先级——>8
Thread-0的优先级——>1
Thread-2的优先级——>5
Thread-4的优先级——>3
*/
7、守护(daemon)线程
1.线程分为用户线程和守护线程
2.虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
3.方法
设置是否为守护线程,默认是false
void setDaemon(boolean on)
public class DaemonTest {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread t1 = new Thread(god);
t1.setDaemon(true);
t1.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 < 100; i++) {
System.out.println("这是我活着的第"+i+"年");
}
System.out.println("******************************************");
System.out.println("生老病死,与世长辞!");
}
}
四、线程同步
并发:同一个对象被多个线程同时操作
1、同步代码块
1.如何定义
synchronized(同步监视器){
//需要被同步的代码
}
2.操作共享数据的代码,即为需要被同步的代码。
3.共享数据:多个线程共同操作的变量
4.同步监视器,俗称锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
5.实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
6.在继承Thread类创建多线程的方式中,考虑使用当前类充当同步监视器。慎用this充当同步监视器。
public class BankSyn {
public static void main(String[] args) {
//账户
Account account = new Account("结婚基金",500);
Drawing tony = new Drawing(account, 100, "托尼");
Drawing tonyGirlFriend = new Drawing(account, 500, "托尼的女朋友");
tony.start();
tonyGirlFriend.start();
}
}
class Account{
//卡主姓名
private String name;
//余额
private int monny;
public Account(String name, int monny) {
this.name = name;
this.monny = monny;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonny() {
return monny;
}
public void setMonny(int monny) {
this.monny = monny;
}
}
//银行:模拟取款
class Drawing extends Thread{
//账户
private Account account = null;
//取了多少钱
private int drawingMoney = 0;
//现在手里多少钱
private int nowMoney = 0;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
synchronized (account){
if(account.getMonny() - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"账户余额不足,请确认账户余额!");
return;
}
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.setMonny(account.getMonny() - drawingMoney);
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.getName() + "账户余额为:" + account.getMonny());
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱为:" + nowMoney);
}
}
}
/*
运行结果:结婚基金账户余额为:400
托尼手里的钱为:100
托尼的女朋友账户余额不足,请确认账户余额!
*/
2、同步方法
1.同步方法仍然设计到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
public class BuyTicketSyn {
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 ticketNum = 10;
private boolean isFlag = true;
@Override
public void run() {
while(isFlag){
buy();
}
}
private synchronized void buy(){
if(ticketNum <= 0){
isFlag = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" +ticketNum-- + "张票 ");
}
}
/*
运行结果:孤身一人抢到了第10张票
孤身一人抢到了第9张票
孤身一人抢到了第8张票
孤身一人抢到了第7张票
孤身一人抢到了第6张票
孤身一人抢到了第5张票
黄牛党抢到了第4张票
黄牛党抢到了第3张票
旅游团抢到了第2张票
旅游团抢到了第1张票
*/
3、JUC安全类型的集合
public class JUCTest {
public static void main(String[] args) {
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
/*
运行结果:10000
*/
4、死锁问题
死锁:多个线程互相抱着对方需要的资源,然后形成僵持.
1)、死锁
public class DeadLock {
public static void main(String[] args) {
MakeUp makeUp1 = new MakeUp("张三", 0);
MakeUp makeUp2 = new MakeUp("李四", 1);
new Thread(makeUp1).start();
new Thread(makeUp2).start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class MakeUp implements Runnable{
//用static来保证只有一份资源
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
String name;
int choice;
public MakeUp(String name, int choice) {
this.name = name;
this.choice = choice;
}
@Override
public void run() {
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeUp() throws InterruptedException {
if(choice == 0){
synchronized (lipstick){
System.out.println(Thread.currentThread().getName() + "拿到了口红");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(Thread.currentThread().getName() + "拿到了镜子");
}
}
}else{
synchronized (mirror){
System.out.println(Thread.currentThread().getName() + "拿到了镜子");
Thread.sleep(1000);
synchronized (lipstick){
System.out.println(Thread.currentThread().getName() + "拿到了口红");
}
}
}
}
}
/*
运行结果:Thread-0拿到了口红
Thread-1拿到了镜子
(发生死锁,程序一直卡住)
*/
2)、解决死锁
把makeUp()方法进行了改进
private void makeUp() throws InterruptedException {
if(choice == 0){
synchronized (lipstick){
System.out.println(Thread.currentThread().getName() + "拿到了口红");
Thread.sleep(1000);
}
synchronized (mirror){
System.out.println(Thread.currentThread().getName() + "拿到了镜子");
}
}else{
synchronized (mirror){
System.out.println(Thread.currentThread().getName() + "拿到了镜子");
Thread.sleep(1000);
}
synchronized (lipstick){
System.out.println(Thread.currentThread().getName() + "拿到了口红");
}
}
}
/*
运行结果:Thread-1拿到了镜子
Thread-0拿到了口红
Thread-1拿到了口红
Thread-0拿到了镜子
*/
5、Lock(锁)
1.创建ReentrantLock对象
final ReentrantLock lock = new ReentrantLock()
2.加锁
void lock()
3.解锁
void unlock()
public class LockTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"李四").start();
new Thread(ticket,"张三").start();
new Thread(ticket,"王五").start();
}
}
class Ticket implements Runnable{
private static int ticketNum = 10;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
//加锁
lock.lock();
while (true){
if(ticketNum > 0){
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}finally {
//解锁
lock.unlock();
}
}
}
6、Lock(锁)与synchronized的对比
1.Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,性能更好,并且具有更好的扩展性(提供更多的子类)
4.优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
五、线程通信问题
1、常用方法
1、线程一直等待,直到其他线程通知,与sleep不同,会释放锁。
void wait()
2、指定等待的毫秒数
void wait(long timeout)
3、唤醒一个处于等待状态的线程
void notify()
4、唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度
void notifyAll()
2、生产者消费者模型
1)、管程法
生产者消费者模型——>利用缓冲区解决:管程法
public class PCTest {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Thread(new Productors(container)).start();
new Thread(new Consumers(container)).start();
}
}
//生产者
class Productors implements Runnable{
SynContainer container;
public Productors(SynContainer container) {
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Products(i));
System.out.println("生产了"+i+"个产品");
}
}
}
//消费者
class Consumers implements Runnable{
SynContainer container;
public Consumers(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+ container.pop().getId() +"个产品");
}
}
}
//产品
class Products{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Products(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//容器
Products[] products = new Products[10];
int count = 0;
//生产者生产
public synchronized void push(Products product){
//如果容器满了,需要等待消费者消费
if(count == products.length){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要放产品
products[count] = product;
count++;
//通知消费者消费
this.notifyAll();
this.notify();
}
//消费者消费
public synchronized Products pop(){
//判断能否消费
if(count == 0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Products product = products[count];
//消费完了,通知生产者生产
this.notifyAll();
return product;
}
}
2)、信号灯法
信号灯法,标志位解决
public class PCTest02 {
public static void main(String[] args) {
TV tv = new TV();
new Thread(new Player(tv)).start();
new Thread(new Watcher(tv)).start();
}
}
//生产者-->演员
class Player implements Runnable{
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.show("快乐大本营");
}else{
this.tv.show("王牌对王牌");
}
}
}
}
//消费者-->观众
class Watcher implements Runnable{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
//产品-->观众
class TV {
//节目
String program = null;
//标志位
boolean isFlag = true;
//表演:T
public synchronized void show(String program){
if(!isFlag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了"+program + "这档节目");
//通知观众观看
this.notifyAll();//通知唤醒
this.program = program;
this.isFlag = !this.isFlag;
}
//观看:F
public synchronized void watch(){
if(isFlag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了"+ this.program);
//通知演员表演
this.notifyAll();//通知唤醒
this.isFlag = !this.isFlag;
}
}
3、线程池
1.线程池相关API:ExecutorService和Executors
2.ExecutorService:真正的线程池接口。常用子类ThreadPoolExecutor。
1)执行任务/命令,没有返回值,一般执行Runnable
void execute(Runnable command)
2)关闭线程池
void shutdown()
3)设置线程池的大小
ExecutorService newFixedThreadPool(int nThreads)
3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
public class ThreadPoolTest {
public static void main(String[] args) {
//1、创建服务,创建线程池
//newFixedThreadPool,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//2、执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3、关闭连接池
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
/*
运行结果:pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-5
*/