第一章 多线程的概念
1.1线程与进程
进程:
是指一个内存中运行的应用程序, 每个进程都有一个独立的内存空间, 一个应用程序可以同时运行多个程序;
进程是程序的一次执行过程,是系统运行程序的进本单位;
线程:
是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,也可以有多个线程;
进程与线程的区别:
进程:有独立的内存空间,进程中数据存放空间(堆内存与栈内存)是独立的,至少有一个线程.
线程:堆空间是共享的, 栈空间是独立的,线程消耗的资源比进程小得多;
第二章 线程的创建--继承方式
2.1继承Thread类方式
java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.
java中通过继承Thread类来创建并启动多线程的步骤如下:
1. 定义Thread类的子类, 并重写该类的run()方法; 该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体;
2. 创建Thread子类的实例, 即创建了线程对象
3. 调用线程对象的start()方法来启动该线程(即调用Thread类的start方法);
代码实例:
自定义线程类:
public class MyThread extends Thread{
/*
重写run方法, 完成该线程程序执行的逻辑
*/
@Override
public void run(){
for(int i = 0; i< 200; i++){
System.out.println("自定义线程正在执行"+i);
}
}
}
测试类:
public static void main(String[] args){
//创建自定义线程对象
MyThread mt = new MyThread();
//开启新线程
mt.start();
//在主方法中执行for循环
for(int i = 0; i< 200;i++){
System.out.println("main线程"+i);
}
}
2.2 run()方法和start()方法
run()方法:
是线程执行的任务方法,每个线程都会调用run()方法执行,我们将线程要执行的任务代码都写在run()方法中就可以被线程调用执行。
start()方法:
开启线程,线程调用run()方法;
注意: 一个线程只能被启动一次!!
2.3 线程名字的设置与获取
String getName()可以获取到线程的名字。
setName(String name)设置线程的名字。
通过Thread类的构造方法Thread(String name)也可以设置线程的名字
获取运行main方法线程的名字:
static Thread CurrentThread() 获取当前线程对象
代码实例:
public class MyThread extends Thread{
public void run(){
System.out.println("线程名称:"+getName());
System.out.println("线程名称:"+Thread.CurrentThread().getName());
}
}
测试类:
public class Demo{
public static void main(String[] args){
//创建自定义线程对象
MyThread mc= new MyThread();
//设置线程名字
mc.setName("旺财");
//开启新线程
mc.start();
}
}
注意:线程是有默认名字的,如果我们不设置线程的名字,JVM会赋予线程默认名字Thread-0,Thread-1;
为什么要继承Thread类?
Thread类就是线程类, 具有线程最基本的属性与功能; 比如 设置线程名 开启线程
继承Thread类 就可以直接使用这些功能;
那为什么不直接创建Thread类对象 开启线程?
Thread t = new Thread();
t.start();
这样虽然开启了线程 但是运行的是Thread类的main方法, 这个run方法中执行的内容
并不是我们想要的, 所以我们继承Thread类 重写run方法 这样运行的就是我们自己的run方法;
第三章 线程的创建--实现方式
3.1 线程创建的第二种方式 实现Runnable接口
1. 定义实现类, 实现 Runnable接口
2. 重写run方法(线程任务)
3. 开启线程
创建实现类对象
创建Thread对象 传入线程任务
public Thread(Runnable r)
调用Thread的start方法 开启线程
代码实例:
public class MyRunnable implements Runnable{
public void run (){
for(int i = 0; i < 20; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
测试类:
public class Demo{
public static void main(String[] args){
//创建(自定义类)实现类对象, 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象(将要执行的线程任务 传递给Thread对象)
Thread t = new Thread(mr);
t.start();
for(int i = 0; i < 20; i++){
System.out.println(" main "+i);
}
}
}
为什么有了继承的方式 还要有实现接口的方式?
1. 实现Runnable接口的方式 避免了单继承的局限性
如果一个类已经有了父类 还想开线程 只能通过实现接口的方式
2. 解耦合 线程任务与线程对象分离
3. 更符合面向对象 更容易实现 多个线程共享一个任务
3.2 匿名内部类方式创建线程
使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法
代码演示:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:"+i);
// }
// }
// }; //---这个整体 相当于new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("张宇:"+i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("费玉清:"+i);
}
}
}
第四章 Thread类API
4.1 睡眠sleep方法
public static void sleep(long time); 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行
代码演示:
public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);
System.out.println(i)
}
}
}
4.2 设置线程优先级(了解)
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高.线程提高了3个常量来表示最低,最高,以及默认优先级:
public static final int MIN_PRIORITY //1 最低优先级
public static final int NORM_PRIORITY //5 默认优先级
public static final int MAX_PRIORITY //10 最大优先级
public final void setPriority(int newPriority)更改线程的优先级。
4.3 用户线程与守护线程(了解)
用户线程与守护线程
我们正常写的代码都是用户线程
守护线程
用户线程存在 守护线程可以执行 用户线程执行完 守护线程即使没执行完 jvm也直接退出
GC就是一个守护线程
public final void setDaemon(boolean on) on的值为true将线程设置为守护线程, 注意一定要在开启线程之前设置
代码演示:
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 "+i);
}
}
});
//如果不设置t1为守护线程 由于t1睡眠时间比t2睡眠时间长
//t1一定是后执行完 ,当t2全部执行结束后,t1再慢慢将剩下的执行完
//但是如果将t1设置为守护线程 当t2执行完毕后,t1也不再执行了 直接退出jvm
//t1.setDaemon(true);//设置t1为守护线程
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 "+i);
}
}
});
t2.start();
}
}
4.4 join方法
join方法是让 当前线程等待,调用方法的线程进行插队先执行,执行完毕后,在让当前线程执行.
对其他线程没有任何影响.注意 此处的当前线程不是调用方法的线程 而是Thread.currentThread().
代码演示:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("t1 "+i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("t2 "+i);
}
}
});
t1.start();
//注意 此时当前线程是main线程
//main线程进入等待,让t1线程执行. 由于此时main等待 所以t2还没有执行
//t1执行完毕后 main和t2抢夺cpu资源 执行
// t1.join();
t2.start();
//如果t1.join放在这里 那么main进入到等待 t2已经开启了
//t1 t2互相抢夺资源
//main一定是在 t1执行完毕后 才会执行
//那么就会有两种情况 t1执行完毕 t2没执行完 main和 t2进行抢夺
//t1执行完毕 t2也执行完毕了 main自己执行
//t1.join();
for (int i = 0; i < 10; i++) {
System.out.println("main "+i);
} }
}
面试题:
现在有T1,T2,T3 三条线程,同时开启,如何保证T2在T1执行完毕后执行,T3在T2执行完毕后执行?
代码实例:
public class Test02 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("t1 "+i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//如果t2抢到,等待 让t1先执行
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("t2 "+i);
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//如果t3抢到,等待 让t2先执行
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("t3 "+i);
}
}
});
t1.start();
// t1.join();
t2.start();
// t2.join();
t3.start();
}
}
4.5 线程停止(了解)
public final void stop() 直接中断线程 此方法已过时 不安全
public boolean isInterrupted() 获取线程是否中断的状态 如果中断返回true 没中断返回false
public void interrupt() 中断线程 此时调用interrupted方法 会返回true
代码演示:
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <1000000 ; i++) {
System.out.println(i);
//是否有人要中断线程 如果有返回true 如果没有返回false
//让线程中断更为平滑 可以使用代码来控制中断
boolean b = Thread.currentThread().isInterrupted();
if(b){
break;
}
}
}
});
t1.start();
try {
Thread.sleep(2000);
// t1.stop(); //方法已经过时 不安全
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
第五章 线程安全
5.1 线程安全问题
线程安全问题引发
线程安全问题引发:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,
一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全
5.2 线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(**synchronized**)来解决。
三种方法为:
1.同步代码块
2.同步方法
3.锁机制
5.2.1同步代码块
同步代码块:线程操作的共享数据进行同步。synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
同步锁又称为对象监视器。同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1. 锁对象 可以是任意类型。
2. 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
代码演示:
public class Ticket implements Runnable{
private int ticket = 100;
private Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
}
5.2.2 同步方法
同步方法:当一个方法中的所有代码,全部是线程操作的共享数据的时候,可以将整个方法进行同步。使用synchronized修饰的方法,就叫做同步方法,
保证A线程执行该方法的时候,其他线程只能在方法外等着
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
使用同步方法代码如下:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
5.2.3 lock锁
java.util.concurrent.locks.Lock机制提供了比*synchronized*代码块和*synchronized*方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() `:加锁。
public void unlock()`:释放锁
代码演示:
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
lock.unlock();
}
}
}
第六章 线程状态
6.1 线程状态概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
| 线程状态 | 导致状态发生条件 |
| ----------------------- | ------------------------------------------------------------ |
| NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
| Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
| Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
| Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
| Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
6.2 等待与唤醒
Object类的方法
public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.
代码演示:
public class Demo1_wait {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}
}).start();
}
public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
代码演示:
public class Demo2_notify {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}
}).start();
//步骤2: 加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
Thread.sleep(3000);
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized ("") {
System.out.println("唤醒");
"".notify();
}
} catch (Exception e) {
}
}
}).start();
}
}