一、进程与线程
进程:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序(应用)就是一个进程。
线程:当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程。
注:一个进程是可以包含一个或多个线程的集合
二、进程与线程的特点
进程的特点:
1.处于运行过程中的程序
2.系统调度的最小单元
3.分配独立的内存空间
4.资源保护要求高,开销大,效率相对较低
线程的特点:
1.线程共享整个进程的资源
2.CPU调度的最小单元
3.与同属一个进程的其它的线程共享进程(内存)的全部资源
4.占用资源少,便于通信
三、线程的三种创建方式
1.方式一:
- 继承Thread类
- 重写run方法
- 创建Thread子类对象
- 调用start方法
public class MyThread1 extends Thread {
//线程要执行的任务
@Override
public void run() {
for (int i=1;i<=1000;i++){
System.out.println(i);
}
}
//main方法在执行的时候会创建一个线程,叫做主线程
public static void main(String[] args) {
MyThread1 mt=new MyThread1();
//开启线程一定要调用start方法,而不是调用run
mt.start();
MyThread1 mt2=new MyThread1();
mt2.start();
}
}
2.方式二:
- 实现Runnable接口
- 重写run方法
- 创建实现类对象
- 创建Thread对象,Runnable对象作为构造方法的实参
- 调用start方法
//继承Thread类的类叫做线程类
//实现Runnable接口的类,叫做任务类,这个类不具备创建线程的能力
public class MyThread2 implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println(i);
}
}
public static void main(String[] args) {
//m1是任务对象,任务对象想要在多线程里执行,还需一个线程对象
MyThread2 m1=new MyThread2();
//让m1在t1里执行
Thread t1=new Thread(m1);
t1.start();
}
}
3.方式三:
- 实现Callable接口
- 重写call方法
- 创建Callable子类的实例化对象
- 创建FutureTask对象,Callable对象作为构造方法的实参
- 创建Thread对象,FutureTask对象作为构造方法实参
- 调用start方法
public class MyThread3 implements Callable {
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <=100 ; i++) {
sum=sum+i;
}
return sum;
}
public static void main(String[] args) throws Exception{
MyThread3 m1=new MyThread3();
ExecutorService es = Executors.newSingleThreadExecutor();
Future res = es.submit(m1);
System.out.println(res.get());
es.shutdown();
}
}
start方法:启动一个线程,这时此线程处于并没有真正运行,一旦得到cpu时间片,执行run方法,这里的run方法称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
(1)在Thread类中
(2)线程会被添加到线程组(ThreadGroup)中,等待线程调度器调用,当获取到资源时,就进入运行状态
(3)同一个线程只能start一次,多次调用start方法会抛出异常
(4)Java虚拟机调用该线程的run方法
run方法:线程要执行的任务需要写在run方法中
(1)在Thread类中
(2)如果线程重写了run方法,则调用该Runnable对象的run方法;否则,该方法不执行任何操作
(3)重写方法的权限修饰符必须使用public
(4)手动调用run方法,其只是Thread类中的一个普通方法调用,还是在主线程里执行
四、两种创建线程方式的对比
1.继承Thread类方式
- 一个类只能继承一个父类,存在局限
- 不能用一个实例建立多个线程
- 能共享Thread类的static变量
2.实现Runnable接口方式
- 一个类可以实现多个接口,扩展性较好
- 多个线程共享同一个Runnable实例中的成员变量与静态变量
五、常用方法
1.currentThread()方法
- Thread类的静态方法
- 返回当前正在执行的线程对象的引用
2.getName()
- Thread类的成员方法
- 返回该线程的名称
3.getId()
- Thread类的成员方法
- 返回该线程的Id
4.isAlive:线程是否处于活动状态
5.sleep:指定的毫秒数内让当前正在执行的线程休眠
- 中断后会抛出InterruptedException异常
6.interrupt:中断线程
7.stop:终止线程(不建议使用)
- 标识变量结束线程
8.join:等待该线程终止(类似方法调用)
9.yield:线程让步,暂停当前正在执行的线程对象
- setPriority:设置优先级
- getPriority:获得优先级
- 默认优先级是5,最小是1,最大是10
六、状态转换
1.新建、就绪、运行、死亡
2.新建、就绪、运行、就绪、运行、死亡
3.新建、就绪、运行、其他阻塞、就绪、运行、死亡
4.新建、就绪、运行、同步阻塞、就绪、运行、死亡
5.新建、就绪、运行、等待阻塞、同步阻塞、就绪、运行、死亡
七、线程同步与异步
1.synchronized关键字
格式:synchronized (this){
代码块
……}
权限修饰符[static] synchronized 数据类型 方法名(){……}
修饰的内容(上锁的范围):
- 代码块
- 成员方法
- 静态方法
- 类
public class CountTest implements Runnable{
private int count;
Object o=new Object();
public synchronized void test(){
count++;
}
public static synchronized void test2(){
}
@Override
public void run() {
/* for (int i = 0; i < 1000; i++) {
//synchronized (this)锁的是对象,当多个线程使用同一个任务对象的时候,才会受到锁的限制
//如果多个线程使用的是不同的任务对象,则不受锁的影响
synchronized (this){
//使用synchronized对这行代码加锁,同一时刻,只有一个线程可以执行这行代码
count++;
}
}*/
for (int i = 0; i < 1000; i++) {
//小括号可以写任意一种对象,写不同对象的时候,锁的范围也不一样
synchronized (o){
}
}
}
public static void main(String[] args) throws Exception{
CountTest c=new CountTest();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(c.count);
}
}
synchronized修饰代码块:
锁的范围:被修饰的代码块
(1)synchronized(this):
- 锁的对象:调用代码块的对象
(2)synchronized(“字符串”):
- 锁的对象:字符串对象
- StringBuffer和StringBuilder的toString方法会创建新的对象
- 可以调用intern()方法保证内容相同的字符串是同一对象
intern方法:如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。否则,此String对象将添加到池中,并返回对此String对象的引用。
synchronized修饰成员方法:
锁的范围:整个方法
锁的对象:调用成员方法的对象
等价形式:
public synchronized void method()
public void method(){ synchronized(this){…… } }
当一个线程访问对象的一个synchronized同步代码块时,另一个线程仍然可以访问该对象中的非synchronized同步代码块。
synchronized关键字不能被继承
- synchronized并不属于方法定义的一部分
- 子类重写父类中用synchronized修饰的被重写方法,无法自动获得同步
抽象方法不能使用synchronized关键字
构造方法不能使用synchronized关键字,但可以使用
synchronized代码块来进行同步
synchronized修饰类
格式:synchronized(MyClass.class){
……
}
锁的范围:代码块
锁的对象:此类中的所有对象
2.Lock类
(1)Lock类型的成员变量
锁的范围:调用lock()方法开始,调用unlock()方法结束
锁的粒度:此类的当前对象
(2)Lock类型的静态变量
锁的范围:调用lock()方法开始,调用unlock()方法结束
锁的粒度:此类中的所有对象
(3)tryLock(long time,TimeUnit unit)方法
表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其它线程获取),则返回false
参数:
- time尝试的时间
- unit时间的单位
说明:低版本中Lock类性能更高,使用CAS实现,JDK1.8之后synchronized两者差异不大
八、锁的分类
1.公平锁与非公平锁:在多个线程获取锁的时候,是否排队,公平锁要排队,非公平锁不需要排队,公平锁可以保证都获取到锁,非公平锁的效率更高
synchronized是非公平锁,lock可以使用公平锁
2.乐观锁与悲观锁:悲观锁认为一定会产生线程安全的问题,必须加锁才能解决问题;乐观锁认为不会产生线程安全问题,出了问题后才会加锁。
public class LockTest implements Runnable{
//构造方法,true公平锁,false非公平锁
private Lock lock=new ReentrantLock(true);
public void test1(){
//调用lock方法进行加锁
lock.lock();
//执行受保护的代码
try {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"获取到了锁");
Thread.sleep(3000);
}catch (Exception e) {
e.printStackTrace();
}finally {
//执行完一定要记得释放锁,最好放在finally里,保证他一定会执行
lock.unlock();
}
}
public void test2(){
//尝试获取锁,如果三秒没有获取到锁,就不等了
try {
boolean tryLock = lock.tryLock(3, TimeUnit.SECONDS);
if (!tryLock){
System.out.println("人太多了,不等了");
return;
}
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"获取到了锁");
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
@Override
public void run() {
test2();
}
public static void main(String[] args) {
LockTest task=new LockTest();
Thread t1=new Thread(task);
Thread t2=new Thread(task);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
3.死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止
产生死锁的条件(需要同时满足以下条件)
互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
请求和保持,即当资源请求者在请求其它的资源的同时保持对原有资源的占有
循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
public class DeadLock {
private Object a=new Object();
private Object b=new Object();
public void sleep(int time){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//a线程调用test1
public void test1(){
synchronized (a){
System.out.println("a线程获取到了a锁");
sleep(10);
System.out.println("a线程尝试获取b锁");
synchronized (b){
}
}
}
//b线程调用test2
public void test2(){
synchronized (b){
System.out.println("b线程获取到了b锁");
sleep(10);
System.out.println("b线程尝试获取a锁");
synchronized (a){
}
}
}
public static void main(String[] args) {
DeadLock d=new DeadLock();
//匿名类对象
Runnable r1=new Runnable() {
@Override
public void run() {
d.test1();
}
};
//当接口里只有一个抽象方法时,可以用lambda的方式来实现
Runnable r2=()->{
d.test2();
};
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
t1.start();
t2.start();
}
}
public class WaitTest extends Thread{
private Object o=new Object();
@Override
public void run() {
System.out.println("线程开始了");
try {
synchronized (o){
//wait方法是Object类提供的,必须先获取到锁,才可以调用wait
//让当前线程进入到等待状态,必须有其他线程来唤醒它,否则会一直等
//sleep不会释放锁,wait会释放锁
o.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
WaitTest w=new WaitTest();
w.start();
}
}
public class NotifyTest {
private Object o=new Object();
public void test1(){
synchronized (o){
try {
System.out.println("a线程进入到等待状态");
o.wait();
System.out.println("a线程被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void test2(){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
System.out.println("b线程开始执行了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒a线程的等待状态
o.notify();
}
}
public static void main(String[] args) {
NotifyTest n=new NotifyTest();
//当lambda表达式的大括号里只有一行代码的时候,可以省略大括号
Runnable r1=()->n.test1();
Runnable r2=()->n.test2();
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
t1.start();
t2.start();
}
}
线程进入死亡(终止)状态,但是该线程对象仍然是一个Thread对象,在没有被垃圾回收器回收之前仍可以像其它对象一样引用它
线程调用了sleep()方法后,该线程将进入阻塞状态
notify()方法的作用是唤醒线程
内存回收程序负责释放无用内存
九、练习题
通过加锁与释放锁更好的了解先后顺序
public class CookTest {
/*
实现以下逻辑:
1. a去买菜
2. b等待a买菜,a买回来之后,b来洗菜
3. b洗完菜之后,a来炒菜
4. a炒完菜之后,b来端菜
*/
//用a和b两个对象来控制两个线程的等待状态
private Object a=new Object();
private Object b=new Object();
public void test1(){
try{
Thread.sleep(5);
System.out.println("a去买菜");
synchronized (b){
System.out.println("a买菜回来了,通知b去洗菜");
b.notify();
}
synchronized (a){
a.wait();
}
System.out.println("a开始炒菜");
synchronized (b){
System.out.println("a炒完菜,通知b端菜");
b.notify();
}
}catch (Exception e){
e.printStackTrace();
}
}
public void test2(){
try{
synchronized (b){
b.wait();
}
System.out.println("b去洗菜");
synchronized (a){
System.out.println("b洗完菜了,通知a去炒菜");
a.notify();
}
synchronized (b){
b.wait();
}
System.out.println("b把菜端到了桌上");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
CookTest n=new CookTest();
Runnable r1=()->n.test1();
Runnable r2=()->n.test2();
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
t1.start();
t2.start();
}
}
十、线程池
public class PoolTest1 {
//线程池
public static void main(String[] args) {
//创建一个单线程的线程池,里面只有一条线程
//线程池处理任务的逻辑是我们把任务交给线程池,由线程池来给任务分配线程,执行任务
//应用场景:任务如果需要按照提交顺序来执行,可以使用这个线程池
ExecutorService es = Executors.newSingleThreadExecutor();
Runnable r1=()-> System.out.println("r1");
Runnable r2=()-> System.out.println("r2");
es.submit(r1);
es.submit(r2);
//关闭线程池
es.shutdown();
}
}
public class PoolTest2 {
public static void main(String[] args) {
//创建一个定长线程池,里面线程的数量由参数来控制
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
final int j=i;
Runnable r=()->{
System.out.println("正在执行第"+j+"个任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
es.submit(r);
}
es.shutdown();
}
}
public class PoolTest3 {
public static void main(String[] args) {
//可缓存的线程池,线程数不够就会创建,空闲了就会删除,适合在一些任务数不会突发特别高的情况下
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
final int j=i;
Runnable r=()->{
System.out.println("正在执行第"+j+"个任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
es.submit(r);
}
es.shutdown();
}
}
public class PoolTest4 {
public static void main(String[] args) {
//定时任务线程池,可以周期性执行任务
ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
Runnable r=()-> System.out.println("hello");
//第一个参数是任务对象,第二个参数是初始的延迟时间,第三个参数是频率时间,第四个参数是时间单位
es.scheduleAtFixedRate(r,1,2,TimeUnit.SECONDS);
}
}
public class PoolTest5 {
public static void main(String[] args) {
//手动创建一个线程池对象
//第一个参数:核心线程数
//第二个参数:最大线程数,线程池最多能拥有的线程数量
//第三个和第四个参数:线程空闲存活时间和时间单位
//第五个参数:任务的等待队列
ExecutorService es=new ThreadPoolExecutor(5,15,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));
}
}