java-线程Thread&线程不安全&线程生命周期&锁对象

  • Post author:
  • Post category:java




一、 线程

1.定义:一个程序中不同的工作分支

2.如何实现:

  • java是面向对象的语言,所有的事物都是对象,包括线程。

  • java中线程的一种实现方式是继承Thread类。

  • 每个线程都有自己的工作,定义线程的时候要定义好这个线程需要做什么。线程对象需要做的工作定义在run()方法中。即定义好这个继承Thread的子类的功能是什么,这个子类的实例化对象就全都是做这种功能的。

  • 线程的启动要用start()方法,而不是run()方法。

    代码示例:

package com.easy;

public class Easy {

	public static void main(String[] args) {
		T1 t1 = new T1();
		t1.start();
	}
}


class T1 extends Thread{
	@Override
	public void run() {
		for(int i = 1; i <= 20; i++) {
			//getName()方法用来获取线程名字, this代表当前实例对象 
			System.out.println(i+"----"+this.getName());
		}
	} 
}

3.线程是异步进行的,彼此之间不受影响

代码实例:

package com.easy;

public class Easy {

	public static void main(String[] args) {
		T1 t1 = new T1();
		T1 t2 = new T1();
		t1.start();
		t2.start();
	}
}


class T1 extends Thread{
	@Override
	public void run() {
		for(int i = 1; i <= 10; i++) {
			//getName()方法用来获取线程名字, this代表当前实例对象 
			System.out.println(i+"----"+this.getName());
		}
	} 
}

运行结果:
1----Thread-0
1----Thread-1
2----Thread-1
3----Thread-1
4----Thread-1
5----Thread-1
2----Thread-0
3----Thread-0
6----Thread-1
4----Thread-0
7----Thread-1
5----Thread-0
8----Thread-1
6----Thread-0
7----Thread-0
8----Thread-0
9----Thread-0
9----Thread-1
10----Thread-0
10----Thread-1

从运行结果可以看出,两个线程异步进行,异步进行也就是说,两个进程互不影响,同时执行。

4.区分mian和线程对象

程序执行有主线程也就是main线程,在main方法中,实例化线程对象,并调用其start()方法开启线程之后,主线程不会等待该线程对象执行,而是会直接继续执行下一句。

代码实例:

package com.easy;

import java.util.ArrayList;

public class Easy1 {

	public static void main(String[] args) {
		ArrayList list = new ArrayList(); //创建容器对象
		//定义线程对象
		T t1 = new T(list);
		//T t2 = new T(list);
		t1.start();
		//t2.start();
		System.out.println(list.size());
	}
}


class T extends Thread{
	ArrayList list;
	public T(ArrayList list) {
		this.list = list;
	}
	
	@Override
	public void run() {     //往数组中添加200个数据
		for(int i = 0; i < 200; i++) {
			list.add(i);
		}
	}
}

执行结果: 0

出现这个执行结果的原因就是,一开始list为空,当t1线程开始执行后,主线程不会等待t1执行,而是直接执行下一行代码,也就是输出list的size();

此时我们可以让主线程休息一会,让t1线程执行完毕。可以在t1.start();后加入一行Thread.sleep(1000); 让主线程休息1s,这样t1就可以执行完毕。



二、线程安全



1.线程安全即指数据安全,也就是数据不错乱。


当多个线程操作一个数据时,会产生异常结果。


举例说明:假设开辟了一个数组,两个线程同时开始往数组存数据,对于i位置,可能两个数组同时检测到该位置没有数据,然后都往这个位置存放数据,那么就会造成

数据的覆盖

。这就产生了异常结果,也就是线程不安全。

代码示例:

package com.easy;

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

public class Easy2 {

	public static List list = new LinkedList();
	public static void main(String[] args) {
		Th t1 = new Th();
		t1.start();
		Th t2 = new Th();
		t2.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(list.size());
	}
	
	public static void add(Object obj) {
		list.add(obj);
	}
}


