线程

进程与线程

一个程序至少有一个进程,一个进程至少有一个线程。
线程的划分尺度小于进程,使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
在Java中,每次程序运行至少启动2个线程:

1.main主线程
2.垃圾收集线程

两种主要的并发情况

IO流阻塞:数据读写,是可以调剂资源的;可以尽可能的拆分线程。
运算阻塞:一次不可以中断的阻塞,是系统无法在运算结束之前调剂资源的阻塞方式;最多阻塞的数量相当于CPU的内核数。要尽可能的避免出现运算阻塞这种情况

线程的创建

线程创建的2种方式

  1. 继承Thread:要通过重写或者实现run方法来完成线程的主体功能
public class ThreadCreateTest {
   public static void main(String[] args) {
   	Thread t = new InheritThread();
   	t.start();
   }
}

class InheritThread extends Thread {
   @Override
   public void run() {
   	System.out.print("继承Thread创建了线程");
   }
}

运行结果:

继承Thread创建了线程

  1. 实现Runnable:要通过重写或者实现run方法来完成线程的主体功能
public class ThreadCreateTest {
   public static void main(String[] args) {
   	Thread t = new Thread(new RunnableIpl());
   	t.start();
   }
}

class RunnableIpl implements Runnable{
   @Override
   public void run() {
   	System.out.println("实现Runnable类创建了线程");
   }
}

执行结果:

实现Runnable类创建了线程

其他说明

1.每一个线程都有一个属于自己独立的执行序列
2.main方法属于主线程;而自己创建的属于子线程
3.线程还可以分为用户线程和守护线程:用户线程是必须执行完的线程;当用户线程执行完以后,无论守护线程是否执行完,都立即停止
4.线程的启动时通过start()方法来启动,而不是通过调用run()方法来启动!
5.继承Thread创建线程时,不会强制要求重写run方法,实现Runnable时则必须重写run方法
6.每个线程都有一个调用栈,每创建一个新的线程,就产生一个新的调用栈

设置线程名称、获取当前线程

设置线程名称:t.setName(String name)
获取当前线程:Thread t = Thread.currentThread()
public class ThreadCreateTest {
	public static void main(String[] args) {
		Thread t = new Thread(new RunnableIpl());
		t.setName("金盏花");
		t.start();
	}
}

class RunnableIpl implements Runnable{
	@Override
	public void run() {
		System.out.println("实现Runnable类创建了线程");
		Thread t = Thread.currentThread();
		System.out.println(t.getName());
	}
}

执行结果

实现Runnable类创建了线程
金盏花

注:线程默认名称:Thread-0 …… n

线程栈

public class ThreadStack {
	public static void main(String[] args) {
		System.out.print("此处主线程执行");
		test();
	}
	
	static void test() {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.print("此处子线程执行");
			}
		});
		t.start();
	}
}

说明:
程序运行ThreadStack的main方法时,系统分配线程栈1给主线程,线程栈1将main压入栈中,主线程运行到test方法时,会将test压入线程栈1中,test运行时,创建了线程,系统分配桟2给test创建的线程,桟2将run方法压入栈中
线程栈

等待

方法说明
void wait()导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
void notify()唤醒在此对象监视器上等待的单个线程。
void notifyAll()唤醒在此对象监视器上等待的所有线程。

注意:

1.通过对象来进行调度
2.必须在同步状态下才可以等待
在这里插入图片描述
在这里插入图片描述

等待的生命周期

a.创建实例
b.准备就绪
c.运行(即start)时,会获取锁,持有锁之后,等待系统分配资源,获取资源运行相关内容,运行途中因为某些原因,线程进入等待(wait)时,会释放锁
d.线程等待时间到/被唤醒时,线程需要再次获取锁,然后进入就绪状态,获得资源执行后面的内容,执行完成后,会释放锁

休眠

1)任何地方都可以休眠
2)调用Thread的sleep方法进行休眠
3)休眠不可唤醒,只可以时间到自己醒
4)休眠如果在同步状态下,是不会释放锁的
生命周期:

a.创建实例
b.准备就绪
c.运行----->休眠---->时间到---->就绪状态---->运行

优先级

不要用!不靠谱,不推荐,不指望!
1)线程的优先级在1-10直接
2)10最高,1最低
3)不靠谱,不能指望
4)让步是让当前正在运行的线程,直接回到可运行状态,重新抢资源

线程的合并

1)Join:让一个线程加入到另一个线程之后,顺序执行
2)先start再join
3)join类似于sleep,在前一个线程执行之后执行;调用了join以后,依然会回到可执行状态
4)可以指定时间,前面一个线程如果执行时间过长,那么合并线程指定时间到了以后立即执行。

