创建线程的第一种方式
步骤:1,创建一个线程子类继承Thread类
2,在创建的子类中重写run方法
3,实例化子类对象,并调用start()方法
为什么要创建一个新的线程子类并继承Thread类?
直接使用Thread去创建线程其实也是可以的,但是Thread类是java语言在设计时就已经存在的类了,类中相关功能都已经书写好了,而我们使用多线程想要执行的是自己书写的任务代码(而Thread类中任务代码却已经固定写好了,不能使用Thread中的代码),只能创建自己的线程类并继承Thread类重写run方法
为什么继承了Thread类后,一定要重写run方法?
当线程开启之后(调用start方法),JVM就会自动的把run方法加载到新开辟的运行通道中执行(java语言底层已经把JVM自动调用任务代码的功能固定死了)
为什么不直接去调用run方法,而是调用start方法?
start()方法是用来开辟一个新的线程通道。
如果没有执行start方法而是直接去执行run()方法,进程中就没有新的运行通道,还是单线程在执行
继承Thread类的弊端:
1、类和类之间只能单一继承,当继承Thread类后就不能再继承其它的类了(单继承)
2、Thread类属于线程类,按照面向对象的方式来讲,线程类就应该是操作线程功能的,但是Thread类除了可以操作线程的相关功能外,还可以执行任务代码。这种情况会让Thread类的耦合度变高(耦合高问题)
解决方案:不论是解决单一问题,还是解决耦合高的问题,在java中都是使用接口(Runnable)
创建线程第二种方式的步骤:
1,创建一个实现Runnable接口的子类
2,子类中重写Runnable接口中的run方法
3,实例化子类对象,并把子类对象作为参数传递给Thread对象
4,调用Thread对象中的start()方法
Runnable接口和Thread类的区别:
Thread类属于线程类,可以用来直接操作多线程。
Runnable接口只把任务代码单独的抽离出来单独实现,用来描述任务功能。想要执行任务代码,必须借助Thread类或其子类
线程默认命名格式:Thread-x x是一个数字,自动增长。从0开始
注:主线程的名称是:main
多线程安全问题的产生:多个线程同时都操作相同的一个数据,造成数据的不安全性
解决多线程安全问题的方案:在操作共享数据时,添加同步代码块
同步的关键字:synchronized
同步代码块的格式:
synchronized(任意对象) //任意对象也称为对象锁
{
//线程要操作的共享数据代码
}
同步代码块使用注意事项:
同步代码块是添加在任务代码中操作共享数据的位置上;
检查对象锁是否使用的是同一个对象锁;
同步方法格式(在方法上添加一个同步关键字)
修饰符 synchronized 返回值类型 函数名(参数列表)
{
}
注:同步方法分为静态同步方法和非静态同步方法
小结:当同步代码块想要和非静态同步方法保持共享数据一致(不出现线程安全问题),需要在同步代码块中使用同步方法上的锁:this对象
单例模式(原子模式、单态模式):
解决的问题:保证所创建出来的对象是唯一的。
实现步骤:1、私有化构造方法;
2、在类的内部书写属于自己的本类对象
3、对外提供一个方法,可以让其它程序访问属于本类的对象
小结:同步代码块想要和静态同步方法使用同一个对象锁,需要使用:类名.class
多个线程存储同一个相同元素或删除同一个相同元素
解决方案:使用Collections工具类中的方法
同步的好处:解决了线程不安全问题(保证多个线程在操作共享数据不会出现不安全数据)
同步的弊端:降低程序的执行效率(执行同步时每次都要判断是否有对象锁存在)
生产者消费者思路:
生产者在生产完商品后,通知消费者可以来消费了。当生产者生产完商品后,就要处于等待状态(等待消费者消费完商品后,通知生产者继续生产)
消费者在消费完商品后,通知生产者可以继续生产商品了。当消费者消费完商品后,也要处于等待状态(等待生产者生产完商品后,通知消费者可以继续消费)
对象锁
任意对象锁都是隐式的获取锁,释放锁;
任意对象锁都具有等待唤醒机制;
一个线程只能绑定一个对象锁;
常用方法格式
Object lock=new Object();创建对象锁
synchronized(lock){ }
lock.notifyAll();唤醒处于等待状态的所有线程
lock.notify(); 唤醒处于等待状态的线程
lock.wait(); 让xxx线程处于等待状态
注:wait()和notify()是属于对象锁的
Lock锁
至JDK1.5后,lock锁把等待唤醒机制从对象锁上分开,1个lock锁可以具有多个等待唤醒机制;
多个线程可以绑定一个Lock锁;
获取锁、释放锁使用格式
Lock lock=new ReentrantLock();//创建lock锁
lock.lock() 获取锁;
lock.unlock() 释放锁;
Lock锁比同步方法或同步代码块更灵活
注意:在使用Lock锁对象时,需要手动的书写代码用来:获取锁、释放锁(在同步代码块中不需要手动的书写获取锁、释放锁的代码。但是,同步代码块在执行时底层还是会获取锁、释放锁)
Condition(等待唤醒机制)
Condition中使用await/signal/signalAll固定格式
Lock lock=new ReentrantLock();//创建一个Lock锁
Condition xxxx=lock.newCondition();//xxx线程绑定的等待唤醒机制
xxxx.await() 让xxx线程等待;
xxxx.signal() 唤醒一个等待线程;
xxxx.signailAll()唤醒所有等待线程;
Lock锁代替了synchronized同步代码块的书写
Condition代替了同步代码块中锁对象上的等待唤醒
多线程细节
wait()和sleep()方法的区别:
sleep方法属于线程的;
sleep()方法中必须指定毫秒值;
sleep()只是让运行的线程暂时的等待,当时间一到,就自动转为运行状态或阻塞状态
wait()会让运行的线程一直处于等待状态,只有唤醒后,才会转为运行状态或阻塞状态
wait()在程序中只能书写在同步代码块中;sleep()可以书写在同步代码块中也可以书写在外部
wait()方法中可以指定毫秒值也可以不指定;
wait()属于对象锁的;
在创建线程时,每一个线程其实都具备了一个执行优先级别。默认的优先级别为:5
线程的优先级别范围是:1~10
在开发中,通常对于线程的优先级别会固守设置为:1、5、10;
提示:当线程的优先级别之间数值间距比较小时,对CPU而言就感觉不到优先级别的差异
线程优先级
max_priority 最高优先级
min_priority 最低优先级
norm_priority 默认优先级
获取/设置线程优先级
getPriority() 获取线程优先级
setPriority() 设置线程优先级
守护线程
普通线程改为守护线程书写格式:
ThreadDaemon t2=new ThreadDaemon();
t2.setName(“xxxx”)//给守护线程对象取名
t2.setDaemon(true) //将该线程标记为守护线程,true为守护线程
守护线程是随着普通线程的运行而运行,当普通线程结束,守护线程也会随着结束
注:守护线程必须与普通线程同时创建存在
|
|