<link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/ck_htmledit_views-b3c43d3711.css">
<div id="content_views" class="htmledit_views">
<p id="main-toc"><strong>目录</strong></p>
Java体系的一个重要部分就是多线程。在JAVA程序中,线程的最普遍用途是允许Applet在接受用户输入的同时,在屏幕的另一部分显示动画。使用多线程可以编写出CPU最大利用率的高效程序,因为空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的。
简单计数器
-
package Thread;
-
import java.awt.*;
-
import java.awt.event.*;
-
import java.applet.*;
-
-
public
class
Counter1
extends
Applet {
-
private
int
count
=
0;
-
private
Button
onOff
=
new
Button(
"Toggle");
// 初始化2个按钮
-
private
Button
start
=
new
Button(
"Start");
-
private
TextField
t
=
new
TextField(
10);
// 初始化文本字段对象
-
private
boolean
runFlag
=
true;
-
private
long
i
=
0;
// 循环变量
-
-
public
void
init
() {
-
add(t);
-
start.addActionListener(
new
StartL());
// 注册按钮
-
add(start);
// 放置按钮
-
onOff.addActionListener(
new
OnOffL());
-
add(onOff);
-
}
-
-
public
void
go
() {
-
while (
true) {
-
i =
0;
-
while (i <=
100000000) {
-
i++;
-
}
// 花费近千毫秒的时间,密集计算
-
if (runFlag)
// 一旦开始,CPU没机会执行其他代码,耗费时间
-
t.setText(Integer.toString(count++));
-
}
-
}
-
-
class
StartL
implements
ActionListener {
-
public
void
actionPerformed
(ActionEvent e)
// 内部类,实现接口,响应按钮单击事件,其实没执行的机会
-
{
-
go();
-
}
-
}
-
-
class
OnOffL
implements
ActionListener {
-
public
void
actionPerformed
(ActionEvent e)
// 内部类,实现接口,响应按钮单击事件,其实没执行的机会
-
{
-
runFlag = !runFlag;
// 一个标志变量
-
}
-
}
-
-
public
static
void
main
(String[] args)
// 应用程序入口,用浏览器时不执行该部分代码
-
{
-
Counter1
applet
=
new
Counter1();
-
Frame
aFrame
=
new
Frame(
"Counter1");
-
aFrame.addWindowListener(
new
WindowAdapter() {
// 关闭框架按钮事件,没机会执行
-
public
void
windowClosing
(WindowEvent e) {
-
System.exit(
0);
-
}
-
});
-
aFrame.add(applet, BorderLayout.CENTER);
-
aFrame.setSize(
300,
200);
-
applet.init();
-
applet.start();
-
aFrame.setVisible(
true);
-
}
-
}
一旦按下start按钮,就会调用go()方法,go()方法是永远不会返回的,因为它被设计成一个无限循环。即在第一个按键以后便陷入go()中,go()根本不会返回,程序不能再对其他任何事件进行控制,这时的Toggle按钮和窗口的关闭按钮不再有反应。
Runnable接口
使用接口的原因在于:Applet程序是由于JApplet类扩展而来,因为Java不支持多重继承,所以我们不能同时从JApplet类和Thread类派生类,所以用Runnable接口。
用线程改写上面的代码
-
package Thread;
-
import java.awt.*;
-
import java.awt.event.*;
-
import java.applet.*;
-
-
public
class
Counter2
extends
Applet
implements
Runnable{
//线程接口
-
private
int
count
=
0;
-
private
Button
onOff
=
new
Button(
"Toggle");
// 初始化2个按钮
-
private
Button
start
=
new
Button(
"Start");
-
private
TextField
t
=
new
TextField(
10);
// 初始化文本字段对象
-
private
boolean
runFlag
=
true;
-
private
long
i
=
0;
// 循环变量
-
-
Thread athread;
//线程对象声明
-
public
void
init
(){
-
start.addActionListener(
new
StartL());
-
add(start);
-
start.addActionListener(
new
OnOffl());
-
add(onOff);
-
-
}
-
public
void
run
(){
-
System.out.println(
"已经调用run()");
-
while(
true){
-
try{
-
athread.sleep(
3000);
//休眠3秒,CPU执行其他代码
-
}
catch(InterruptedException e){
-
-
}
-
if(runFlag){
-
count++;
-
System.out.println(
"count="+count);
-
}
-
}
-
}
-
class
StartL
implements
ActionListener{
-
public
void
actionPerformed
(ActionEvent e)
//CPU有机会响应按钮的单击事件
-
{
-
Counter2 aCounter2=
new
Counter2();
//
-
athread =
new
Thread(aCounter2);
//线程创立
-
athread.start();
//线程启动
-
System.out.println(
"已经按下start按钮");
-
}
-
}
-
class
OnOffl
implements
ActionListener{
-
public
void
actionPerformed
(ActionEvent e){
-
System.out.println(
"已经按下onOff按钮");
-
}
-
}
-
public
static
void
main
(String[] args){
-
Counter2 applet=
new
Counter2();
-
Frame
aFrame
=
new
Frame(
"Counter2");
-
aFrame.addWindowListener(
new
WindowAdapter() {
// 关闭框架按钮事件,没机会执行
-
public
void
windowClosing
(WindowEvent e) {
-
System.exit(
0);
-
}
-
});
-
aFrame.add(applet, BorderLayout.CENTER);
-
aFrame.setSize(
300,
200);
-
applet.init();
-
applet.start();
-
aFrame.setVisible(
true);
-
}
-
-
}
程序运行后,单击start按钮开始计数,并显示相应的信息,计数过程中单击Toggle按钮也能做出相应的反应,单击退出可退出。
狂神的多线程代码
-
package Thread;
-
//创建线程方式,实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
-
public
class
TestThread3
implements
Runnable{
-
public
void
run
(){
-
//run方法线程体
-
for(
int
i
=
0;i<
100;i++){
-
System.out.println(
"在练花式单杠动作马凯、"+i);
-
}
-
}
-
public
static
void
main
(String[] args){
-
//创建runnable接口的实现类对象
-
TestThread3
testThread3
=
new
TestThread3();
-
//创建线程对象,通过线程对象来开启我们的线程,代理
-
// Thread thread = new Thread(testThread3);
-
// thread.start();
-
new
Thread(testThread3).start();
//上面两行代码可以写成这一句
-
for(
int i=
0;i<
1000;i++){
-
System.out.println(
"我在练单杠360--"+i);
-
}
-
}
-
}
可以从运行结果看粗是同时运行的,不过在把i调整为100以下的就会出现线程堆块。
实现Runnable接口的好处
1.实现接口Runnable具有多线程能力
2.启动线程:传入目标对象+Thread对象.start()
3.避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
狂神的并发问题代码——以抢票为例
-
package Thread;
-
//多个线程同时操作一个对象
-
//买火车票
-
public
class
TestThread4
implements
Runnable{
-
//票数
-
private
int
ticketNums
=
10;
//10张票
-
-
-
//重写run方法
-
public
void
run
(){
-
while(
true){
-
if(ticketNums<=
0){
-
break;
//如果没票了就break跳出
-
}
-
System.out.println(Thread.currentThread().getName()+
"-->拿到了第"+ticketNums--+
"票");
-
}
-
}
-
public
static
void
main
(String[] args){
-
TestThread4 ticket=
new
TestThread4();
-
new
Thread(ticket,
"俊俊").start();
-
new
Thread(ticket,
"俊爷").start();
-
new
Thread(ticket,
"虾翻").start();
-
}
-
}
这里我们运行可以看到俊爷一个人把所有的票抢光了
然后设置休眠以防止一个人拿走全部的票
在16行代码加入
-
try{
-
Thread.sleep(
100);
//设置休眠200毫秒
-
}
catch(InterruptedException e){
-
e.printStackTrace();
-
}
发现拿了超过10张的票,说明多个线程操作同一个资源的情况下,线程不安全,数据出现紊乱。
龟兔赛跑-Race
-
package Thread;
-
//模拟龟兔赛跑
-
public
class
Race
implements
Runnable {
-
//静态的胜利者
-
private
static String winner;
-
public
void
run
(){
-
for(
int i=
0;i<=
100;i++){
-
//判断比赛是否结束
-
boolean
flag
= gameOver(i);
-
if(flag){
//如果flag等于真
-
break;
//如果比赛结束就停止
-
}
-
System.out.println(Thread.currentThread().getName()+
"跑了————"+i+
"步长");
-
}
-
}
-
-
//判断是否完成比赛
-
private
boolean
gameOver
(int steep){
-
//判断是否有胜利者
-
if(winner !=
null){
-
return
true;
-
}{
-
if(steep>=
100){
-
winner = Thread.currentThread().getName();
-
System.out.println(
"winner is "+winner);
-
return
true;
-
}
-
}
-
return
false;
-
}
-
public
static
void
main
(String[] args){
-
Race
race
=
new
Race();
-
-
new
Thread(race,
"乌龟").start();
-
new
Thread(race,
"兔子").start();
-
}
-
}
兔子赢了
然后我们让兔子睡觉在第八行加入
-
//这里让兔子睡觉
-
if(Thread.currentThread().getName().equals(
"兔子")&&i%
10==
0){
-
try{
-
Thread.sleep(
1);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
实现Callable接口(了解即可)
Lamda表达式
--希腊字母表中排序第十一位的字母,英文名称为Lambda
--避免匿名内部类定义过多
--其实质函数式编程的概念
为什么要使用lambda表达式
--去掉一堆没有意义的代码,只留下核心的逻辑
-
package Thread;
-
/*
-
推导lamda表达式
-
*/
-
public
class
TestLamda {
-
public
static
void
main
(String[] args){
-
Ilike
l
=
new
Like();
-
l.lamda();
-
}
-
-
}
-
//1.定义一个函数式的接口
-
interface
Ilike{
-
void
lamda
();
-
}
-
//2.实现类
-
class
Like
implements
Ilike{
-
public
void
lamda
(){
-
System.out.println(
"牛马努力给老板奋斗法拉利");
-
}
-
-
}
优化。。
-
package Thread;
-
/*
-
推导lamda表达式
-
*/
-
public
class
TestLamda {
-
//静态内部类
-
static
class
Like2
implements
Ilike{
-
public
void
lamda
(){
-
System.out.println(
"久旱逢甘露");
-
}
-
-
}
-
public
static
void
main
(String[] args){
-
Ilike
l
=
new
Like();
-
l.lamda();
-
-
l =
new
Like2();
-
l.lamda();
-
-
//4.局部内部类
-
class
Like3
implements
Ilike{
-
public
void
lamda
(){
-
System.out.println(
"他乡遇故知");
-
}
-
}
-
l =
new
Like3();
-
l.lamda();
-
-
//5.匿名内部类,没有类的名称,必须借助接口或者父类(没有名字,借助了接口)
-
l=
new
Ilike(){
-
public
void
lamda
(){
-
System.out.println(
"洞房花烛夜");
-
}
-
};
-
l.lamda();
-
-
//6.有lamda简化
-
l = ()->{
-
System.out.println(
"金榜题名时");
-
};
-
l.lamda();
-
}
-
-
}
-
//1.定义一个函数式的接口
-
interface
Ilike{
-
void
lamda
();
-
}
-
//2.实现类
-
class
Like
implements
Ilike{
-
public
void
lamda
(){
-
System.out.println(
"牛马努力给老板奋斗法拉利");
-
}
-
-
}
lamda表达式的一些总结
//lamda表达式只能有一行代码的情况下才能简化为一行;如果有多行,那么就用代码块包裹。(即花括号)
//前提是接口为函数式接口
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
线程状态
五大状态:创建状态、就绪状态、阻塞状态、运行状态、死亡状态。
线程停止Stop()
-
package Thread;
-
-
//测试stop
-
//1.建议线程正常停止-->利用次数,不建议死循环
-
//2.建议使用标志位-->设置一个标志位
-
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
-
public
class
StopTest
implements
Runnable{
-
//1.设置一个标识位
-
private
boolean
flag
=
true;
-
-
public
void
run
(){
-
int i=
0;
-
while(flag){
-
System.out.println(
"runThread"+i++);
-
}
-
}
-
-
//2.设置一个公开的方法停止线程,转换标志位
-
public
void
Stop
(){
-
this.flag =
false;
-
}
-
-
public
static
void
main
(String[] args){
-
StopTest
ST
=
new
StopTest();
-
-
new
Thread(ST).start();
-
-
for(
int i=
0; i<
100;i++){
-
System.out.println(
"main"+i);
-
if (i ==
90) {
-
//调用stop方法切换标志位,让线程停止
-
ST.Stop();
-
System.out.println(
"stopThread");
-
}
-
}
-
}
-
}
在main执行到90的时候停止线程
线程休眠Sleep()
>sleep(时间)指定当前线程阻塞的毫秒数
>sleep存在异常InterruptedException
>sleep时间达到后线程进入就绪状态
>sleep可以模拟网络延时,倒计时等
>每一个对象都有一个锁,sleep不会释放锁
-
package Thread;
-
//模拟倒计时
-
public
class
Sleep {
-
public
static
void
main
(String[] args){
-
try{
-
tenDown();
-
}
catch(InterruptedException e){
-
e.printStackTrace();
-
}
-
}
-
public
static
void
tenDown
()
throws InterruptedException{
-
int
num
=
10;
-
while(
true){
-
Thread.sleep(
1000);
-
System.out.println(num--);
-
if(num==
0){
-
break;
-
}
-
}
-
}
-
}
每隔一秒打印一下
-
package Thread;
-
import java.util.Date;
-
import java.text.SimpleDateFormat;
-
//模拟倒计时
-
public
class
Sleep {
-
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();
-
}
-
}
-
}
-
public
static
void
tenDown
()
throws InterruptedException{
-
int
num
=
10;
-
while(
true){
-
Thread.sleep(
1000);
-
System.out.println(num--);
-
if(num==
0){
-
break;
-
}
-
}
-
}
-
}
第一次运行发现没有类 SimpleDateFormat,于是调用java.text.SimpleDateFormat
观察线程状态
-
package Thread;
-
//观察测试线程的状态
-
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(
100);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
System.out.println(
"");
-
});
-
//观察状态
-
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);
//输出状态
-
}
-
}
-
}
这里我们试一下线程停止后还能否正常启动,在while循环后添加thread.start()
运行发现没办法再次启动线程。
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
package Thread;
-
//测试线程的优先级
-
public
class
Testpriority {
-
public
static
void
main
(String[] args){
-
//主线程默认优先级
-
System.out.println(Thread.currentThread().getName()+
"-->"+Thread.currentThread());
-
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);
-
-
//先不设置优先级,启动
-
t1.start();
-
-
t2.setPriority(
1);
-
t2.start();
-
-
t3.setPriority(
4);
-
t3.start();
-
-
t4.setPriority(Thread.MAX_PRIORITY);
//MAX_PRIPRITY
-
t4.start();
-
}
-
}
-
-
class
Mypriority
implements
Runnable{
-
public
void
run
(){
-
System.out.println(Thread.currentThread().getName()+
"-->"+Thread.currentThread().getPriority());
-
}
-
}
可以看到优先级高的先跑,但是你得设置优先级(优先级数为1~10)
要注意的是这里可能会出现性能倒置:优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调度了,这都是看CPU的调度。
守护(daemon)线程
(1)线程分为用户线程和守护线程
(2)虚拟机必须确保用户线程执行完毕
(3)虚拟机不用等待守护线程执行完毕
-
package Thread;
-
//测试守护线程
-
//上帝守护你,人生不过三万天
-
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);
//默认false表示用户线程
-
-
thread.start();
//上帝线程启动
-
new
Thread(you).start();
//你 用户线程启动
-
}
-
}
-
-
//上帝
-
class
God
implements
Runnable{
-
public
void
run
(){
-
while (
true) {
-
System.out.println(
"你被上帝保护着");
-
-
}
-
}
-
}
-
-
-
//你
-
class
You
implements
Runnable {
-
public
void
run
() {
-
for (
int
i
=
1; i <
36500; i++) {
-
System.out.println(
"你活到了"+i+
"天");
-
}
-
System.out.println(
"差不多活够了,该狗带了");
-
}
-
}
虚拟机关闭需要一定的时间,所以用户线程结束后,守护线程还在运行。
线程同步
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放即可。
三大不安全案例
1.不安全的买票
-
package Thread;
-
//不安全的买票
-
//线程不安全,会有负数
-
public
class
UnsafeBuyTicket {
-
public
static
void
main
(String[] args){
-
BuyTicket
station
=
new
BuyTicket();
-
-
new
Thread(station,
"niuam").start();
-
new
Thread(station,
"niuam1").start();
-
new
Thread(station,
"niuam2").start();
-
}
-
-
}
-
-
class
BuyTicket
implements
Runnable{
-
//票
-
private
int
ticketNums
=
10;
-
boolean
flag
=
true;
//外部停止方式
-
-
public
void
run
(){
-
//买票
-
while (flag) {
-
try{
-
buy();
//调用方法buy买票
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
-
private
void
buy
()
throws InterruptedException{
-
//判断是否有票
-
if (ticketNums <=
0) {
-
flag =
false;
-
return;
-
}
-
//模拟延时
-
Thread.sleep(
100);
-
//买票
-
System.out.println(Thread.currentThread().getName()+
"拿到"+ticketNums--);
-
}
-
}
2.银行取钱
-
package Thread;
-
//不安全的取钱
-
//两个人同时取钱
-
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,
"牛马de女票");
-
-
you.start();
-
girlfriend.start();
-
}
-
}
-
-
-
//账户
-
class
Account{
-
int money;
//余额
-
String name;
//卡名
-
//Alt+insert构造函数
-
//按住Ctrl选中两个
-
public
Account
(int money, String name) {
-
this.money = money;
-
this.name = name;
-
}
-
}
-
-
//银行:模拟取款
-
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;
-
-
}
-
-
//取钱
-
public
void
run
(){
-
//判断有没有钱
-
if(account.money-drawingMoney<
0){
-
//钱不够,要取的钱比账户余额多
-
System.out.println(Thread.currentThread().getName()+
"钱不够,不能操作");
-
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.currountThread().getName() = this.getName()
-
System.out.println(
this.getName()+
"手里的钱"+nowMoney);
-
}
-
}
这里相当于负债了,两人操作一个账户,但是在不同的内存地址中。
3.线程不安全的情况
死锁
两个线程在一瞬间同时加载到了同一个位置中,把数据加载在同一位置,然后覆盖掉了。就会缺失掉一部分线程。
死锁:多个线程相互抱着对方需要的资源,然后形成僵持package com.wang.thread;
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"灰姑娘");
Makeup girl2 = new Makeup(1,"白雪公主");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
//需要的资源只有一份,用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();
}
}
//super.run();
//化妆,互相持有对方的锁,需要拿到对方的资源
/*
第一个人进来想拿口红,1秒之后拿镜子,然后离开
第二个人进来拿镜子,2秒之后拿口红,然后离开
于是形成了互相僵持,想拿到对方的东西
**/
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+"获得口红的锁");
}
}
}
}
}
解决方法:把同步块拿出来,不让一个对象同时获得两把锁
package com.wang.thread;
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"灰姑娘");
Makeup girl2 = new Makeup(1,"白雪公主");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
//需要的资源只有一份,用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();
}
}
//super.run();
//化妆,互相持有对方的锁,需要拿到对方的资源
/*
第一个人进来想拿口红,1秒之后拿镜子,然后离开
第二个人进来拿镜子,2秒之后拿口红,然后离开
于是形成了互相僵持,想拿到对方的东西
**/
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+"获得口红的锁");
}
}
}
}
死锁避免方法:
产生死锁的四个条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
(4)循环等待条件:若进程之间形成一种头尾相接的循环等待资源关系。
Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁用Lock对象充当 java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象 ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
-
package Thread;
-
import java.util.concurrent.locks.ReentrantLock;
-
//测试Lock锁
-
public
class
TestLock {
-
public
static
void
main
(String[] args){
-
TestLock2
testLock2
=
new
TestLock2();
-
-
new
Thread(testLock2).start();
-
new
Thread(testLock2).start();
-
new
Thread(testLock2).start();
-
new
Thread(testLock2).start();
-
}
-
}
-
-
-
class
TestLock2
implements
Runnable{
-
//定义票数
-
int
ticketNums
=
10;
-
-
//定义lock锁
-
private
final
ReentrantLock
lock
=
new
ReentrantLock();
-
-
public
void
run
(){
-
while(
true){
-
// lock.lock();
-
try {
-
lock.lock();
-
-
if(ticketNums>
0){
-
if(ticketNums>
0){
-
try{
-
lock.lock();
//加锁
-
Thread.sleep(
1000);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
System.out.println(ticketNums--);
-
}
else{
-
break;
-
}
-
}
-
}
finally {
-
//解锁
-
lock.unlock();
-
}
-
-
}
-
}
-
}
排队一个一个来拿票。
多多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有**”两个以上对象的锁“**时,就可能发生”死锁“的问题个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有**”两个以上对象的锁“**时,就可能发生”死锁“的问题管程法
-
package newbegin;
-
//测试:生产者消费者模型-->利用缓冲区解决;
-
-
-
//生产者,消费者,产品,缓冲区
-
public
class
TestPC {
-
public
static
void
main
(String[] args) {
-
SynContainer
container
=
new
SynContainer();
-
new
Productor(container).start();
-
new
Cousumer(container).start();
-
}
-
}
-
-
//生产者
-
class
Productor
extends
Thread{
-
SynContainer container;
-
public
Productor
(SynContainer container){
-
this.container=container;
-
}
-
-
//生产工作
-
public
void
run
(){
-
for (
int
i
=
0; i <
100 ; i++) {
-
container.push(
new
Chicken(i));
//把生产的鸡丢经容器
-
System.out.println(
"生产了"+i+
"只鸡");
-
}
-
}
-
}
-
-
//消费者
-
class
Cousumer
extends
Thread{
-
SynContainer container;
-
public
Cousumer
(SynContainer container){
-
this.container = container;
-
}
-
//消费
-
public
void
run
(){
-
for (
int
i
=
0; i <
100; i++) {
-
System.out.println(
"消费了"+container.pop().id+
"只鸡");
-
-
}
-
}
-
}
-
-
//产品
-
class
Chicken{
-
int id;
//产品鸡的编号
-
public
Chicken
(int id){
-
this.id=id;
-
}
-
}
-
-
//缓冲区
-
class
SynContainer{
-
//需要一个容器大小
-
Chicken[] chickens =
new
Chicken[
10];
//一次性10只鸡
-
int count=
0;
//这里是容器计数器
-
//生产者放入产品
-
public
synchronized
void
push
(Chicken chicken){
-
//如果容器满了,就需要等待消费者消费
-
if(count==chickens.length){
-
//通知消费者消费,生产者等待
-
-
try{
-
this.wait();
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
//这里容器没满,我们把产品丢入
-
chickens[count]=chicken;
-
count++;
-
//可以通知消费者消费
-
this.notifyAll();
-
}
-
//消费者消费产品
-
public
synchronized Chicken
pop
(){
-
//判断能否消费
-
if(count==
0){
-
try{
-
this.wait();
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
//这里可以消费
-
count--;
-
Chicken
chicken
= chickens[count];
-
//吃完通知生成者生产
-
this.notifyAll();
-
return chicken;
-
}
-
}
生产的产品都能被消费者消费到。
解决方式2:信号灯法
并发写作模型“生产者/消费者模式”–>信号灯法==设置标志位
package com.xuyuan;
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Wathcher(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 Wathcher extends Thread{
TV tv;
public Wathcher(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.notifyAll();// 通知唤醒
this.voice = voice;
this.flag = !flag;
}
// 观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了: "+voice);
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}package com.wang.gaoji;
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Wathcher(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 Wathcher extends Thread{
TV tv;
public Wathcher(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.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !flag;
}
//观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了: "+voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
使用背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似于公共交通工具,共享单车。
优点:
(1)提高响应速度(减少创建新线程的时间)
(2)降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
(3)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize;最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
上代码:
-
package newbegin;
-
-
import java.util.concurrent.Executor;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
-
//线程池
-
public
class
TestPool {
-
public
static
void
main
(String[] args) {
-
//1.创建服务,创建线程池
-
//newFixedThreadPool参数为:线程池大小
-
ExecutorService
service
= Executors.newFixedThreadPool(
10);
-
//执行
-
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());
-
}
-
}
总结
-
package newbegin;
-
-
import org.w3c.dom.ls.LSOutput;
-
-
import java.util.concurrent.Callable;
-
import java.util.concurrent.ExecutionException;
-
import java.util.concurrent.FutureTask;
-
-
//总结线程的创建
-
public
class
ThreadNew {
-
public
static
void
main
(String[] args) {
-
new
MyThread1().start();
-
-
new
Thread(
new
MyThread2()).start();
-
-
FutureTask
futureTask
=
new
FutureTask<Integer>(
new
MyThread3());
-
-
new
Thread(futureTask).start();
-
try{
-
Integer
integer
= (Integer) futureTask.get();
-
System.out.println(integer);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
catch (ExecutionException e){
-
e.printStackTrace();
-
}
-
}
-
}
-
//1.继承Thread类
-
class
MyThread1
extends
Thread{
-
@Override
-
public
void
run
() {
-
System.out.println(
"启动线程1");
-
}
-
}
-
-
//2.实现Runnable接口
-
class
MyThread2
implements
Runnable{
-
@Override
-
public
void
run
() {
-
System.out.println(
"启动线程2");
-
}
-
}
-
//3.实现Callable接口
-
class
MyThread3
implements
Callable<Integer>{
-
@Override
-
public Integer
call
()
throws Exception {
-
System.out.println(
"启动线程3");
-
return
100;
-
}
-
}
三个线程正常启动。