class Th extends Thread{
	@Override
	public void run() {
		for(int i = 0; i < 200; i++) {
			Easy2.add(i);
		}
	}
}
运行结果(多次): 387
                  375
                  386



2.解决:

synchronized关键字

synchronized关键字可以标注某个代码块是

同步

代码块。也就是说,被synchronized关键字修饰的代码块,当有多个线程准备运行此代码块时,同一时间

只有一个

线程可以运行此代码块。

public static syncharonized void add(Object obj) {
		list.add(obj);
	}
运行结果:400

同步和异步比较:

同步更安全,不会发生数据错乱

异步处理数据速度更快,但可能引发数据错乱



3.

volatile

关键字

易变的,不稳定的,每次用它都要从新检测他的值。

3.1先介绍一种情况:

package com.easy;

public class Easy3 {

	public static boolean a = true;
	public static void main(String[] args) {
	
		E e = new E();
		e.start();
		try {
			Thread.sleep(500);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		a = false;
		System.out.println("主线程修改a为false");
	}
}

class E extends Thread{
	@Override
	public void run() {
		System.out.println("线程e开始了");
		while(Easy3.a) {
			
		}
		System.out.println("线程e结束了");
	}
}
执行结果:
         线程e开始了
         主线程修改a为false
         (且程序未结束)

分析原因:Java虚拟机(JVM)在运行的过程中有一块栈,在Java虚拟机运行过程中,每一个线程都有一个自己的栈区,在这个栈区中执行了一个方法,就形成一个栈帧。对于变量则在栈区形成一个副本,当使用这个变量时,便用副本的值。在上述代码中,因为线程e的栈区中存的a的副本值是一开始在main主线程中读取到的true,且线程e一直使用这个副本值,因此线程e不会结束,也就不会输出最后一句话。因此,我们想要做的就是让while(Easy3.a) 这行代码去读取a的真实值而不是副本值。

3.2解决:

volatile

关键字

将a变量的声明语句改为:

public static volatile boolean a = true;


执行结果:

线程e开始了

主线程修改a为false

线程e结束了



三、线程生命周期(面试常考)

生命周期——对象的生老病死过程

线程从开始到结束会经历哪些过程(状态)?

线程状态:新建状态 可运行状态 运行状态 堵塞状态 死亡状态




堵塞状态例子:



1.

jion()方法

package com.easy;

public class Easy4 {

	public static void main(String[] args) {
		//T4 t = new T4(); //新建状态
		//t.start(); //可运行状态   临界状态,类似短跑比赛裁判放枪的过程
		           //线程进入到可运行状态后,CPU分配资源给线程,线程进入运行状态
		           //线程运行结束后,进入死亡状态
		//堵塞状态:
		//sleep()   join()方法   锁对象(利用synchronized关键字实现)
		//wait   notify  notifyall  ????
		T44 t44 = new T44();
		T4 t4 = new T4();
		t4.t = t44;   //t线程就是t44,这俩是同一个线程
		
		t44.start();
		t4.start();
	}
}


class T4 extends Thread{
	Thread t;
	@Override
	public void run() {
		System.out.println("T4已经开始运行");
		System.out.println("T4开始休眠50ms");
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("T4结束休眠50ms——————t线程加入");
	    try {
			t.join();  **//用jion()方法堵塞进程(t堵塞t4)**
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    System.out.println("t线程结束");
	    System.out.println("T4结束运行");
	}
}

class T44 extends Thread{
	@Override
	public void run() {
		System.out.println("T44开始休眠10s");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("T44结束休眠10s");
	}
}
执行结果:
T4已经开始运行
T4开始休眠50ms
T44开始休眠10s
T4结束休眠50ms——————t线程加入
(10s-50ms后才显示下面的结果)
T44结束休眠10s(因为t和t44是同一个进程,所以一起结束)
t线程结束
T4结束运行



2.锁对象

package com.easy;

public class Easy4 {

