一、 线程
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-0
(5s后)
结束————Thread-0
开始————Thread-1
(5s后)
结束————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-1
(1s后显示下面结果)
从小黑屋放出来一个线程————Thread-1
T6结束————Thread-0