1.多线程
1.并行与并发
- 并行:多个事件都在执行,并在某个时刻多个事件是同时执行。
- 并发:多个事件都在执行,但是在某个时刻多个事件没有同时执行。
2.进程与线程
- 进程:一个进程就是应用程序的一次执行。
- 线程:线程是进程的执行单元。一个进程中可以包含多个线程,一个进程起码有一个线程。
3.Thread类
-
构造方法
方法 说明 public Thread() 创建线程对象 public Thread(String name) 创建线程对象并指定线程名字 public Thread(Runnable target) 使用Runnable创建线程 public Thread(Runnable target,String name) 使用Runable创建线程并指定线程名字 -
常用方法
方法 说明 String getName() 获取线程的名字 void start() 开启线程,每个对象只调用一次start void run() run方法写线程执行的代码 static void sleep(long millis) 让当前线程睡指定的时间 static Thread currentThread() 获取当前线程对象
2.创建线程方式
1.继承方式
-
步骤
- 创建线程的子类
- 重写run方法
- 创建子类对象
- 使用子类对象调用start()开启线程
-
代码演示
public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(" "+i); } } } public class Demo继承方式 { public static void main(String[] args) { //创建子类对象 MyThread mt = new MyThread(); mt.start(); //创建子类对象 MyThread mt2 = new MyThread(); mt2.start(); //主线程要执行的代码 for (int i = 0; i < 100; i++) { System.out.println("我是主线程"+i); } } }
-
获取线程名字
getName(); 这个方法在父类Thread中有,所以子类可以直接调用
2.实现方式
-
步骤
- 创建子类实现Runnable接口
- 重写接口的run()方法
- 创建子类对象
- 创建线程Thread类对象,把子类作为参数传入构造方法中
- 调用线程的start()方法开启线程
-
代码演示
public class MyRun implements Runnable { @Override public void run() { //也是线程执行的代码 for (int i = 0; i < 100; i++) { //获取当前正在执行的线程对象 System.out.println(Thread.currentThread().getName() +" "+ i); } } } public class Demo实现方式 { public static void main(String[] args) { //创建子类对象 MyRun mr = new MyRun(); //创建Thread对象 Thread t = new Thread(mr); //开启线程 t.start(); t.setName("线程一"); //再开启一个线程 Thread t2 = new Thread(mr); //开启线程 t2.start(); t2.setName("线程二"); } }
-
获取线程名字
Thread.currentThread().getName() 因为接口的子类不能直接调用getName()方法,所以先获取当前线程再调用线程的获取姓名的方法
3.匿名内部类写法
-
匿名内部类的方式继承Thread类
public class Demo01匿名内部类写法 { public static void main(String[] args) { /* 匿名内部类格式: new 父类/接口(){ 方法重写 }; */ //继承的方式需要定义子类,使用匿名内部类方式代替 Thread t = new Thread(){ @Override public void run() { //会被新线程执行 for (int i = 0; i < 100; i++) { System.out.println(i); } } }; //开启线程 t.start(); for (int i = 0; i < 100; i++) { System.out.println("主线程" + i); } } }
-
匿名内部类的方式实现了Runable接口
public class Demo02匿名内部类写法2 { public static void main(String[] args) { //用匿名内部类的方式完成实现Runnable Runnable r = new Runnable(){ //方法重写 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i); } } }; //开启线程 Thread t = new Thread(r); t.start(); for (int i = 0; i < 100; i++) { System.out.println("主线程" + i); } } }
4.区别
在普通情况下你想用哪种就用哪种。
java中类是单继承的,如果一个类已经有一个父类了,那么只能实现一个接口而不能再去继承一个类。
匿名内部类的方式完全是为了简化代码,你想用就用不想用就不用。
3.高并发和线程安全
1.高并发和线程安全
-
高并发:在某个时间点上,有大量的用户(线程)同时访问同一个资源。比如:双11抢购,12306春运。
-
线程安全:在高并发情况下,数据可能处出现“数据污染”的情况。也就是说程序执行的效果和预想的结果不一样。线程和线程之间产生了互相的影响。
2.多线程内存机制
3.安全性问题-可见性
-
代码演示
public class MyThread extends Thread { public static int a = 0; @Override public void run() { //睡觉2秒钟 try { Thread.sleep(2000); } catch (InterruptedException e) { } //把a修改 a = 1; System.out.println("a的值已经被修改"); } } public class Demo { public static void main(String[] args) { //创建子类对象 MyThread mt = new MyThread(); mt.start(); while(true){ if(MyThread.a == 1){ System.out.println("嘿嘿嘿"); } } } } /* 应该a的值是1 但是主程序中没有输出嘿嘿嘿 */
-
图解
4.安全性问题-有序性
在代码编译期间,如果代码没有上下的逻辑关系,系统可能出出现代码重排的现象。他可能会先执行后面的代码再执行前面的代码。
5.安全性问题-原子性
-
代码演示
//创建子类 public class MyThread extends Thread { public static int a = 0; @Override public void run() { //a会被加了10000次 for (int i = 0; i < 10000; i++) { a++; } } } public class Demo { public static void main(String[] args) throws InterruptedException { //创建对象 MyThread mt = new MyThread(); //开启线程 mt.start(); //再开启线程 MyThread mt2 = new MyThread(); mt2.start(); //让主线程先睡两秒 Thread.sleep(2000); //打印变量的值 System.out.println(MyThread.a); } } 代码的正常逻辑应该是给静态变量加成了20000, 但是程序执行结果会小于20000 有些自增没有执行成功。
-
图解
4.volatile关键字
volatile可以修饰成员变量,被修饰变量不会出现可见性和有序性的问题。
public static volatile int a = 0;
不能解决原子性。
5.原子类
1.原子类示例
原子类可以用来解决多线程情况下可见性,有序性,原子性问题。
1).java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”; 2).java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”; 3).java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;
2.AtomicInteger演示
import java.util.concurrent.atomic.AtomicInteger;
//创建子类
public class MyThread extends Thread {
//public static int a = 0;
public static AtomicInteger a = new AtomicInteger(0);
@Override
public void run() {
//a会被加了10000次
for (int i = 0; i < 10000; i++) {
//a++;
a.getAndIncrement();
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
//创建对象
MyThread mt = new MyThread();
//开启线程
mt.start();
//再开启线程
MyThread mt2 = new MyThread();
mt2.start();
//让主线程先睡两秒
Thread.sleep(2000);
//打印变量的值
System.out.println(MyThread.a);
}
}
执行结果:
20000
3.AtomicInteger工作机制-CAS机制
4.AtomicIntegerArray类示例
-
使用普通数组可能出现原子性问题
import java.util.Arrays; public class Demo { public static void main(String[] args) throws InterruptedException { //创建了1000次线程,run方法执行了1000次 for (int i = 0; i < 1000; i++) { //创建子类对象 MyThread mt = new MyThread(); //开启线程 mt.start(); } //让循环先执行完 Thread.sleep(2000); System.out.println(Arrays.toString(MyThread.arr)); } } //子类 public class MyThread extends Thread { //定义数组 public static int[] arr = new int[10]; @Override public void run() { for (int i = 0; i < arr.length; i++) { arr[i]++; } } } 本来执行的结果应该全是1000,但是看到的效果是: [999, 999, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
-
使用原子类数组解决
import java.util.concurrent.atomic.AtomicIntegerArray; //子类 public class MyThread extends Thread { //定义数组 //public static int[] arr = new int[10]; public static AtomicIntegerArray arr = new AtomicIntegerArray(10); @Override public void run() { //for (int i = 0; i < arr.length; i++) { // arr[i]++; //} for (int i = 0; i < arr.length(); i++) { arr.addAndGet(i,1); } } } public class Demo { public static void main(String[] args) throws InterruptedException { //创建了1000次线程,run方法执行了1000次 for (int i = 0; i < 1000; i++) { //创建子类对象 MyThread mt = new MyThread(); //开启线程 mt.start(); } //让循环先执行完 Thread.sleep(2000); //System.out.println(Arrays.toString(MyThread.arr)); //使用循环遍历数组 for(int i=0; i < MyThread.arr.length();i++){ //get()获取i索引的元素 System.out.print(MyThread.arr.get(i)+ " "); } } }
总结
并行与并发:【理解】
并行:多个任务同时执行。
并发:多个任务都在执行但不同时执行。(交替执行)
进程与线程:【理解】
进程:应用程序的一次运行就是一个进程。
线程:线程是进制中的执行单元,一个进程可以有多个线程。
多线程创建方式:【记】
1.继承Thread类
步骤:
定义Thread的子类
重写run方法
创建子类对象
调用start开启线程
获取线程名称: 设置线程名字:
getName(); setName("线程名");
2.实现Runnable接口
步骤:
定义Runnable的子类
重写run方法
创建子类对象
创建线程对象,将子类对象传入到构造方法中
调用start开启线程
获取线程名字: 设置线程名字:
Thread.currentThread().getName(); setName("线程名");
可以使用匿名内部类代替上面的写法【理解】
匿名内部类的作用就是简化代码
你想写就写,不想这么写也可以。
继承和实现的区别:
实现的方式什么时候都能用。
继承的方式只要在类没有父类的情况才能用。
高并发和线程安全
【看懂上课的代码,不用做任何扩展】
三个问题:
可见性
一个线程没有看见另一个线程对变量的修改。
有序性
代码可能出现重排,对别的线程可能造成影响。
原子性
一句代码内部操作在多个线程之间出现互相影响。
问题的解决办法:
volatile关键字可以加在成员变量上
可以解决 可见性 和 有序性
不能解决原子性
原子类
AtomicInteger原子整数
AtomicIntegerArray原子整数数组类
可以解决原子性问题。
三个问题以及解决办法大家只需要把上课代码写一下就ok,不需要做任何扩展。
以后也绝对不会专门出现这些问题。