public class ThreadStack {
	public static void main(String[] args) throws Exception{
		System.out.println("此处主线程执行");
		Thread t1 = new ThreadTest();
		t1.start();
		t1.join();
		Thread t2 = new ThreadTest();
		t2.start();
		t2.join();
		Thread t3 = new ThreadTest();
		t3.start();
		t3.join(1000);
		Thread t4 = new ThreadTest();
		t4.start();
		t4.join();
		Thread t5 = new ThreadTest();
		t5.start();
		t5.join();
	}
}

class ThreadTest extends Thread{
	@Override
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

执行结果:

此处主线程执行
Thread-0
Thread-1
Thread-2
Thread-3
Thread-4

线程的生命周期

线程生命周期
一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。
线程生命周期如下:

1.创建线程实例;
2.调用start方法,让线程变为可执行状;
3.等待系统分配资源
4.有资源之后,进入运行状态
5.run方法执行结束,中途没有任何阻塞情况(线程被调度),那么该线程生命周期结束

同步与锁

概念:线程同步指的是,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作。
多线程访问同一个资源会产生线程安全问题,所以需要进行加锁。
每个锁在同一时刻,只能由一个线程持有。
方法或声明执行期间,如程序遇到任何异常或return,线程都会释放锁。
1.多个线程未加锁

public class LockObject {
	public static void main(String[] args) {
		for(int i = 0; i < 10000; i++) {
			new Thread(new ThreadTest1()).start();
		}
	}

}

class ThreadTest1 implements Runnable{
	@Override
	public void run() {
		MathOpera.getMathOpera().add();
	}
}

class MathOpera{
	public MathOpera() {}
	private static MathOpera m = new MathOpera();
	public static MathOpera getMathOpera() {
		return m;
	}
	
	int num = 0;
	public void add() {
		num++;
		System.out.println("增加后的值:" + num);
	}
}

按照平时理解的逻辑,for(int i = 0; i < 10000; i++) {…}执行了10000次,num最后应该是10000,但实际上num可能不是10000,因为有些线程可能获取到了同样的num,将num加1后的值赋值过去,相当于执行了至少2次同样的值加1


增加后的值:9986
增加后的值:9987
增加后的值:9988
增加后的值:9989
增加后的值:9990
增加后的值:9991
增加后的值:9992
增加后的值:9993
增加后的值:9994
增加后的值:9995
增加后的值:9996

synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,都是锁对象
锁在方法上:

class MathOpera{
	public MathOpera() {}
	private static MathOpera m = new MathOpera();
	public static MathOpera getMathOpera() {
		return m;
	}
	
	int num = 0;
	public synchronized void add() {
		num++;
		System.out.println("增加后的值:" + num);
	}
}

执行结果:


增加后的值:9993
增加后的值:9994
增加后的值:9995
增加后的值:9996
增加后的值:9997
增加后的值:9998
增加后的值:9999
增加后的值:10000

锁代码块:

class MathOpera{
	public MathOpera() {}
	private static MathOpera m = new MathOpera();
	public static MathOpera getMathOpera() {
		return m;
	}
	
	int num = 0;
	public void add() {
		synchronized(this) {
			num++;
		}
		System.out.println("增加后的值:" + num);
	}
}

执行后结果:


增加后的值:9994
增加后的值:9995
增加后的值:9996
增加后的值:9997
增加后的值:9998
增加后的值:9999
增加后的值:10000

一般而言,锁方法比较霸道,方法被锁了之后,其他线程都只能等待当前线程释放锁之后才能执行,但方法中有部分内容是不需要安全控制的,相当于上厕所时,有人将厕所的大门给关了,其他人进不了厕所,但是你只用到厕所的一个隔间,其他你用不到,锁代码块就相当于锁隔间,其他隔间其他人去使用。一般是找有共享数据修改的代码块来上锁。

实例方法可以通过锁对象控制线程,但是静态方法是不根据对象存在,是在类里的,如果静态方法需要枷锁,可以通过锁类来达到目的
静态方法add里加入对象的同步锁时,可以看到,编译错误:
在这里插入图片描述

锁类之后:

public class LockClass {
	public static void main(String[] args) {
		for(int i = 0; i < 1000; i++) {
			new Thread(new ThreadTest2()).start();
		}
	}
}

class ThreadTest2 implements Runnable{
	@Override
	public void run() {
		new MathOperate().add();
	}
}

class MathOperate{
	static int addCount = 0;
	public static void  add() {
		synchronized(MathOperate.class) {
			addCount++;
			System.out.println("增加后的值:" + addCount);
		}
	}

}

执行结果:


增加后的值:988
增加后的值:989
增加后的值:990
增加后的值:991
增加后的值:992
增加后的值:993
增加后的值:994
增加后的值:995
增加后的值:996
增加后的值:997
增加后的值:998
增加后的值:999
增加后的值:1000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值