Java学习—多线程详解
1、线程简介
程序
:是指令和数据的有序集合,是一个静态的概念
进程
:是程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的。 进程作为
操作系统调度和分配资源的最小单位
线程
:是CPU调度和执行的单位。一个进程中至少有一个线程注意:
- main()称之为主线程,为系统的入口,用于执行整个程序
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2、线程实现
2.1、线程创建
-
继承Thread类
步骤:
(1)创建一个继承于Thread类的子类
(2)重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
(3)创建当前Thread的子类的对象
(4)通过对象调用start():1.启动线程;2.调用当前线程的方法//注意:线程开启不一定立即执行,由cpu调度执行 public class TestThread01 extends Thread{ @Override public void run() { //run方法线程体 for (int i = 0; i < 10; i++) { System.out.println("我在学线程----"+i); } } public static void main(String[] args) { //main线程,主线程 //创建一个线程对象 TestThread01 testThread01 = new TestThread01(); //调用start()方法开启线程 testThread01.start(); for (int i = 0; i < 10; i++) { System.out.println("我在学Java----"+i); } } }
案例:网图下载
public class TestThread02 extends Thread{ private String url;//网络图片地址 private String name;//保存的文件名 public TestThread02(String url, String name) { this.url = url; this.name = name; } //下载图片线程的执行体 @Override public void run() { WebImageDownload webImageDownload = new WebImageDownload(); webImageDownload.downloader(url,name); System.out.println("下载了文件名为:"+name); } public static void main(String[] args) { TestThread02 t1 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman1.jpg"); TestThread02 t2 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman2.jpg"); TestThread02 t3 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman3.jpg"); t1.start(); t2.start(); t3.start(); } } //下载器 class WebImageDownload{ //下载方法 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异常,downloader方法出现问题"); } } }
-
实现Runnable接口
步骤:
(1)创建一个实现Runnable接口的类
(2)实现接口中的run() —>将此线程要执行的操作,声明在此方法体中
(3)创建当前类的对象
(4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
(5)Thread类的实例调用start():1.启动线程;2.调用当前线程的方法public class TestThread01 implements Runnable{ @Override public void run() { //run方法线程体 for (int i = 0; i < 10; i++) { System.out.println("我在学线程----"+i); } } public static void main(String[] args) { //main线程,主线程 //创建Runnable接口的实现类对象 TestThread01 testThread01 = new TestThread01(); //创建线程对象,通过线程对象来开启线程 //Thread thread = new Thread(testThread01); //调用start()方法开启线程 //thread.start(); new Thread(testThread01).start(); for (int i = 0; i < 10; i++) { System.out.println("我在学Java----"+i); } } }
案例:网图下载(Runnable接口实现)
public class TestThread02 implements Runnable{ private String url;//网络图片地址 private String name;//保存的文件名 public TestThread02(String url, String name) { this.url = url; this.name = name; } //下载图片线程的执行体 @Override public void run() { WebImageDownload webImageDownload = new WebImageDownload(); webImageDownload.downloader(url,name); System.out.println("下载了文件名为:"+name); } public static void main(String[] args) { TestThread02 t1 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman11.jpg"); TestThread02 t2 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman22.jpg"); TestThread02 t3 = new TestThread02("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman33.jpg"); new Thread(t1).start(); new Thread(t2).start(); new Thread(t3).start(); } } //下载器 class WebImageDownload{ //下载方法 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异常,downloader方法出现问题"); } } }
-
实现Callable接口(了解)
步骤:
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:Boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
案例:利用Callable改造下载网图案例
/*
Callable的好处:
1.可以定义返回值
2.可以抛出异常
*/
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 {
WebImageDownload webImageDownload = new WebImageDownload();
webImageDownload.downloader(url,name);
System.out.println("下载了文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman11.jpg");
TestCallable t2 = new TestCallable("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman22.jpg");
TestCallable t3 = new TestCallable("https://ts1.cn.mm.bing.net/th/id/R-C.40f3e1a743389a3019170e0a2240c7a0?rik=6vb2h0bmmahvwg&riu=http%3a%2f%2ffiles.photops.com%3a81%2fattachment%2fMon_2209%2f66_578680_4f63fce054db0e7.jpg%3f80&ehk=PXKQn0XjPd8M5elRSveWLt4fEt%2fYBUBgoYnznTwD%2bvc%3d&risl=&pid=ImgRaw&r=0", "woman33.jpg");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 = ser.submit(t1);
Future<Boolean> result2 = ser.submit(t2);
Future<Boolean> result3 = ser.submit(t3);
//获取结果:
Boolean r1 = result1.get();
Boolean r2 = result2.get();
Boolean r3 = result3.get();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//关闭服务:
ser.shutdownNow();
}
}
//下载器
class WebImageDownload{
//下载方法
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异常,downloader方法出现问题");
}
}
}
2.2、小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 一般不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
案例1:使用多个线程操作同一个对象
//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱(该问题在线程同步模块进行解决)
public class TestThread implements Runnable{
//票数
private int ticketNum=100;
@Override
public void run() {
while (true){
if (ticketNum<=0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNum--+"张票");
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
new Thread(testThread,"小明").start();
new Thread(testThread,"黄牛").start();
new Thread(testThread,"老师").start();
}
}
案例2:龟兔赛跑-Race
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中乌龟是赢家,兔子需要睡觉,所以我们会模拟兔子睡觉
- 最后,乌龟赢得比赛
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 1; i < 101; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag=gameOver(i);
if (flag){//比赛结束,程序停止
break;
}
System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){//胜利者已存在
return true;
}else {
if (steps==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();
}
}
2.3、静态代理
演示:实现静态代理对比Thread
静态代理模式总结:
真实对象和代理对象的都要实现同一个接口;
代理对象必须要代理真实角色
好处:代理对象可以做很多真实对象无法做的事;真实对象专注做自己的事
静态代理是实现Runnable接口进行调用start方法是底层原理
public class StaticProxy {
public static void main(String[] args) {
new Thread(()-> System.out.println("我爱你")).start();
new WeddingComapany(new You()).HappyMarry();
// WeddingComapany weddingComapany = new WeddingComapany(new You());
// weddingComapany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色,结婚的主人公
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("我很开心!结婚了");
}
}
//代理角色,婚庆公司
class WeddingComapany implements Marry{
private Marry target;
public WeddingComapany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before(){
System.out.println("结婚前,布置现场");
}
private void after(){
System.out.println("结婚之后,收尾款");
}
}
2.4、扩展
-
Lambda表达式:避免匿名内部类定义过多;使代码看起来更简洁;保留核心逻辑
函数式接口定义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口 例如:Runnable接口
- 对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
(params)->expression[表达式] (params)->statement[语句] (params)->{statement}
演示:代码推导Lambda表达式
public class TestLamda01 { //3.静态内部类 static class Like2 implements ILike{ @Override public void lamda() { System.out.println("I am a Lamda2"); } } public static void main(String[] args) { ILike like = new Like(); like.lamda(); like=new Like2(); like.lamda(); //4.局部内部类 class Like3 implements ILike{ @Override public void lamda() { System.out.println("I am a Lamda3"); } } like=new Like3(); like.lamda(); //5.匿名内部类,没有类的名称,必须借助接口或者父类 like=new ILike() { @Override public void lamda() { System.out.println("I am a Lamda4"); } }; like.lamda(); //6.用Lamda简化 like=() ->{ System.out.println("I am a Lamda5"); }; like.lamda(); } } //1.定义一个函数式接口 interface ILike{ void lamda(); } //2.实现类 class Like implements ILike{ @Override public void lamda() { System.out.println("I am a Lamda"); } }
Lamba表达式的简化:
public class TestLambda02 { //Lambda表达式仅限于函数式接口 public static void main(String[] args) { // ILove love=new Love(); // ILove love=(int a) ->{ // System.out.println("I love you-->"+a); // }; //简化1:参数类型(多个参数也可以去除参数类型,但需都保持一致) // ILove love=(a) ->{ // System.out.println("I love you-->"+a); // }; //简化2:简化括号 // ILove love=a ->{ // System.out.println("I love you-->"+a); // }; //简化3:去除{}(仅限单行) ILove love=a -> System.out.println("I love you-->"+a); love.love(2); } } interface ILove{ void love(int a); } //class Love implements ILove{ // @Override // public void love(int a) { // System.out.println("I love you-->"+a); // } //}
3、线程状态
3.1、线程的生命周期
3.2、线程的常用方法
>start():1.启动线程;2.调用线程中的run()
>run():将线程要执行的操作,声明在run()中
>currentThread():获取当前执行代码对应的线程
>getName():获取线程名
>setName():设置线程名
>sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
>yield():静态方法,一旦执行此方法,就释放CPU的执行权
>join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
>isAlive():判断当前线程是否还存活
过时方法:
>stop():强行结束一个线程的执行,直接进入死亡状态,不建议使用
>void suspend() / void resume():可能造成死锁,所以也不建议使用
线程的优先级:
getPriority():获取线程的优先级
setPriority():设置线程的优先级,范围[1,10]
-
线程停止
/* 1.建议线程正常停止---->利用次数,不建议死循环 * 2.建议使用标志位---->设置一个标志位 * 3.不要使用stop或者destory等过时或JDK不建议使用的方法 */ public class TestStop implements Runnable { //1.设置一个标志位 private boolean flag=true; @Override public void run() { int i=0; while (flag){ System.out.println("run----Thread"+i); } } //2.设置一个公开的方法停止线程,转换标志位 public void stop(){ this.flag=false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i=0;i<1000;i++){ System.out.println("main...."+i); if (i==500){ //调用stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程该停止了"); } } } }
-
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
演示:计时
public class TestSleep2 { //模拟倒计时 public static void tenDown() throws InterruptedException { int num=10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num<0){ break; } } } public static void main(String[] args) { // try { // tenDown(); // } catch (InterruptedException e) { // e.printStackTrace(); // } //打印当前系统时间 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(); } } } }
-
线程礼让(yield)
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看CPU心情
public class TestYield01 { public static void main(String[] args) { new Thread(new MyYield(),"a").start(); new Thread(new 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()+"线程停止执行"); } }
-
线程强制执行(join)
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可想象成插队
public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("VIP来了"+i); } } public static void main(String[] args) throws InterruptedException { //启动我们的线程 // new Thread(new TestJoin()).start(); TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); //主线程 for (int i = 0; i < 50; i++) { if (i==20){ thread.join();//插队 } System.out.println("main----"+i); } } }
3.3、线程状态观测
-
Thread.State
- NEW 尚未启动的线程处于此状态
- RUNNABLE 在Java虚拟机中执行的线程处于此状态
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING 正在等待另一个线程执行动作达到等待时间的线程处于此状态
- TERMINATED 已退出的线程处于此状态
2.观测线程状态
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("-----");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后
thread.start();
state=thread.getState();
System.out.println(state);//Runnable
while (state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
Thread.sleep(100);
state=thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
}
}
注意
:线程中断或者结束,一旦进入死亡状态,就不能再次启动
3.4、线程优先级
public class TestPtiority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
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.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
3.5、守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等
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{
@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!world");
}
}
4、线程同步
并发问题:多个线程操作同一个资源
4.1、线程同步机制
Java是使用线程的
同步机制
解决线程的安全问题的使用synchronized关键字
-
测试线程不安全的买票机制,该机制会出现重票和错票的问题
public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"打工人").start(); new Thread(station, "老板").start(); new Thread(station,"黄牛").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNum=10; boolean flag=true; @Override public void run() { //买票 while (flag){ buy(); } } private 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 UnsafeBank { public static void main(String[] args) { //帐户 Account account = new Account(100, "创业基金"); Bank you = new Bank(account, 50, "创业人"); Bank friend = new Bank(account, 100, "创始人2"); you.start(); friend.start(); } } class Account{ int money;//金额 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行:模拟取款 class Bank extends Thread{ Account account;//帐户 int drawingMoney;//取款金额 int nowMoney;//手上现有金额 public Bank(Account account, int drawingMoney, String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 @Override public void run() { //判断卡里有没有钱 if (account.money<drawingMoney){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } //模拟延时 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额 account.money-=drawingMoney; //现存现金 nowMoney+=drawingMoney; System.out.println(account.name + "余额为:" + account.money); //Thread.currentThread().getName()=this.getName() System.out.println(this.getName()+"手上的现金金额为:"+nowMoney); } }
-
测试线程不安全的集合
public class UnsafeList { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Iterator<String> iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } System.out.println(list.size()); } }
4.使用synchronized关键字
- 方式1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}说明:
- 需要被同步的代码,即为操作共享数据的代码
- 共享数据,即多个线程需要操作的数据。比如:ticket
- 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待
- 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码
- 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器
注意
:
- 在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
- 在继承Thread类的方式中,同步监视器要慎用this。可以考虑使用:当前类.class
- 方式2:同步方法
说明:
- 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可
- 非静态的同步方法,默认同步监视器是this;静态的同步方法,默认同步监视器是当前类本身
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"打工人").start();
new Thread(station, "老板").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNum=10;
boolean flag=true;
@Override
public void run() {
//买票
while (flag){
buy();
}
}
//synchronized 同步方法,锁的是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 UnsafeBank {
public static void main(String[] args) {
//帐户
Account account = new Account(180, "创业基金");
Bank you = new Bank(account, 50, "创业人");
Bank friend = new Bank(account, 100, "创始人2");
you.start();
friend.start();
}
}
class Account{
int money;//金额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Bank extends Thread{
Account account;//帐户
int drawingMoney;//取款金额
int nowMoney;//手上现有金额
public Bank(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是this
@Override
public void run() {
//锁的对象需要是变化的量,如增删改
synchronized (account){
//判断卡里有没有钱
if (account.money<drawingMoney){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
//模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money-=drawingMoney;
//现存现金
nowMoney+=drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手上的现金金额为:"+nowMoney);
}
}
}
//-------------------------------------------------------------------------------------------------------
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(list.size());
}
}
4.2、死锁
定义:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
诱发死锁的原因:
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待
以上4个条件,同时出现就会触发死锁
如何避免死锁?
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
死锁情况举例:
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "小红帽");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.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();
}
}
//化妆,互相持有对方的锁,就是需要拿到对方是资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");
Thread.sleep(1000);
// synchronized (mirror){//1s后想获得镜子的锁
// System.out.println(this.girlName+"获得镜子的锁");
// }
}
synchronized (mirror){//1s后想获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");
}
}else {
synchronized (mirror){//获得口红的锁
System.out.println(this.girlName+"获得镜子的锁");
Thread.sleep(2000);
// synchronized (lipstick){//1s后想获得镜子的锁
// System.out.println(this.girlName+"获得口红的锁");
// }
}
synchronized (lipstick){//1s后想获得镜子的锁
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
4.3、Lock(锁)
-
JDK5.0的新增功能,保证线程的安全。与采用synchronized相比,Lock可提供多种锁方案,更灵活、更强大。Lock通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
-
在实现线程安全的控制中,比较常用的是
ReentrantLock
,可以显式加锁、释放锁。- ReentrantLock类实现了 Lock 接口,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
-
Lock锁也称同步锁,加锁与释放锁方法,如下:
- public void lock() :加同步锁。
- public void unlock() :释放同步锁。
class A{
//1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
//2. 调动lock(),实现需共享的代码的锁定
lock.lock();
try{
//保证线程安全的代码;
}
finally{
//3. 调用unlock(),释放共享代码的锁定
lock.unlock();
}
}
}
public class TestLock {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"Tom").start();
new Thread(buyTickets,"Mary").start();
new Thread(buyTickets,"Tony").start();
}
}
class BuyTickets implements Runnable{
private int ticketNums=20;
//定义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(Thread.currentThread().getName()+"-->"+ticketNums--);
}else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
5、线程通信问题
5.1、生产者消费者模式
wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程优先级相同,则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
注意点:
- 此三个方法的使用,必须是在同步代码块或同步方法中(超纲:Lock需要配合Condition实现线程间的通信)
- 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateException
- 此三个方法声明在Object类中
5.2、管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor01(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor01 extends Thread{
SynContainer synContainer;
public Productor01(SynContainer synContainer) {
this.synContainer = synContainer;
}
//生产
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
synContainer.push(new Products(i));
System.out.println("生产了"+i+"个产品");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+synContainer.pop().id+"个产品");
}
}
}
//产品
class Products{
int 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();
}
//消费者消费产品
public synchronized Products pop(){
//判断能否消费
if (count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Products product=products[count];
//消费完了,通知生产者生产
this.notifyAll();
return product;
}
}
5.3、信号灯法
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++) {
this.tv.watch();
}
}
}
//产品---节目
class TV{
//演员表演,观众等待
//观众观看,演员等待
String voice;//表演的节目
boolean isflag=true;
//表演
public synchronized void play(String voice){
if (!isflag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice=voice;
this.isflag=!this.isflag;
}
//观看
public synchronized void watch(){
if (isflag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+voice);
//通知演员表演
this.notifyAll();
this.isflag=!this.isflag;
}
}
5.4、线程池
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());
}
}