线程
进程与线程_并行与并发的概念
- 进程:是“操作系统”的概念,指一个“独立运行的程序”,由操作系统管理。
- 线程:是由“进程”创建。指一个“进程”中可以将一段代码分离出来,与“主进程”“同时”执行。这样可以使我们的程序同时做多件事情,提高程序的运行效率。
其实Java程序本身都是“多线程”的。JVM启动(进程),会加载我们的main()方法,会被JVM以“线程”的方式执行。
“垃圾回收器(GC)”也会被JVM以“线程”的方式在运行。 - 并发:一颗CPU,多个线程;
- 并行:两颗CPU,两个线程;
创建线程的方式一_继承Thread类
-
自定义线程,继承自Thread,并重写“run()”方法;
-
启动线程:
- 创建自定义线程对象;
- 调用自定义线程对象的“start()”方法,启动线程。
注意:
1).一个“线程类”,可以创建多个“线程对象”,每个“线程对象”都可以独立的启动、运行。
2).每个“线程对象”只能start()一次,运行完毕,会自动成为垃圾。
3).在“线程”中,重写的是“run()”方法,而启动线程调用的是“start()”方法。
代码演示:
- 自定义线程
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("敌人开枪:" + i);
}
}
}
- 测试类
public class Demo01 {
public static void main(String[] args) {
//做一个游戏,有两个人物:我、敌人
//两个人相互开枪,各开30枪
//启动敌人线程
MyThread t = new MyThread();
t.start();
//启动线程后,主线程会继续向下走
//我开枪
for (int i = 0; i < 30; i++) {
System.out.println("我开枪:" + i);
}
}
}
创建线程的方式二_实现Runnable接口
步骤:
1. 自定义类,实现Runnable接口,并重写run()方法;
2. 启动线程:
1).创建自定义类对象;
2).创建一个Thread对象,并且要将自定义对象作为实参传给Thread的构造方法;
3).调用Thread对象的start()启动线程。
注意:
可以只创建一个Runnable子类对象,然后传给几个Thread对象,每个Thread对象可以单独启动,独立运行;
代码演示
- 自定义类,实现Runnable接口,并重写run():
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("敌人开枪:" + i);
}
}
}
- 测试类:
public class Demo02 {
public static void main(String[] args) {
//1.创建自定义对象
MyRunnable myRun = new MyRunnable();
//2.创建Thread对象,将MyRunnable委托给Thread去执行
Thread t = new Thread(myRun);
Thread t2 = new Thread(myRun);
//3.启动线程
t.start();
t2.start();
//启动线程后,主线程继续运行
for (int i = 0; i < 30; i++) {
System.out.println("我开枪:" + i);
}
}
}
继承Thread与实现Runnable两种创建多线程方式的对比
- 需要定义线程类,继承自Thread类,而Java是单继承,这样就对子类形成了限制,以后再想继承其他类,就不可以了。
- 需要定义类,实现Runnable接口,对子类没有限制,子类仍然可以继承其他类,也可以同时实现多个接口。
匿名内部类的方式实现线程
- 匿名内部类的格式:
new 父类/父接口(){
//子类类体
} - 两种使用匿名内部类的格式:
- 继承Thread的匿名内部类的形式:
new Thread( ){
//子类类体
}.start(); - 实现Runnable接口的匿名内部类的形式:
new Thread(new Runnable(){
//子类类体
}
).start();
- 继承Thread的匿名内部类的形式:
代码演示:
public class Demo03 {
public static void main(String[] args) {
//1.new Thread(){ //子类类体 }.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("敌人开枪:" + i);
}
}
}.start();
//2.new Thread(new Runnable(){//Runnable的子类类体}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("狙击手开枪:" + i);
}
}
}).start();
for (int i = 0; i < 30; i++) {
System.out.println("我开枪:" + i);
}
}
}
Thread类的常用方法
- 当我们使用第一种方式的时候,自定义类,继承了Thread,也会继承一些方法:
1).run():线程的工作方法。
2).start():线程启动的方法。
3).getName():获取线程名称。任何一个线程天生就有名字:Thread-[索引]
4).setName(String name):设置线程名称。
5).public static void sleep(long m):让当前线程休息指定的毫秒值。
6).public static Thread currentThread():获取当前的Thread对象。
代码演示
public static void main(String[] args) {
//使用匿名内部类的方式实现线程
new Thread(){
@Override
public void run() {
for (int i = 0; i < 30; i++) {
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(format);
try {
Thread.sleep(1000);//让当前线程休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
高并发及线程安全
多线程的运行原理
- 当多个线程同时运行时,相互独立,互不干扰。
图解代码
高并发及线程安全的概念
- 什么是“高并发”:指多个线程在瞬间同时访问系统的情况。例如:秒杀、12306。
- 线程安全:如果多个线程无序执行,这多个线程还同时访问一个资源(一个商品、一个变量、一个文件…),由于多个线程无序、不可控的,这样就可能造成最终的结果不是我们预期的结构,这样就造成了业务数据错误、系统异常。
- 在多线程高并发情况下会产生的线程安全性问题:
1).访问变量的:可见性问题
2).访问代码的:有序性问题
3).访问代码的:原子性问题
高并发问题一:可见性
指:多个线程访问同一个变量时,一个线程对变量进行了修改,而第二个线程由于访问这个变量的速度太快,不能及时看到这个变量被其它线程修改了。
这是Java多线程的“运行机制”导致的。
代码演示:
package com.itheima.demo07_多线程运行的原理;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(this.getName() + "敌人开枪:" + i);
}
}
}
测试类
package com.itheima.demo07_多线程运行的原理;
public class Demo07 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
代码图解:
Java内存模型JMM
每个线程一个“独立的栈区”,每个线程的代码都在各自的“栈区”内部运行,相互独立,互不干扰。
代码图解
结合可见性问题看内存图
高并发问题二:有序性
-
有序性:指:编译器在不影响最终结果的前提下,在编译时,会对某些代码进行“重排”,为了提高效率。
例如:
int a = 10;
int b = 20;
int c = a + b;//前两行代码的编译顺序不会影响的最终结果,这时为了提高效率,编译器可能会对上面的两行代码进行重排。int b = 20; int a = 10; int c = a + b;
-
但是,在“多线程环境下”,如果对代码进行重排,可能会造成不同的结果,这是我们不希望看到的。
代码图解
高并发问题三:原子性
代码演示
package com.itheima.demo09_高并发问题三_原子性;
public class MyThread extends Thread {
//1.定义一个"公有、静态"变量
//为了让多个线程访问的是同一个变量
public static int money = 0;
//2.run()方法
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
money++;
}
System.out.println(this.getName() + " 完成");
}
}
测试类
package com.itheima.demo09_高并发问题三_原子性;
public class Demo09 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start(