目录
Thread(Runnable target, String name)
Thread
改进后的电影院售票出现问题
- 问题
相同的票出现多次
CPU的一次操作必须是原子性的
还出现了负数的票
随机性和延迟导致的
- 注意
线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
Thread(Runnable target, String name)
查看API文档我们知道:
public Thread(Runnable target,String name)分配一个新的
Thread
对象。 此构造具有相同的效果Thread(null, target, name)
。参数
target
- 启动此线程时调用其run
方法的对象。 如果null
,则调用此线程的run方法。
name
- 新线程的名称
改进电影院售票1
创建TicketWindow1类:
public class TicketWindow1 implements Runnable{
//定义100张票
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if (tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
创建SellTicketDemo1实现类:
public class SellTicketDemo1 {
public static void main(String[] args) {
TicketWindow1 ticketWindow1 = new TicketWindow1();
//创建线程对象模拟3个窗口,并给线程起名字
Thread t1 = new Thread(ticketWindow1, "窗口1");
Thread t2 = new Thread(ticketWindow1, "窗口2");
Thread t3 = new Thread(ticketWindow1, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
由于博主的CPU太过强大,多次尝试后这里没有出现预想的结果。
解决线程安全问题的基本思想
首先想为什么出现问题?(也是我们判断是否有问题的标准)
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
- 怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
同步代码块
格式:
synchronized(对象){需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
同步代码块的锁对象是谁呢?
任意对象,但是多个线程之间锁对象要一样
同步方法的锁对象是谁呢?
将synchronized关键字放到方法上 同步方法的锁对象是this
静态方法的锁对象是谁呢? class文件,字节码文件对象也是属于一个Object类下面的对象,这个class文件不能是随便一个类的字节码文件应该是run方法所在类的字节码文件
解决线程同步安全问题的第一种方法
改进电影院售票2
创建TicketWindow2类:
public class TicketWindow2 implements Runnable {
//定义1000张票
private static int tickets = 1000;
int i = 1;
@Override
public void run() {
while (true){
if (i%2==0){
synchronized (TicketWindow2.class){
if (tickets > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}else {
sellTicket();
}
}
}
private synchronized static void sellTicket() {
if (tickets > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
创建SellTicketDemo2实现类:
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
Thread t1 = new Thread(ticketWindow2, "窗口1");
Thread t2 = new Thread(ticketWindow2, "窗口2");
Thread t3 = new Thread(ticketWindow2, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
从运行结果上来看我们实现了线程的安全。
解决线程同步安全问题的第二种解法:加Lock锁
Class ReentrantLock
查看API文档我们知道:
public class ReentrantLock extends Object implements Lock, Serializable一个可重入互斥
Lock
具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。A
ReentrantLock
由线程拥有 ,最后成功锁定,但尚未解锁。 调用lock
的线程将返回,成功获取锁,当锁不是由另一个线程拥有。 如果当前线程已经拥有该锁,该方法将立即返回。 这可以使用方法isHeldByCurrentThread()
和getHoldCount()
进行检查。该类的构造函数接受可选的公平参数。 当设置
true
,在争用下,锁有利于授予访问最长等待的线程。 否则,该锁不保证任何特定的访问顺序。 使用许多线程访问的公平锁的程序可能会比使用默认设置的整体吞吐量(即,更慢,通常要慢得多),但是具有更小的差异来获得锁定并保证缺乏饥饿。 但是请注意,锁的公平性不能保证线程调度的公平性。 因此,使用公平锁的许多线程之一可以连续获得多次,而其他活动线程不进行而不是当前持有锁。 另请注意, 未定义的tryLock()
方法不符合公平性设置。 如果锁可用,即使其他线程正在等待,它也会成功。建议的做法是始终立即跟随
lock
与try
块的通话,最常见的是在之前/之后的建设,如:class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
除了实现
Lock
接口,这个类定义了许多public
种protected
方法用于检查锁的状态。 其中一些方法仅适用于仪器和监控。此类的序列化与内置锁的操作方式相同:反序列化锁处于未锁定状态,无论其序列化时的状态如何。
此锁最多支持同一个线程的2147483647递归锁。 尝试超过此限制会导致
Error
从锁定方法中抛出。
void lock() 加锁
查看API文档我们知道:
public void lock()获得锁。
如果锁没有被另一个线程占用并且立即返回,则将锁定计数设置为1。
如果当前线程已经保持锁定,则保持计数增加1,该方法立即返回。
如果锁被另一个线程保持,则当前线程将被禁用以进行线程调度,并且在锁定已被获取之前处于休眠状态,此时锁定保持计数被设置为1。
Specified by:
void unlock() 释放锁
查看API文档我们知道:
public void unlock()尝试释放此锁。
如果当前线程是该锁的持有者,则保持计数递减。 如果保持计数现在为零,则锁定被释放。 如果当前线程不是该锁的持有者,则抛出
IllegalMonitorStateException
。Specified by:
异常
IllegalMonitorStateException
- 如果当前线程不持有此锁
在此之前我们解决线程同步安全问题的时候,使用的是synchronized关键字,通过分析然后将哪些代码块给包起来,但是我们并没有直接看到在哪里上了锁,或者说在哪里释放了锁让其它线程获取到,为了更加清晰的表达如何加锁以及如何释放锁,JDK1.5之后提供了一个新的锁对象Lock。
改进电影院售票3
创建TicketWindow3类:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketWindow3 implements Runnable {
//定义100张票
private int tickets = 100;
//创建锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售出第" + (tickets--) + "张票");
}
lock.unlock();
}
}
}
创建SellTicketDemo3实现类:
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow3 = new TicketWindow3();
Thread t1 = new Thread(ticketWindow3, "窗口1");
Thread t2 = new Thread(ticketWindow3, "窗口2");
Thread t3 = new Thread(ticketWindow3, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
线程同步案例:共享数据案例(线程通信)
创建Student类:
public class Student {
String name;
int age;
boolean flag;
}
创建自定义类GetThread:
import java.util.concurrent.locks.Lock;
public class GetThread implements Runnable {
private Student s;
private Lock lock;
public GetThread(Student student) {
this.s = student;
}
public GetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
@Override
public void run() {
while (true){
synchronized (s){
//判断学生对象有没有值
//如果flag的值是false,说明没有数据,消费者进if判断等待
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//消费者消费完数据后通知生产者生产数据
s.notify();
s.flag = false;
}
}
}
}
创建自定义类SetThread类:
import java.util.concurrent.locks.Lock;
public class SetThread implements Runnable{
private Student s;
private int i = 0;
private Lock lock;
public SetThread(Student student) {
this.s = student;
}
public SetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
@Override
public void run() {
while (true){
synchronized (s){
//判断学生对象有没有值
//flag初始的时候是flase,表示没有值,如果是true表示学生对象有值
//有值对于生产者来说,等待消费者消费
if(s.flag){
try {
s.wait(); //调用wait(),线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
s.name = "刘德华";
s.age = 65;
}else {
s.name = "张学友";
s.age = 73;
}
i++;
//生产者生产完数据之后通知消费者消费数据
s.notify();
s.flag = true;
}
}
}
}
创建实现类:StudentDemo
public class StudentDemo {
public static void main(String[] args) {
//创建共享的学生对象
Student student = new Student();
//创建自定义类对象
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
//根据Runnable的对象创建生产者和消费者的线程对象
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
cusThread.start();
proThread.start();
}
}
运行结果:
分析:
共享数据:Student 生产者:SetThread 给学生的成员变量进行赋值操作 消费者:GetThread 获取学生的成员变量的信息 测试类:StudentDemo 创建线程并测试在写这个案例之前我们将会遇到一些问题可能是以下原因导致的
CPU一点点的时间片就足矣执行很多次
线程执行的时候具有随机性导致的
注意事项: 1、不同种类的线程类都要加锁 2、不用种类的线程类中的锁对象要一样 问题3:虽然我们解决线程安全问题,但是经过分析,程序还存在着等待唤醒机制的问题。 加入等待唤醒机制。 如何添加等待唤醒机制呢? Object类中有三个方法: void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。 void notify() 唤醒正在等待对象监视器的单个线程。 void notifyAll() 唤醒正在等待对象监视器的所有线程。 这三个方法为什么不定义再Thread类中呢? 这些方法想要调用,必须通过锁的对象调用,而我们直到同步代码块的锁对象可以是任意对象 所以这些方法都在Object类中,因为java中所有类的父类都是Object
既然实现了线程的共享,那么如果出现了同步嵌套,就容易产生死锁问题
死锁
死锁是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
参考代码:
创建DieLock类:
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag){
this.flag = flag;
}
/* 现象1:
if lock1
else lock2
现象2:
else lock2
if lock1
*/
@Override
public void run() {
if(flag){
//d1
synchronized (MyLock.lock1){
System.out.println("if lock1");
//d1
synchronized (MyLock.lock2){
System.out.println("if lock2");
}
}
}else {
//d2
synchronized (MyLock.lock2){
System.out.println("else lock2");
//d2
synchronized (MyLock.lock1){
System.out.println("else lock1");
}
}
}
}
}
创建实现类DieLockDemo:
public class DieLockDemo {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
d1.start();
d2.start();
}
}
运行结果:
if lock1
else lock2
else lock2
if lock1
在我们今后的开发中要避免锁的同步嵌套使用,不然就很容易产生死锁问题
线程组
查看API文档我们知道:
public Thread(ThreadGroup group,Runnable target,String name)分配一个新的
Thread
对象,使其具有target
作为其运行对象,具有指定的name
作为其名称,属于group
引用的线程组。如果有安全管理器,则使用ThreadGroup作为参数调用其
checkAccess
方法。此外,它的
checkPermission
方法由RuntimePermission("enableContextClassLoaderOverride")
权限调用,直接或间接地由覆盖getContextClassLoader
或setContextClassLoader
方法的子类的getContextClassLoader
setContextClassLoader
调用。新创建的线程的优先级设置为等于创建线程的优先级,即当前正在运行的线程。 可以使用方法setPriority将优先级改变为新值。
当且仅当创建它的线程当前被标记为守护线程时,新创建的线程才被初始化为守护线程。 方法setDaemon可以用于改变线程是否是守护进程。
参数
group
- 线程组。 如果是null
并且有一个安全管理器,则该组由SecurityManager.getThreadGroup()决定 。 如果没有安全管理员或SecurityManager.getThreadGroup()
返回null
,该组将设置为当前线程的线程组。
target
- 启动此线程时调用其run
方法的对象。 如果null
,则调用此线程的run方法。
name
- 新线程的名称异常
SecurityException
- 如果当前线程不能在指定的线程组中创建线程,或者不能覆盖上下文类加载器方法。
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理, Java允许程序直接对线程组进行控制。
参考代码:
创建MyRunnable类:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
创建实现类ThreadGroupDemo:
public class ThreadGroupDemo {
public static void main(String[] args) {
//创建自定义类对象
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(myRunnable, "刘德华");
Thread t2 = new Thread(myRunnable, "张学友");
//我们之前都没有提过线程组,以及分组的概念,想着应该有一个默认的分组
//获取分组
//ThreadGroup getThreadGroup()
//返回此线程所属的线程组。
ThreadGroup tg1 = t1.getThreadGroup();
System.out.println(tg1);
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg2);
//获取线程组的名字
//String getName()
//返回此线程组的名称。
System.out.println(tg1.getName());
System.out.println(tg2.getName());
System.out.println(Thread.currentThread().getName());
//需求:给线程分组
//Thread(ThreadGroup group, Runnable target, String name)
//分配一个新的 Thread对象,使其具有 target作为其运行对象,
// 具有指定的 name作为其名称,属于 group引用的线程组。
//创建一个新的线程组
//ThreadGroup(String name)
//构造一个新的线程组。
ThreadGroup tg3 = new ThreadGroup("下路三人组");
//创建线程对象
Thread t3 = new Thread(tg3, myRunnable, "荣耀行刑官");
Thread t4 = new Thread(tg3, myRunnable, "魂锁典狱长");
Thread t5 = new Thread(tg3, myRunnable, "蒸汽机器人");
System.out.println(t3.getThreadGroup().getName());
System.out.println(t4.getThreadGroup().getName());
//Java允许程序直接对线程组进行控制
//直接通过组名设置这一组都是守护线程,组里面的线程都是守护线程
tg3.setDaemon(true);
}
}
运行结果:
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=main,maxpri=10]
main
main
main
下路三人组
下路三人组
线程池Executors
newFixedThreadPool
查看API文档我们知道:
public static ExecutorService newFixedThreadPool(int nThreads)创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 在任何时候,最多
nThreads
线程将处于主动处理任务。 如果所有线程处于活动状态时都会提交其他任务,则它们将等待队列中直到线程可用。 如果任何线程由于在关闭之前的执行期间发生故障而终止,则如果需要执行后续任务,则新线程将占用它。 池中的线程将存在,直到它明确地为shutdown
。参数
nThreads
- 池中的线程数结果
新创建的线程池
异常
IllegalArgumentException
- 如果是nThreads <= 0
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时, 更应该考虑使用线程池。 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态, 等待下一个对象来使用。 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池 如何实现线程池的代码呢? 1、创建线程池对象,Executors工厂类下的静态方法 newFixedThreadPool是其中一种线程池 public static ExecutorService newFixedThreadPool(int nThreads) 2、如何往线程池中存放线程?(可以存放哪些线程呢?) 3、在线程池中的线程如何运行呢? 4、我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
参考代码1:
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//Future<?> submit(Runnable task)
//提交一个可运行的任务执行,并返回一个表示该任务的未来。
MyRunnable myRunnable = new MyRunnable();
pool.submit(myRunnable);
pool.submit(myRunnable);
//提交数超过线程池的数量的时候也会执行,只不过是当有空闲线程位置的时候再去执行
//newFixedThreadPool最大一次性可执行线程数量为初始设置的数量
pool.submit(myRunnable);
//我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
//void shutdown()
//启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
pool.shutdown();
System.out.println("======================================");
//RejectedExecutionException
//线程池已经被关闭了,不能再提交任务执行
pool.submit(myRunnable);
}
}
运行结果:
提交数超过线程池的数量的时候也会执行,只不过是当有空闲线程位置的时候再去执行newFixedThreadPool最大一次性可执行线程数量为初始设置的数量
创建线程的第三种方式
callable
查看API文档我们知道:
public static Callable<Object> callable(PrivilegedExceptionAction<?> action)返回一个
Callable
对象,该对象在被调用时运行给定的特权异常操作并返回其结果。参数
action
- 运行的特权异常操作结果
可调用对象
异常
NullPointerException
- 如果动作为空
参考代码2:
创建自定义类MyCallable:
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return null;
}
}
创建实现类ThreadPoolDemo2:
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
MyRunnable myRunnable = new MyRunnable();
MyCallable myCallable = new MyCallable();
//可以执行Runnable对象或者Callable对象代表的线程池
pool.submit(myRunnable);
pool.submit(myCallable);
pool.shutdown();
}
}
运行结果:
匿名内部类方式使用多线程
参考代码:
public class ThreadDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread("靓仔"){
@Override
public void run() {
for (int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
},"刘德华");
t2.start();
}
}
运行结果:
定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。
Timer
查看API文档我们知道:
public class Timer extends Object线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。
对应于每个Timer对象是单个后台线程,用于依次执行所有定时器的所有任务。 计时器任务应该快速完成。 如果一个定时器任务需要花费很多时间来完成,它会“计时”计时器的任务执行线程。 这可能会延迟随后的任务的执行,这些任务在(和)如果违规任务最后完成时,可能会“束起来”并快速执行。
在最后一次对Timer对象的引用后,所有未完成的任务已完成执行,定时器的任务执行线程正常终止(并被收集到垃圾回收)。 但是,这可能需要任意长时间的发生。 默认情况下,任务执行线程不作为守护程序线程运行,因此它能够使应用程序终止。 如果主叫方想要快速终止定时器的任务执行线程,则调用者应该调用定时器的cancel方法。
如果定时器的任务执行线程意外终止,例如,因为它调用了stop方法,那么在计时器上安排任务的任何进一步的尝试将会产生一个IllegalStateException ,就像定时器的cancel方法被调用一样。
这个类是线程安全的:多个线程可以共享一个单独的Timer对象,而不需要外部同步。
此类不提供实时保证:使用Object.wait(long)方法是调度任务。
Java 5.0引入了
java.util.concurrent
软件包,其中一个java.util.concurrent
程序是ScheduledThreadPoolExecutor
,它是用于以给定速率或延迟重复执行任务的线程池。 这实际上是对一个更灵活的替代Timer
/TimerTask
组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask
(只实现Runnable
)。 使用一个线程配置ScheduledThreadPoolExecutor
使其等同于Timer
。实现注意事项:这个类可以扩展到大量并发计划任务(千应该没有问题)。 在内部,它使用二进制堆表示其任务队列,因此计划任务的成本为O(log n),其中n为并发计划任务的数量。
实现注意事项:所有构造函数启动计时器线程。
参考代码1:
public class TimerDemo {
public static void main(String[] args) {
//创建定时器对象
//Timer()
//创建一个新的计时器。
Timer timer = new Timer();
//调度任务执行
//在指定的延迟之后安排指定的任务执行。 定时在未来的某一时刻执行任务
//public void schedule(TimerTask task, long delay)
//自定义子类的形式创建任务
//delay这个类型是毫秒
timer.schedule(new MyTask(timer), 3000);
//public void cancel()终止此计时器,丢弃任何当前计划的任务
//timer.cancel();
// timer.schedule(new MyTask(timer), 3000);
}
}
class MyTask extends TimerTask{
private Timer timer;
public MyTask(Timer timer){
this.timer = timer;
}
@Override
public void run() {
System.out.println("靓仔真帅");
timer.cancel();
}
}
运行结果:
靓仔真帅
参考代码2:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
public static void main(String[] args) {
//创建定时器对象
Timer timer = new Timer();
//void schedule(TimerTask task, long delay, long period)
//在指定的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
//3秒后执行任务,并且每个2秒执行一次任务
timer.schedule(new MyTask2(),3000,2000);
}
}
class MyTask2 extends TimerTask{
@Override
public void run() {
try {
FileReader fr = new FileReader("src\\liangzai.txt");
BufferedReader br = new BufferedReader(fr);
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
通过观察运行结果发现,3秒后输出靓仔真帅!,之后每两秒输出一次靓仔真帅!
到底啦!给靓仔一个关注吧!༼ つ ◕_◕ ༽つ