- 下面内容是我根据B站视频总结,这是线程的基础部分的,如果有错误,欢迎指正 *
概念
- 进程:我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行中的程序,就被称为「进程」(Process)。
- 进程是程序的一次执行过程,或者正在运行的一个程序,是动态的过程,有它自身的产生、存在和消亡的过程
- 线程是由进程创建的,是进程一个实体
- 一个进程可以拥有多个线程
- 单线程:同一时刻,只允许执行一个线程;多线程:同一个时刻,可以执行多个线程
- 并发:同一时刻,多任务交替执行,造成一种貌似同时的错觉,简单的说,单核cpu实现的多任务就是并发
- 并行:同一时刻,多个任务同时执行,多核cpu可以实现并行
线程的基本使用
1、创建线程的2种方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
2、当一个类继承类Thread类,该类就可以当做线程使用
3、我们会重写run方法,写上自己的业务代码
4、run Thread类实现了Runnable接口的run方法
继承Thread类
底层机制
// start0()是本地方法,是JVM调用,底层是c/c++实现
// 真正实现多线程的效果,是start0()而不是run
// start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度
(1)
public synchronized void start() {
start0();
}
(2)
private native void start0();
例如:
package thread_use;
public class Thread_01 {
public static void main(String[] args) {
//创建一个Dog对象可以当作线程使用
Child_Thread child_thread = new Child_Thread();
//启动线程,最终会执行Child_Thread的run方法
child_thread.start();
// 当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
// 主线程和子线程是交替执行
// child_thread.run(); // 这里就是main线程的普通方法。没有真正的启动一个线程,把这个run方法执行完毕,才会往下执行
for (int i=0;i<5;i++){
System.out.println("主线程 "+i+" 线程名:"+Thread.currentThread().getName());
//让主线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Child_Thread extends Thread{
int num=0;
@Override
public void run() {
while (true) {
System.out.println("子线程 "+(num++)+" 线程名:"+Thread.currentThread().getName());
//让该线程休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (num==5){
break; //当num等于5就退出
}
}
}
}
输出结果:
主线程 0 线程名:main
子线程 0 线程名:Thread-0
主线程 1 线程名:main
子线程 1 线程名:Thread-0
主线程 2 线程名:main
子线程 2 线程名:Thread-0
主线程 3 线程名:main
子线程 3 线程名:Thread-0
主线程 4 线程名:main
子线程 4 线程名:Thread-0**
实现Runnable接口
1、java是单继承,在某些情况下一个类可能已经继承了某个父类,这时用继承Thread类的方法来创建线程显然不可能了
2、java设计者提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
例如:
package thread_use;
public class Thread_03 {
public static void main(String[] args) {
Child_Thread01 childThread01 = new Child_Thread01();
// childThread01.start(); 这里不能调用start
//创建了Thread对象,把childThread01对象(实现Runnable)放入Thread
Thread thread = new Thread(childThread01);
thread.start();
}
}
class Child_Thread01 implements Runnable{
int num=0;
@Override
public void run() {
while (true){
System.out.println("子线程 "+(num++)+" 线程名:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (num==10){
break;
}
}
}
}
输出结果:
子线程 0 线程名:Thread-0
子线程 1 线程名:Thread-0
子线程 2 线程名:Thread-0
子线程 3 线程名:Thread-0
子线程 4 线程名:Thread-0
子线程 5 线程名:Thread-0
子线程 6 线程名:Thread-0
子线程 7 线程名:Thread-0
子线程 8 线程名:Thread-0
子线程 9 线程名:Thread-0
例如:
package thread_use;
public class Thread_02 {
public static void main(String[] args) {
Child child = new Child();
ThreadProxy threadProxy = new ThreadProxy(child);
threadProxy.start();
}
}
class People{}
class Child extends People implements Runnable{
@Override
public void run() {
System.out.println("Child_Thread");
}
}
//线程代理类,模拟一个最简单的Thread类
class ThreadProxy implements Runnable{
private Runnable target =null; //属性,类型是Runnable
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
@Override
public void run() {
if (target != null){
target.run(); //动态绑定
}
}
}
输出结果:
Child_Thread
多个子线程
例如:
package thread_use;
public class Thread_04 {
public static void main(String[] args) {
Th1 th1 = new Th1();
Th2 th2 = new Th2();
Thread thread1 = new Thread(th1);
Thread thread2 = new Thread(th2);
thread1.start(); //启动第一个线程
thread2.start(); //启动第二个线程
System.out.println("主线程结束");
}
}
class Th1 implements Runnable{
int num=0;
@Override
public void run() {
while(true) {
System.out.println("子线程1--" + (num++) + " 线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (num == 5) {
break;
}
}
}
}
class Th2 implements Runnable{
int num=0;
@Override
public void run() {
while(true) {
System.out.println("子线程2--" + (num++) + " 线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (num == 5) {
break;
}
}
}
}
输出结果:
主线程结束
子线程2--0 线程名:Thread-1
子线程1--0 线程名:Thread-0
子线程1--1 线程名:Thread-0
子线程2--1 线程名:Thread-1
子线程1--2 线程名:Thread-0
子线程2--2 线程名:Thread-1
子线程1--3 线程名:Thread-0
子线程2--3 线程名:Thread-1
子线程1--4 线程名:Thread-0
子线程2--4 线程名:Thread-1
继承Thread vs 实现Runnable
1、从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,Thread类本身就实现了Runnable接口
2、实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
例如:
package thread_use;
public class thread_05 {
public static void main(String[] args) {
SellTicket sellTicket1 = new SellTicket();
SellTicket sellTicket2 = new SellTicket();
SellTicket sellTicket3 = new SellTicket();
//出现票数超卖的问题
sellTicket1.start();
sellTicket2.start();
sellTicket3.start();
}
}
class SellTicket extends Thread{
private static int ticket_num=5;
@Override
public void run() {
while(true){
if (ticket_num<=0){
System.out.println("售票结束...");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口:"+Thread.currentThread().getName()+" 还剩:"+(--ticket_num)+"张票");
}
}
}
输出结果:
窗口:Thread-1 还剩:4张票
窗口:Thread-0 还剩:3张票
窗口:Thread-2 还剩:2张票
窗口:Thread-0 还剩:1张票
窗口:Thread-1 还剩:0张票
售票结束...
窗口:Thread-2 还剩:-1张票
售票结束...
窗口:Thread-0 还剩:-2张票
售票结束...
例如:
package thread_use;
public class Thread_06 {
public static void main(String[] args) {
SellTicket1 sellTicket1 = new SellTicket1();
Thread thread1 = new Thread(sellTicket1);
Thread thread2 = new Thread(sellTicket1);
Thread thread3 = new Thread(sellTicket1);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket1 implements Runnable{
private static int ticket_num=5;
@Override
public void run() {
while(true){
if (ticket_num<=0){
System.out.println("售票结束...");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口:"+Thread.currentThread().getName()+" 还剩:"+(--ticket_num)+"张票");
}
}
}
输出结果:
窗口:Thread-1 还剩:4张票
窗口:Thread-0 还剩:3张票
窗口:Thread-2 还剩:2张票
窗口:Thread-1 还剩:1张票
窗口:Thread-0 还剩:1张票
售票结束...
窗口:Thread-2 还剩:0张票
售票结束...
售票结束...
线程终止
1、当线程完成任务后,会自动退出
2、还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
例如:
package thread_use;
public class Thread_07 {
public static void main(String[] args) {
AA aa = new AA();
aa.start();
//如果希望main线程去控制aa线程终止,必须可以修改loop
//让aa退出run方法,从而终止aa线程,称为:通知方式
System.out.println("主线程休眠");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
aa.setLoop(false);
}
}
class AA extends Thread{
private int num=0;
private boolean loop=true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程运行中 "+(num++));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
输出结果:
主线程休眠
子线程运行中 0
子线程运行中 1
子线程运行中 2
子线程运行中 3
子线程运行中 4
线程常用方法
1、setName
:设置线程名称,使之与参数name相同
2、getName
:返回该线程的名称
3、start
:使该线程开始执行;java虚拟机底层调用该线程的start0方法
4、run
:调用线程对象run方法
5、setPriority
:更改线程优先级
6、getPriority
:获取线程优先级
7、sleep
:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8、interrupt
:中断线程
注意
1、start
底层会创建新的线程,调用run
,run
就是一个简单的方法调用,不会启动新线程
2、线程优先级的范围,常用的有:
- MAX_PRIORITY : 10
- MIN_PRIORITY : 1
- NORM_PRIORITY : 5
3、interrupt
,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠的线程
4、sleep
线程的静态方法,使当前的线程休眠
例如:
package thread_use;
public class Thread_08 {
public static void main(String[] args) {
T t = new T();
t.setName("sls");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
//主线程循环,之后中断子线程休眠
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
System.out.println("主线程 "+i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
t.interrupt();
}
}
class T extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
//Thread.currentThread().getName() 获取当前线程名称
System.out.println(Thread.currentThread().getName()+" "+i);
}
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(20000);
} catch (InterruptedException e) {
//InterruptedException捕获一个中断异常
//当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码中
System.out.println(Thread.currentThread().getName()+" 中断结束");
}
}
}
输出结果:
sls 0
sls 1
sls 2
sls 3
sls 4
sls 5
sls 6
sls 7
sls 8
sls 9
sls
主线程 0
主线程 1
主线程 2
主线程 3
主线程 4
主线程 5
主线程 6
主线程 7
主线程 8
主线程 9
sls 中断结束
此外
1、yield
:线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
2、join
:线程的插队。插队的线程一旦成功,这肯定先执行完插入的线程所有的任务
例如:
package thread_use;
public class Thread_09 {
public static void main(String[] args) throws InterruptedException{
TT tt = new TT();
tt.start();
for (int i=1;i<5;i++){
Thread.sleep(2000);
System.out.println("主线程 "+i);
if(i==3){
System.out.println("主线程礼让子线程");
tt.join();
System.out.println("子线程结束,主线程运行");
}
}
}
}
class TT extends Thread{
@Override
public void run() {
for (int i=0;i<5;i++){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程 "+i );
}
}
}
输出结果:
主线程 1
子线程 0
主线程 2
子线程 1
主线程 3
主线程礼让子线程
子线程 2
子线程 3
子线程 4
子线程结束,主线程运行
主线程 4
用户线程和守护线程
1、用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2、守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3、常见的守护线程:垃圾回收机制
例如:
package thread_use;
public class Thread_10 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果希望主线程结束子线程也结束,只需将子线程设置成守护线程
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i=0;i<5;i++){
Thread.sleep(1000);
System.out.println("主线程 "+i);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {
for (; ; ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程");
}
}
}
输出结果:
子线程
主线程 0
子线程
主线程 1
子线程
主线程 2
子线程
主线程 3
子线程
主线程 4
线程的生命周期
线程可以处于一下状态之一:
NEW
:尚未启动的线程处于此状态RUNNABLE
:在java虚拟机中执行的线程处于此状态BLOCKED
:被阻塞等待监视器锁定的线程处于此状态TIMED_WAITING
:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态TERMINATED
:已退出的线程处于此状态
例如:
package thread_use;
public class Thread_11 {
public static void main(String[] args) {
T1 t1 = new T1();
System.out.println("线程名: "+t1.getName()+"线程状态:"+t1.getState());
t1.start();
while(t1.getState()!=Thread.State.TERMINATED){
System.out.println("线程名: "+t1.getName()+"线程状态:"+t1.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程名: "+t1.getName()+"线程状态:"+t1.getState());
}
}
class T1 extends Thread{
@Override
public void run() {
for (int i=0;i<5;i++ ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程("+Thread.currentThread().getName() +")运行中");
}
}
}
输出结果:
线程名: Thread-0线程状态:NEW
线程名: Thread-0线程状态:RUNNABLE
线程名: Thread-0线程状态:TIMED_WAITING
子线程(Thread-0)运行中
线程名: Thread-0线程状态:TIMED_WAITING
子线程(Thread-0)运行中
子线程(Thread-0)运行中
线程名: Thread-0线程状态:TIMED_WAITING
子线程(Thread-0)运行中
线程名: Thread-0线程状态:TIMED_WAITING
子线程(Thread-0)运行中
线程名: Thread-0线程状态:TERMINATED
线程同步机制
1、在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
2、可以这样理解,线程同步:当一个线程在对内存进行操作时,其他线程都不可以对和这个内存地址进行操作,知道该线程完成操作,其他线程才能对该内存地址进行操作
3、实现同步
- 同步代码块
synchronized(对象){ //得到对象的锁,才能操作同步代码
//需要被同步的代码
}
- synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
例如:
package thread_use;
public class Thread_12 {
public static void main(String[] args) {
SellTicket2 sellTicket2 = new SellTicket2();
Thread thread1 = new Thread(sellTicket2);
Thread thread2 = new Thread(sellTicket2);
Thread thread3 = new Thread(sellTicket2);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket2 implements Runnable{
private static int ticket_num=5;
private boolean loop=true;
// public synchronized void sell()就是一个同步方法
// 这时锁在this对象
// 也可以在代码块上写synchronize,就变为同步代码块
public synchronized void sell(){
if (ticket_num<=0){
System.out.println("售票结束...");
loop=false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口:"+Thread.currentThread().getName()+" 还剩:"+(--ticket_num)+"张票");
}
@Override
public void run() {
while(loop) {
sell(); //sell方法是一种同步方法
}
}
}
输出结果:
窗口:Thread-0 还剩:4张票
窗口:Thread-0 还剩:3张票
窗口:Thread-0 还剩:2张票
窗口:Thread-0 还剩:1张票
窗口:Thread-0 还剩:0张票
售票结束...
售票结束...
售票结束...
互斥锁
1、Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
2、每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
3、关键字synchronized
来与对象的互斥锁联系。当某个对象用synchronized
修饰时,表明该对象在任意时刻只能由一个线程访问
4、同步的局限性:导致程序的执行效率降低
5、同步方法(非静态)的锁可以是this,也可以是其他对象(要求同一个对象)
6、同步方法(静态的)的锁为当前类本身
例如:
package thread_use;
public class Thread_13 {
public static void main(String[] args) {
SellTicket3 sellTicket3 = new SellTicket3();
Thread thread1 = new Thread(sellTicket3);
Thread thread2 = new Thread(sellTicket3);
Thread thread3 = new Thread(sellTicket3);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket3 implements Runnable{
private static int ticket_num=5;
private boolean loop=true;
// public synchronized void sell()就是一个同步方法
// 这时锁在this对象
// 也可以在代码块上写synchronize,就变为同步代码块,这个互斥锁还是this
// 同步方法(静态的)的锁为当前类本身
// public synchronzied static void m1() 锁加在 SellTicket3.class
// 如果在静态方法中,实现一个同步代码块
/*
public static void m2(){
synchronzied(SellTicket3.class){
System.out.println("m2");
}
}
*/
public synchronzied static void m1(){
}
public /* synchronized */ void sell(){
synchronized (this) {
if (ticket_num <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口:" + Thread.currentThread().getName() + " 还剩:" + (--ticket_num) + "张票");
}
}
@Override
public void run() {
while(loop) {
sell(); //sell方法是一种同步方法
}
}
}
输出结果:
窗口:Thread-0 还剩:4张票
窗口:Thread-0 还剩:3张票
窗口:Thread-0 还剩:2张票
窗口:Thread-0 还剩:1张票
窗口:Thread-0 还剩:0张票
售票结束...
售票结束...
售票结束...
例如:
package thread_use;
import jdk.nashorn.internal.ir.CallNode;
public class Thread_14 {
public static void main(String[] args) {
SellTicket4 sellTicket4 = new SellTicket4();
Thread thread1 = new Thread(sellTicket4);
Thread thread2 = new Thread(sellTicket4);
Thread thread3 = new Thread(sellTicket4);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket4 implements Runnable{
private static int ticket_num=5;
private boolean loop=true;
// public synchronized void sell()就是一个同步方法
// 这时锁在this对象
// 也可以在代码块上写synchronize,就变为同步代码块,这个互斥锁还是this
Object object = new Object();
public /* synchronized */ void sell(){
synchronized (object) {
if (ticket_num <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口:" + Thread.currentThread().getName() + " 还剩:" + (--ticket_num) + "张票");
}
}
@Override
public void run() {
while(loop) {
sell(); //sell方法是一种同步方法
}
}
}
输出结果:
窗口:Thread-0 还剩:4张票
窗口:Thread-0 还剩:3张票
窗口:Thread-2 还剩:2张票
窗口:Thread-2 还剩:1张票
窗口:Thread-2 还剩:0张票
售票结束...
售票结束...
售票结束...
注意
1、同步方法如果没有使用static
修饰:默认锁对象为this
2、如果方法使用static
修饰,默认锁对象:当前类.class
3、实现的落地步骤:
- 需要先分析锁的代码
- 选择同步代码块或同步方法
- 需要多个线程的锁对象为同一个即可
死锁
介绍
多个线程都占用了对方的锁资源,但是不肯相让,导致了死锁,在编程里是一定要避免死锁的发生
例如:
package thread_use;
public class Thread_15 {
public static void main(String[] args) {
DeadLock A = new DeadLock(true);
A.setName("A线程");
A.start();
DeadLock B= new DeadLock(false);
B.setName("B线程");
B.start();
}
}
class DeadLock extends Thread{
static Object o1=new Object();
static Object o2=new Object();
boolean flag;
public DeadLock(boolean flag){
this.flag=flag;
}
@Override
public void run() {
//下面的业务逻辑
//1、如果flag为True,线程A就会先得到或者持有o1对象锁,然后尝试去获取o2对象锁
//2、如果线程A得不到o2对象锁,就会Blocked
//3、如果flag为False,线程B就会先得到或者持有o2对象锁,然后尝试去获取o1对象锁
//4、如果线程B得不到o1对象锁,就会Blocked
if (flag) {
synchronized (o1) { //对象互斥锁
System.out.println(Thread.currentThread().getName() + " 在o1中");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 在o2中");
}
}
}else {
synchronized (o2) {//对象互斥锁
System.out.println(Thread.currentThread().getName() + " 在o3中");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 在o4中");
}
}
}
}
}
输出结果:
A线程 在o1中
B线程 在o3中
#一直不会停~~~
释放锁
1、当前线程的同步方法、同步代码块执行结束
2、当前线程在同步代码块,同步方法中遇到了break
、return
3、当前线程在同步代码块、同步方法中出现了未处理的Error
或Exception
,导致异常结束
4、当前线程在同步代码块,同步方法中执行了线程对象的wait()
方法,当前线程暂停,并释放锁
反例
不会释放锁
1、线程执行同步方法时,程序调用Thread.sleep()
、Thread.yield()
方法暂停当前线程的执行,不会释放锁
2、线程执行同步代码块时,其他线程调用了该线程的suspend()
方法将该线程挂起,该线程不会释放锁
提示:应尽量避免使用suspend()
和resume()
来控制线程,方法不在推荐使用