java多线程基础

1.线程的相关概念

进程: 进程就是一个程序或者服务运行的过程在操作系统中的体现,即操作系统中一个独立运行的程序或者服务就是一个进程。

多进程: 现在的操作系统都支持同时运行多个程序或者服务,体现在进程上,就是多个进程可以并行的执行,这称之为操作系统支持多进程。

线程: 一个进程的内部还可以划分出多个并行执行的过程,称之为在进程内部存在线程,线程是操作系统能够进行运算调度的最小单位。

多线程: 一个进程可以存在多个线程,并且这些线程可以并行的执行,这样的机制称之为进程支持多线程。

2.java创建线程的方式

方式1: 创建一个类继承Thread类,重写其中的run()方法,在run()方法中编写你要执行的逻辑代码,然后创建该类的对象并调用该对象的start()方法,线程就启动了。

public class ThreadDemo{
	public static void main(String[] args){
		MyThread thread = new MyThread();
		thread.start();
	}
}
class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("吃饭。。。");
		}
	}		
}

方式2: 创建一个类实现Runnable接口,实现其中的run()方法,在run()方法中编写你要执行的逻辑代码,然后创建该类的对象并传入Thread类的构造方法,创建Thread对象,最后调用Thread对象的start()方法,线程就启动了。

public class ThreadDemo{
	public static void main(String[] args){
		Thread thread = new Thread(new MyThread());
		thread.start();
	}
}
class MyThread implements Runnable{
	@Override
	public void run(){
		while(true){
			System.out.println("吃饭。。。");
		}
	}		
}

方式3: 实现Callable接口,新建当前类对象,在新建FutureTask类对象时传入当前类对象,接着新建Thread类对象时传入FutureTask类对象,最后运行Thread对象的start()方法。(这种方式我基本没用过,所以不做过多解释)

前两种方式的比较:
java是单继承的,继承方式创建线程将会占用extends关键字,这在类本身需要继承其他类的情况下无法使用;
java是多实现的,实现接口的数量没有限制,接口方式创建线程不会受到单继承的限制。

3.线程的一些细节

主线程(main)和其他线程比起来,唯一特殊的地方就是它是程序的入口,除此之外并无任何差别;
多线程并发执行的过程中,线程在不断的无序的抢夺cpu,某一时刻,哪个线程先抢到,哪个线程就先执行,由于cpu执行速度非常快,看起来这些线程都在并发执行;
线程是在进程内部执行的,进程内部只要有任一非守护线程存活,进程就不会结束。

4.线程的关闭

调用线程对象的stop()方法可以显示的关闭该线程,但此方法具有相当大的不安全性, 所以目前已经过时废弃掉了,不建议大家使用。
jdk在废弃掉stop()方法后,并没有提供类似的方法来关闭线程,官方给出的建议是应该由程序本身提供相应的开关,来控制线程的运行和停止。我们通常用一个静态的布尔类型来作为这个开关。

public class ThreadDemo{
	public static void main(String[] args){
		Thread thread = new Thread(new MyThread());
		thread.start();
		Thread.sleep(3000);
		MyThread.canRan = false;
	}
}
class MyThread implements Runnable{
	@Override
	public void run(){
		public static boolean canRan = ture;
		while(canRan){
			System.out.println("吃饭。。。");
		}   
	}		
}   

5.java多线程并发安全问题

5.1多线程并发安全问题概述
多线程环境下,多个线程是并发执行的,并且由于无序的cpu争夺机制,线程的执行顺序是不确定的,此时如果多个线程同时操作共享资源,就有可能因为线程的无序执行,产生一些意外的情况,这些情况统称为多线程并发安全问题。

5.2多线程并发安全产生的条件
有共享资源;
有多线程并发操作了共享资源;
有多线程并发操作了共享资源且涉及到了修改操作。

5.3解决多线程并发安全问题
解决多线程并发安全问题的关键,就是破坏产生多线程并发安全问题的条件
禁止共享资源—ThreadLocal
禁止多线程并发操作—Synchronized锁(同步代码块)
禁止修改—ReadWriteLock

5.4Synchronized同步代码块的使用
原理:
通过控制并发线程无法同时操作共享资源,从而实现多线程并发安全问题的解决。

