1概念
- 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
- 在程序中线程指的是对于多个相互独立的代码片段重叠、并行执行,这样就可以提高程序的处理效率。
- Java的线程是通过java.lang.Thread类来实现的
- 当我们执行一个类的Main方法实质上是相当于启动了一个主线程
线程和进程的区别:
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
- 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小
- 以上两点说明线程上下文切换比进程上下文切换要快得多。
- 多进程: 在操作系统中能同时运行多个任务(程序)
- 多线程: 在同一应用程序(进程)中有多个顺序流同时执行
2.线程的创建和启动
线程的创建方式一
- 定义自己的线程类实现Runnable接口
- 创建的时候利用Thread的构造函数:
thread = new Thead(Runnable子类实例) - Runnable中只有一个方法:
public void run(); 在该方法中定义线程运行主体。 - 使用Runnable接口可以为多个线程提供共享的数据。
public class CreateThreadUseRunable implements Runnable {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("ChildThread----------------"+i);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new CreateThreadUseRunable());
//启动线程
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("MainThread----------------"+i);
}
}
}
方式二
- 定义自己的线程类继承Thread,重写他的run方法
public class MyThread extends Thread{
public void run() {
//运行主体
}
} - 创建的时候直接实例化:
Thread thread = new MyThread();
public class CreateThreadUseExtend extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("ChildThread----------------"+i);
}
}
public static void main(String[] args) {
Thread thread = new CreateThreadUseExtend();
//启动线程
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("MainThread----------------"+i);
}
}
}
线程的启动
- 通过调用Thead类的start()方法来启动一个线程
- 注意是调用start()方法来启动线程,而不是调用run方法来启动线程
3.线程的调度和优先级
线程的状态
新建、就绪、运行、阻塞、死亡五种状态
线程的基本常用方法:
- start()启动线程,新生状态到就绪
- stop()终止线程,由于安全问题JDK不建议使用,切换到死亡状态
- destroy()用于破坏该线程,但不作任何清除,不建议使用,切换到死亡状态
- isAlive()判断线程是否还“活”着,即线程是否还未终止。
- getPriority()获得线程的优先级数值
- setPriority()设置线程的优先级数值
- Thread.sleep()将当前线程睡眠指定毫秒数
- join()调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行,运行状态到阻塞状态。
public class TestJoin {
public static void main(String[] args) {
Thread thread = new Thread(new CreateThreadUseRunable());
//启动线程
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("MainThread----------------"+i);
if(i==5){
try {
//你会发现当主线程执行到5的时候,合并子线程到主线程,等待子线程执行完毕后,再才执行主线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- yield()让出CPU,当前线程进入就绪队列等待调度,在就绪和运行状态中切换。
public class TestYield { public static void main(String[] args) { Thread thread = new Thread(new CreateThreadUseRunable()); //启动线程 thread.start(); for (int i = 0; i < 50; i++) { System.out.println("MainThread----------------"+i); //当主线程执行到5的时候,就进入阻塞状态,自动让出cpu给子线程调用 if(i==5){ Thread.yield(); } } } }
- wait()当前线程进入对象的wait pool,变为阻塞状态。
- notify()/notifyAll()唤醒对象的wait pool中的一个/所有等待线程,由阻塞状态变为就绪状态。
线程的优先级:
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5 - 获得或设置线程对象的优先级
int getPriority();
void setPriority(int newPriority);
public class TestPriority {
public static void main(String[] args) {
Thread t1 = new Thread(new Thread1());
Thread t2 = new Thread(new Thread2());
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
class Thread1 implements Runnable{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("Thread1--------------"+i);
}
}
}
class Thread2 implements Runnable{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("Thread2--------------"+i);
}
}
}
4.线程同步
- 同步是指多个线程希望同时访问同一资源时,确保在任意时刻始终只被一个线程访问,以保证数据的完整性
- 为什么需要同步
如一个银行账户中剩余余额为1000元
现在用户到ATM机中取钱,首先查询余额1000,取500元,余额变为500
在用户取钱的同时账户收到一笔新的入款500,首先查询余额1000,加500,余额变为1500
当ATM机执行完毕后就把余额500赋值给了账户余额,此时余额就变为了500
出现了问题,实质余额应该还是1000 - 用代码模拟一下
public class BankMoneyTest implements Runnable{
//最初余额
public static double remaing =10000;
public void run() {
//为了模拟出问题我执行了多次的取钱和入款操作
for(int i=0;i<10000;i++){
if(i%2==0){
add(100);
}else{
reduce(100);
}
}
}
/**
* 模拟入款
* @param value
*/
public void add(double value){
double temp = remaing;
remaing = getRemain()+value;
System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing);
}
/**
* 模拟取钱
* @param value
*/
public void reduce(double value){
double temp = remaing;
remaing = getRemain()-value;
System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing);
}
/**
* 获取余额
* @return
*/
public double getRemain(){
return remaing;
}
public static void main(String[] args) {
BankMoneyTest test = new BankMoneyTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
执行结果不是固定的第一种结果(最后几行):
当前余额10000.0,取款100.0余额为9900.0
当前余额9900.0,入款100.0余额为10000.0
当前余额10000.0,取款100.0余额为9900.0
当前余额9900.0,入款100.0余额为10000.0
当前余额10000.0,取款100.0余额为9900.0
当前余额9900.0,入款100.0余额为10000.0
当前余额10000.0,取款100.0余额为9900.0
当前余额9900.0,入款100.0余额为10000.0
当前余额10000.0,取款100.0余额为9900.0
执行结果不是固定的第二种结果(最后几行):
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
当前余额9000.0,入款100.0余额为9100.0
当前余额9100.0,取款100.0余额为9000.0
执行结果不是固定的第三种结果(最后几行):
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
当前余额10400.0,入款100.0余额为10500.0
当前余额10500.0,取款100.0余额为10400.0
由此发现结果都出现了潜在性的问题。
怎么解决这种问题?就是使用同步,加锁
同步方法:
- 同步方法,使用synchronized关键字声明的方法叫同步方法
public synchronized void method(){} - 同步方法表示在任意时刻都只能执行类对象中多个同步方法中的一个,只有在对象当前执行的同步方法结束后,才能访问该对象的其他同步方法。
- 其实质是确保每个同步方法在执行时相对于其他同步方法是独占对象实例的访问权限,他相当于给对象加了一把锁,当一开始执行同步方法就会获得锁,当方法执行完毕后才会释放锁,其他同步方法才会可以访问。
把上面的方法加锁:
public class BankMoneyTest2 implements Runnable{
//最初余额
public static double remaing =10000;
public void run() {
for(int i=0;i<10000;i++){
if(i%2==0){
add(100);
}else{
reduce(100);
}
}
}
/**
* 模拟入款
* @param value
*/
public synchronized void add(double value){
double temp = remaing;
remaing = getRemain()+value;
System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing);
}
/**
* 模拟取钱
* @param value
*/
public synchronized void reduce(double value){
double temp = remaing;
remaing = getRemain()-value;
System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing);
}
/**
* 获取余额
* @return
*/
public synchronized double getRemain(){
return remaing;
}
public static void main(String[] args) {
BankMoneyTest2 test = new BankMoneyTest2();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
执行结果始终是:
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
你会发现在执行这个方法时就不会和逻辑发生冲突
同步代码块:
- 同步代码块,使用synchronized关键字来指定程序中的语句或代码块
synchronized(obj){} - 这种方法更强大,它表示对某个指定对象将获得它所包括的语句或代码块的执行权限,而不像同步方法仅是为包含代码的对象获取锁权限。
- 如果被锁的代码块引用了其他方法,那么对应的方法也相当于加上了这个对象的锁
- 也就是说对给定对象执行同步代码块的时候,就不能执行该对象同步的其他代码块或方法
采用同步代码块修改以上例子:
public class BankMoneyTest3 implements Runnable{
//最初余额
public static double remaing =10000;
public void run() {
for(int i=0;i<10000;i++){
if(i%2==0){
synchronized (this) {
add(100);
}
}else{
synchronized (this) {
reduce(100);
}
}
}
}
/**
* 模拟入款
* @param value
*/
public void add(double value){
double temp = remaing;
remaing = getRemain()+value;
System.out.println("当前余额"+temp+",入款"+value+"余额为"+remaing);
}
/**
* 模拟取钱
* @param value
*/
public void reduce(double value){
double temp = remaing;
remaing = getRemain()-value;
System.out.println("当前余额"+temp+",取款"+value+"余额为"+remaing);
}
/**
* 获取余额
* @return
*/
public double getRemain(){
return remaing;
}
public static void main(String[] args) {
BankMoneyTest3 test = new BankMoneyTest3();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
执行结果始终是:
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
当前余额10000.0,入款100.0余额为10100.0
当前余额10100.0,取款100.0余额为10000.0
你会发现在执行这个方法时也不会和逻辑发生冲突
死锁:
- 死锁的产生是指两个线程对访问资源的相互依赖而发生冲突产生的。
- 如某个线程执行给定对象obj1的同步代码块时,而在这个同步代码块中包含另一个对象obj2的同步代码块,而obj2中的同步代码块中又依赖obj1的同步代码块。这样就产生了死锁
用代码来模拟一个死锁
public class DeadSync implements Runnable{
public boolean flag ;
public static Object o1 = new Object();
public static Object o2 = new Object();
public void run() {
System.out.println("flag=" + flag);
if(flag) {
synchronized(o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o2) {
System.out.println(flag);
}
}
}
if(!flag) {
synchronized(o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o1) {
System.out.println(flag);
}
}
}
}
public static void main(String[] args) {
DeadSync td1 = new DeadSync();
DeadSync td2 = new DeadSync();
td1.flag = true;
td2.flag = false;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}