	public static void main(String[] args) {
		//T4 t = new T4(); //新建状态
		//t.start(); //可运行状态     临界状态,类似短跑比赛裁判放枪的过程
		           //线程进入到可运行状态后,CPU分配资源给线程,线程进入运行状态
		           //线程运行结束后,进入死亡状态
		//堵塞状态:
		//sleep()   join()方法   锁对象(利用synchronized关键字实现)
		T5 t5 = new T5();
		T5 t55 = new T5();
		t5.start();
		t55.start();
	}
}



class T5 extends Thread{
	//利用synchronized关键字构造锁对象
	public synchronized static void test() {   
		System.out.println("开始————"+Thread.currentThread().getName());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("结束————"+Thread.currentThread().getName());
	}
	@Override
	public void run() {
		test();
	}
}
}
执行结果:
开始————Thread-05s后)
结束————Thread-0
开始————Thread-15s后)
结束————Thread-1



3.wait notify notifyall

对象的方法,wait方法声明在Object中

wait notify notifyall是锁对象调用的方法



介绍锁对象

锁对象是控制代码块进行同步的,谁(指不同线程)拿到锁对象谁就可以执行这个

同步

代码块

任何对象都可以当作锁对象

但在不同的代码中,锁对象是一定的

没有同步(synchronized)就没有锁对象

在以对象为锁的同步代码块中调用wait notify notifyall

package com.easy;

public class Easy5 {

	public static void main(String[] args) {
		
		T6 t6 = new T6();
		T6 t66 = new T6();
		t6.start();
		t66.start();
	}
	
	**//锁对象是谁 ???    ————————this**
	public synchronized static void test() {   
		System.out.println("开始————"+Thread.currentThread().getName());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("结束————"+Thread.currentThread().getName());
	}
}
//指定对象为锁:
	static Object obj = new Object();
	public static void test() {
		synchronized (obj) {  //指定obj为锁对象
			System.out.println("开始————"+Thread.currentThread().getName());
			try {
				obj.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("结束————"+Thread.currentThread().getName());
		}
	}

回到wiat notify notifyall

锁对象.wait()方法的作用是锁对象将某个正在执行当前代码块且执行到锁对象.wait()这一行代码的线程关进小黑屋(即该线程丢失了获取锁对象的权限,也就不能继续向下执行)。等到该线程重新获取了锁对象的权限后,才可继续执行。锁对象.notify()方法可以从锁对象的小黑屋中释放出一个线程,即重新把所对像赋予该线程。

代码实例:

package com.easy;

public class Easy5 {

	public static void main(String[] args) {
		
		T6 t6 = new T6();
		t6.start();
		try {
			Thread.sleep(3);   //确保t6先执行,即确保一定关进小黑屋
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        T7 t7 = new T7();
        t7.start();
	}
	
//	//锁对象是谁 ???    ————————this
//	public synchronized static void test() {   
//		System.out.println("开始————"+Thread.currentThread().getName());
//		try {
//			Thread.sleep(5000);
//		} catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		System.out.println("结束————"+Thread.currentThread().getName());
//	}
	
	
	
	
	//指定对象为锁:
	static Object obj = new Object();
	
    public static void testNotify() {
		synchronized (obj) {
			System.out.println("开始休眠————"+Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("从小黑屋放出来一个线程————"+Thread.currentThread().getName());
		    obj.notify();
		}
	}
	
	
	public static void test() {
		synchronized (obj) {  //指定obj为锁对象
			System.out.println("T6开始————"+Thread.currentThread().getName());
			try {
				obj.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("T6结束————"+Thread.currentThread().getName());
		}
	}
}


class T7 extends Thread{
	@Override
	public void run() {
		new Easy5().testNotify();
	}
}

class T6 extends Thread{
	@Override
	public void run() {
		new Easy5().test();
	}
}

执行结果:
T6开始————Thread-0
开始休眠————Thread-11s后显示下面结果)
从小黑屋放出来一个线程————Thread-1
T6结束————Thread-0



版权声明:本文为weixin_52509741原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。