细节:
在存在多线程并发安全问题的场景下,可以使用Synchronized代码块,将产生多线程并发安全问题的代码包裹起来,并选择一个锁对象。锁对象可以任意选择,但是要保证多个并发的线程操作的都是同一个锁对象。锁对象并没有实际的作用,但是锁对象身上会有一个锁状态的标记(开锁状态与关锁状态),在线程执行到同步代码块时,会在当前锁对象上检查,如果当前锁对象是开锁状态,则进入同步代码块,同时将锁状态改为关锁状态,此时再有其他线程试图进入同步代码块时,检查到锁对象是关锁状态,无法得到锁,无法进入,只能等待,进入阻塞状态,直到之前进入的线程执行完逻辑代码,释放锁,重新将锁对象改为开锁状态,等待的线程才可以从阻塞状态中恢复,重新参与锁的争夺,从而就保证了造成线程并发安全问题的代码同一时刻只能有一个并发线程执行,破坏了多线程并发操作这一条件,从而解决了多线程并发安全问题。

用法:
Synchronized(锁对象){
要同步的代码
}

常用的锁对象:
创建一个专用的对象作为锁对象;
使用共享资源作为锁对象;
使用类的字节码对象作为锁对象。

6.死锁

6.1死锁概述
死锁是一种并发锁定的特殊状态,指的是当具有多个共享资源时,一部分线程持有一部分资源的锁而要求另外的线程持有另外的资源的锁,形成了各自持有各自的锁而要求对方的锁的状态,这样进入了一个互相等待的状态,谁都无法继续执行,这种情况称之为死锁。
死锁并不是一种真正的锁,而是一种特殊状态,会造成程序无法继续执行,所以要尽力的解决死锁。

6.2死锁产生的条件
多把锁;
多个线程;
同步嵌套(在Synchronized代码块中包含Synchronized代码块,这就意味着,占用一部分锁,再要求另一部分锁)。

6.3解决死锁
解决死锁有两种方式:

避免死锁: 理论上破坏死锁产生的条件就可以避免死锁,但是前两个条件难以破坏,所以避免死锁就是避免同步嵌套的产生。

检测并打断死锁: 有时无法进行避免死锁的操作,此时只能不停地检测是否有死锁的产生,如果有死锁产生,则打断死锁,打断死锁就是将造成死锁的某一线程错误退出,打断对锁互相要求的环,从而使程序可以正常运行下去。

7.线程间的通信

7.1线程通信概述
在程序运行过程中,线程是相对独立的单位,多个线程之间并行的执行,并不会有太多的沟通,每个线程都有属于自己的内存空间且无法互相访问,所以可以认为多个线程之间是隔离的状态,并没有过多的信息传递。然而线程在并发运行的过程中,还会无序的抢夺cpu,造成执行的顺序不确定,使执行的结果不可预期。所以有时候我们希望能够实现多个线程之间进行信息的传递或者执行过程的协调,这样的技术称之为线程间的通信技术。

线程间的通信技术主要有两种:
共享内存机制—解决多个线程之间信息的传递
等待唤醒机制—解决多个线程执行过程的协调

7.2共享内存机制
每个线程都有各自的内存空间,并且无法互相访问,当多个线程需要进行信息的共享时,可以在多个线程都可以看到的公共内存中保存数据,从而实现两者的通信。例如一个线程通过一个静态布尔变量来控制另一个线程的启停。

7.3等待唤醒机制
多个线程在并发的过程中,互相抢夺cpu,造成执行的顺序是不确定的,如果需要控制线程的执行顺序,可以采用等待唤醒机制。
在锁对象身上,具有等待、唤醒的方法,这些方法其实是定义在Object类上的。

wait():
在Synchronized代码块中,调用锁对象的wait()方法,将会使当前线程进入阻塞状态,释放锁,同时不再争夺cpu,阻塞状态会一直持续下去,直到被其他线程调用锁对象身上的notify()或者notifyAll()方法唤醒。

notify():
在Synchronized代码块中,调用锁对象的notify()方法,将会唤醒之前在这个锁对象上进入wait()状态的一个线程,使其退出阻塞状态,恢复cpu的争夺,但是仍然需要得到锁才可以继续运行。唤醒的线程是不确定的。

notifyAll():
在Synchronized代码块中,调用锁对象的notifyAll()方法,将会唤醒之前在这个锁对象上进入wait()状态的所有线程,使这些线程退出阻塞状态,恢复cpu的争夺,但是仍然需要得到锁才可以继续运行。

8.线程之间的状态转换

在这里插入图片描述

9.线程的其他方法

setDaemon(boolean on):
线程从是否可以独立运行的角度,可以分为用户线程和守护线程,通常我们创建出来的线程都是用户线程,但可以通过setDaemon(true)将用户线程转换为守护线程。用户线程可以独立运行,守护线程不可以独立运行,也就是说如果进程中已经没有用户线程,只剩下守护线程,则守护线程会自动退出,进程结束。

join():
在当前线程执行时可以调用另一个线程的jion()方法,则当前线程立即进入阻塞模式,直到被join进来的线程执行完成为止。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值