文章目录
- 多线程
- 概念
- 进程vs线程
- 多线程的优势/好处/使用场景
- 线程的状态
- 创建线程的方式
- 线程的启动
- Thread中,start()和run()有什么区别?
- Thread类中的常用方法
- join()
- 获取当前线程引用
- 线程休眠
- 线程中断
- 线程的属性
- 多线程效率
- 局部变量在多线程中的使用
- 线程安全问题
- 1.什么情况会产生线程不安全
- 2.线程不安全的原因是什么
- 如何解决线程不安全的问题
- 解决线程安全的方式一: synchronized关键字
- 解决线程安全的方式二: volatile关键字
多线程
概念
钱程的实现万式有多种:
(1)内核线程=>Java中的线程,是基于内核线程的轻量级实现(简单的说:轻量级进程)
(2)用户线程: 由程序自己来实现线程,包括线程的调度等等
进程vs线程
进程和线程的关系:
- 多个进程内存是隔离开的,一个进程中的多个线程,可以共享内存(进程包含线程)
- 进程是系统分配资源的最小单位;线程是系统调度cpu执行的最小单位。
- 线程的创建、销毁代价比进程小(状态转换进程的开销大于线程)
- 线程(由bug)可能会造成整个进程挂掉;进程间是独立运行(可能存在进程通信)
多线程的优势/好处/使用场景
- 充分利用CPU资源,提高执行效率。
- io等阻塞时(如果希望能同时接受数据)
缺陷、注意:
- 线程的创建和销毁也是具有一定的系统开销:一般用于执行耗时比较长的任务。
- 增加编码的复杂程度:执行顺序、线程安全问题。
线程的状态
java线程的状态
创建线程的方式
1.继承Thread
public class 继承Thread {
public static void main(String[] args) {
//继承的写法:1.定义一个类来继承Thread
//创建一个线程
MyThread myThread = new MyThread();
//运行一个线程:申请系统调度运行
myThread.start();
//继承的写法 2.使用一个匿名内部类
Thread t=new Thread(){
@Override
public void run() {
System.out.println("匿名内部类 run");
}
};
}
//继承Thread;重写run方法
private static class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread run");
}
}
}
2.实现Runnable接口
public class 实现Runnable {
public static void main(String[] args) {
Thread t1=new Thread(new MyRunnable());
t1.start();
//实现Runnable写法2. 匿名内部类
Runnable r=new Runnable(){
@Override
public void run() {
System.out.println("匿名内部类 run");
}
};
Thread t2=new Thread(r);
t2.start();
//变形 也可把匿名内部类对象,直接写在构造方法参数上
Thread t3=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类run");
}
});
t3.start();
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable run");
}
}
}
线程的启动
thread.start() =>申请系统调度,执行thread中的任务(重写的run方法)
Thread中,start()和run()有什么区别?
(1) start:启动线程的方式
(2) run:属于线程任务的描述
通过代码进一步分析其区别:
public class StartVsRun {
/**
* java StartVsRun:
* 1.执行java.exe进程,分配内存
* 2.创建java虚拟机,执行StartVsRun类加载
* 3.创建一个main线程,执行StartVsRun.main()
* @param args
*/
public static void main(String[] args) {
// while(true){
//
// }
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while(true){}
}
},"t线程");
t.start();
}
}
main线程创建Thread的代码,到底执行了哪些代码:没有执行run
多个线程,同时存在并发和并行的现象
即:以下代码打印语句结果顺序是无序的。main线程和t线程存在并发和并行。
Thread类中的常用方法
join()
使用join后,两个线程并发并行随机执行的方式,就变为有一定顺序
代码:
public class Threadjoin {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
// System.out.println("t线程 run");
while(true){}
}
},"t线程");
//当前线程(t.join在哪个线程执行,就是谁)等待,直到线程引用执行完毕
t.start();
// t.join();//main 等待,
t.join(3000);//不注释:t线程先打印,然后一直运行; main线程等3秒后打印
System.out.println("t线程 start,这里是main");
}
}
获取当前线程引用
一般上下文语义,是描述某一行,说当前线程——某行代码所在的线程
public class CurrentThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//在哪个线程中,执行,currentThread返回的就是哪个线程
System.out.println(Thread.currentThread().getName());
}
}, "子线程").start();
System.out.println(Thread.currentThread().getName());
//打印结果
//main
//子线程
}
}
线程休眠
调试代码可用sleep()方法调试
public class Sleep {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
线程中断
自定义标志位,能够实现某些条件的中断
但如果线程处于等待/超时等待/阻塞,就没法中断
public class 自定义标志位 {
//设计一个标志位:表示是否被中断
private static boolean isInterrupt=false;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
//循环10次。每次打印次数,休眠1s
try {
for(int i=0;i<10&&!isInterrupt;i++){
System.out.println(i);
//自定义标志位,能够实现某些条件的中断
//但如果线程处于等待/超时等待/阻塞,就没法中断
//修改时间为100s,就无法中断
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(3000);
isInterrupt=true;
}
}
public class Interrupt {
//设计一个标志位:表示是否被中断
private static boolean isInterrupt=false;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
//循环10次。每次打印次数,休眠1s
//获取当前线程的中断标志位:这样写,只是获取到一个不变标志位
// boolean isInterrupt=Thread.currentThread().isInterrupted();
// for(int i=0;i<10&&!isInterrupt;i++){
try {
while (!Thread.currentThread().isInterrupted()) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}});
t.start();
Thread.sleep(3000);
t.interrupt();
}
}
注意:
(1) Java中断,是以中断标志位的方式来执行
(2) interrupt是发起中断的动作,但线程是否中断,由自己代码是否判断标志位来决定
(3)线程如果处于某些等待/超时等待的方式(会显示的抛InterruptedException),都是允许被中断
中断的方式是:1.抛异常来中断⒉抛异常以后,会重置/还原中断标志位
线程的属性
public class 获取线程属性 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while(true){}
}
});
t.setDaemon(true);//为后台线程
t.start();
System.out.println(t.getId());
System.out.println(t.getName());
System.out.println(t.getState());//获取线程状态
System.out.println(t.getPriority());
System.out.println(t.isDaemon());//是否后台线程
System.out.println(t.isAlive());//是否存活,如果
//后台线程:java进程,至少有一个非后台线程(用户线程)存活,进程才不会退出
//如果把t线程在启动前,设为后台线程,进程不会管t线程是否执行完
}
}
多线程效率
执行一段任务,如何确定多线程的线程数量?
由于系统中,可能有很多需要耗费cpu资源的进程,可能对本java进程造成影响
我们这里探讨的前提:系统内存满足java进程,希望Java进程执行的任务能充分利用cpu资源(占满)
局部变量在多线程中的使用
局部变量在另一个线程无法使用,为什么?
public class Demo {
//静态变量i:方法区; 0:Integer缓存(堆)
private static int i = 0;
//常量i2: 方法区; 1:同上
private static final int i2 = 1;
//静态变量o:方法区, new Object(堆)
private static Object o = new Object();
//实例变量i3:和对象生命周期一样; 3:同上
private int i3 = 3;
public static void main(String[] args) {
//x: 栈,3: 栈
int x = 3;
//ox: 栈; new Object堆
Object ox = new Object();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(x);
// x = 4;
}
});
t.start();
for (int j = 0; j < 10; j++) {
Thread tt = new Thread(new Runnable() {
@Override
public void run() {
// System.out.println(j);
}
});
tt.start();
}
}
}
局部变量是属于线程私有的
线程安全问题
加里多线程情况下代码运行的结果符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
1.什么情况会产生线程不安全
2.线程不安全的原因是什么
如何解决线程不安全的问题
(1)某个线程,对共享变量的写操作,可以加锁来保证线程安全
(2)如果某个线程,对共享变量是读操作,使用volatile关键字就可以保证线程安全
如果对共享变量的写操作,不依赖任何共享变量,也可以使用volatile保证线程安全
解决线程安全的方式一: synchronized关键字
synchronized作用:
多个线程,使用同一个对象进行加锁(基于对象头加锁),可以实现线程间的同步互斥
同步: 多个线程,按序执行同步代码(synchronized花括号的部分)
互斥: 多个线程间,执行同步代码具有互斥的特点
注意:如果不同的线程,是申请不同对象的锁,就没有同步互斥的作用(还能并发并行的执行)
synchronized保证原子性、可见性、有序性
互斥:就能满足原子性(某个线程执行同一个对象加锁的同步代码,排斥另一个线程加锁,满足最小执行单位)
刷新内存: synchronized结束释放锁,会把工作内存中的数据刷新到主存;其他线程申请锁,获取的始终是最新的数据(满足可见性)
有序性:某个线程执行一段同步代码,不管如何重排序,过程中不可能有其他线程执行的指令
这样多个线程执行同步代码,就是满足一定的顺序(大家依次执行)
线程安全版代码
两个变量对同一变量执行++操作。
public class synchronized线程安全 {
private static int count;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (synchronized线程安全.class) {
count++;
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (synchronized线程安全.class) {
count++;
}
}
}
});
t1.start();
t2.start();
//main线程等待t1,t2都执行完
t1.join();
t2.join();
//看看count的值
System.out.println(count);
}
}
解决线程安全的方式二: volatile关键字
语法
修饰某个变量(实例变量,静态变量)
作用
(1) 保证可见性:多个线程对同一个变量的操作,具有可见性
(2)禁止指令重排序,建立内存屏障
使用场景:
共享变量的读操作,及常量赋值操作(原因:这些操作本身就具有原子性)
某些即存在读,也存在写的操作:
简单的满足线程安全:就是整个代码全部加锁–效率会稍微低一点
优化方案:对共享变量的读使用volatile保证线程安全,写操作加锁——满足线程安全,效率也高