不提供代码,希望大家动动手指敲敲代码!
三种实现方式:
Thread
Runnable
Callable
作为子线程的类需继承Thread类,重写run()方法。
创建继承了Thread类的对象,使用start()方法启动子线程。
结果:
Main:26
Thread:3
Main:27
Thread:4
Main:28
Thread:5
Thread:6
Main:29
Thread:7
Main:30
Thread:8
Main:31
Thread:9
Thread:10
Thread:11
作为子线程的类需要实现Runnable接口,重写run()方法。
创建实现Runnable接口的对象,再创建Thread对象,需传入参数,类型为Runnable,再使用start()方法启动子线程。
结果:
runnableTest:64
runnableTest:65
runnableTest:66
Main:18
runnableTest:67
Main:19
runnableTest:68
runnableTest:69
runnableTest:70
线程3:获得第2张票
线程2:获得第1张票
线程1:获得第2张票
线程1:获得第3张票
线程2:获得第4张票
线程3:获得第5张票
线程2:获得第7张票
线程3:获得第8张票
线程1:获得第6张票
线程1:获得第10张票
线程3:获得第9张票
在获取票的时候:
当线程2刚获取第一张票,执行total操作后,此时total=2。
此时线程3和线程1同时执行获取操作,当时total=2,所以线程3和线程1都是total=2。
结果:
true
继承Thread类:
实现Runnable接口:
结果:
婚前准备
xx要结婚了
婚后结算
使用lamda表达式前提:是函数式接口
函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。此时他和其他java对象一样,仅仅由Java虚拟机为其分配内存并初始化成员变量值。
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的*start()方法。当线程对象调用start()*方法即启动了线程,*start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()*方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序*(*thread scheduler*)*来调度的。
表示某线程对象被CUP调度器调度,执行线程体。就绪状态和运行状态可以相互切换,切换的原因依旧参照CUP调度器调度了哪一个线程。
即死亡状态,表示线程终止。当线程成功执行完成或线程抛出未捕获的Exception或Error或调用线程的stop方法(易导致死锁,不推荐)
有两个原因会导致线程死亡:
sleep()–单位为毫秒
yield()方法
遇到线程1时,线程1让步。
join() 方法
参数:毫秒单位,0为无限等待
三种方法
调用stop方法强制停止线程
该方法不安全已被Deprecated。
为什么不安全?因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对 象所运行的线程就会立即停止
调用Thread类的interrupt() 方法
interrupt() 方法只是将线程状态置为中断状态而已,它不会中断一个正在运行的线程,此方法只是给线程传递一个中断信号,程序可根据此信号来判断是否需要终止。
当线程中使用了wait(),sleep()以及join()方法导致线程阻塞,则interrupt()会在线程中抛出InterruptException,在catch块中捕获该异常,然后退出,并且将线程的中断状态由true置为false。
当线程中没有wait, sleep, join,调用interrupt只是将线程状态置为interrupt=true
最终结果:终断线程,线程状态从false-->true
最终结果:没有终断线程,线程状态一直都是false
不安全,过时
Suspend()用于挂起线程,Resume()用于继续执行已经挂起的线程。可以使用这两个方法进行线程的同步,和Start()方法有些类似的是:在调用Suspend方法后不会立即的停止,而是执行到一个安全点后挂起。
Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行
public final void setPriority(int newPriority);//设置线程优先级
thread3线程先执行的几率比其他两个高,只能说是几率。
守护线程与普通线程的唯一区别是:当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则不会退出。(以上针对正常退出,调用System.exit则必定会退出)
setDeamon(true):告诉JVM不需要等待它退出
守护线程在没有用户线程可服务时自动离开,在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。典型的守护线程例子是JVM中的系统资源自动回收线程,我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。
Java有两种Thread:“守护线程Daemon”与“用户线程User”。
用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。
守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。setDaemon(boolean on)方法必须在线程启动之前调用,当线程正在运行时调用会产生异常。isDaemon方法将测试该线程是否为守护线程。值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
结果:
线程3拿到8
线程1拿到10
线程2拿到9
线程2拿到7
线程3拿到5
线程1拿到6
线程2拿到4
线程1拿到2
线程3拿到3
线程2拿到1
线程3拿到-1
线程1拿到0
出现-1,不安全
结果:
结婚基金余额为:0
女朋友手里的钱:100
结婚基金余额为:-50
你手里的钱:50
银行存储出现负数,不安全
9959
没有完整的数据,不安全
public synchronized void method(int args){}
缺陷:若将一个大的方法声明为synchronized将会影响效率
synchronized 同步方法,锁的是this
结果:
线程1拿到10
线程1拿到9
线程3拿到8
线程2拿到7
线程2拿到6
线程2拿到5
线程2拿到4
线程3拿到3
线程3拿到2
线程3拿到1
买票正常,安全。
同步块:synchronized(Obj){}
Java反射(拓展)
结婚基金余额为:50
你手里的钱:50
女朋友钱不够,取不了
其他代码不变,取钱正常,安全
结果:
10000
添加数据正常,安全
CopyOnWriteArrayList<>源码实际使用synchronized块实现线程安全
多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
一个简单的死锁类
t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟
而t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟
t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定
t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定
t1、t2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
结果:程序无法停止
线程1占用锁
线程2占用锁
之前已经说道,JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,JAVA提供了Lock类来实现和synchronized一样的功能,并且还提供了Condition来显示线程间通信。
Lock类是Java类来提供的功能,丰富的api使得Lock类的同步功能比synchronized的同步更强大。
Lock lock = new ReentrantLock();
lock.lock
来加锁,用lock.unlock
来释放锁。在两者中间放置需要同步处理的代码。lock.lock
lock.unlock
结果:
线程2-->1
线程3-->2
线程1-->3
生产者消费者问题是一个著名 的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓存区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空大缓冲区中取产品,也不允许生产者向一个已经放满产品的缓存区中再次投入产品。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GyqVtfWi-1596205510891)(D:\笔记\博客\images\java\多线程\834666-20171006132515411-1581950806.jpg)]
解决上述问题:
例子:
结果:生产者最多存储2只,达到2只生产者线程就会沉睡。消费者消费了1或2只就或唤醒生产者线程,当消费者连续消费达到2只,消费者线程就会沉睡,等待生产者唤醒。
存储1只全鸡...
存储2只全鸡...
消费第2只全鸡...
消费第1只全鸡...
存储1只全鸡...
存储2只全鸡...
消费第2只全鸡...
存储2只全鸡...
消费第2只全鸡...
存储2只全鸡...
消费第2只全鸡...
存储2只全鸡...
消费第2只全鸡...
存储2只全鸡...
消费第2只全鸡...
省略...
例子:
结果:
TV播放:快乐大本营播放中...
观众观看了:快乐大本营播放中...
TV播放:抖音记录美好生活...
观众观看了:抖音记录美好生活...
TV播放:快乐大本营播放中...
观众观看了:快乐大本营播放中...
TV播放:抖音记录美好生活...
观众观看了:抖音记录美好生活...
TV播放:快乐大本营播放中...
观众观看了:快乐大本营播放中...