多线程
进程
一个程序对应一个进程
线程
进程是程序的执行路径,程序当前不同动作
并发
充分利用CPU,线程切换速度快,系统运行多线程程序,单核
并行
多核,同时做不同的事
线程的五个状态
新建状态 | 就绪状态 | 运行状态 | 阻塞状态 | 终止状态 |
---|---|---|---|---|
new Thread | tread.start | tread.run | tread.sleep/wait | tread.stop |
jion():线程强制运行
其中主线程比子线程的优先级高,可以通过让某个线程强制先运行完成
sleep(秒数):线程睡眠
某个线程主动阻塞,让出资源给其他线程运行,时间到后自动进入到线程队列等待运行
interrupt();中断线程
主动将线程中断
isinterrupted();判断线程是否中断
返回线程是否中断状态
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
if (Thread.currentThread().isInterrupted()){
break;
}
System.out.println(i);
}
});
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
t1.interrupt();
//判断线程是否中断
System.out.println(t1.isInterrupted());
}
在线程睡眠时将线程中断
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
//抛出异常,用来结束线程
}
System.out.println(i);
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();//如果线程在线程休眠时被中断,无法结束,可以通过抛出异常方法结束
System.out.println(t1.isInterrupted());
}
这将会出现睡眠异常,sleep()被打断,所以sleep就直接抛出终止异常
创建子线程
使用Runnable类先创建,然后传入Thread里面
启动线程即可
使用内部类进行
写资源类,调用时才写线程,写一个内部类,内部类强制重写抽象方法
Thread.run();纯粹是调用run方法,不开启新线程,相当于单线程运行
匿名内部类
new Thread("窗口"+i){
@Override
public void run() {
for (int j = 0; j < 100; j++) {
ticket.sale();
}
}
}.start();
使用接口进行创建
当接口里面只有一个方法时可以使用箭头函数写
开发通常使用接口来创建
new Thread(() -> {
for (int j = 0; j < 100; j++) {
ticket.sale();
}
},"窗口"+i).start();
多个线程同时调度一个资源
加上一个同步锁synchronized,保证线程同步,作用在方法上,此方法同一个时刻只允许一个线程执行
同步方法
作用在方法上,此方法同一时刻只允许一个线程执行,其他线程进行等待,直到当前线程完成
效率太慢,一般不使用,因为不方便,让其他的线程等待了
public synchronized void sale(){
if (num > 0){
num--;
System.out.println(Thread.currentThread().getName()+"还剩"+num+"张票");
}
}
同步对象
同步代码块,给一段代码上锁,锁可以是任何对象,但是必须是多个线程共同拥有的对象,即多个线程拥有的是同一把锁,这样子才可以起到互斥作用,上锁之后,代码块同时只能被一个线程执行
this表示当前对象,或者可以指定现在的是哪个类,对该对象进行上锁,直接类名.class
public static void sale1(){
/**
* 同步代码块,给一段代码上锁,锁可以是任何对象,
* 但是必须是多个线程共同拥有的对象,
* 即多个线程拥有的是同一把锁,这样子才可以起到互斥作用
* 代码块同时只能被一个线程执行
*
* this表示当前对象,或者可以指定现在的是哪个类,直接类名.class
*/
synchronized (Ticket.class){
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"还剩"+(num--)+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程不安全
当线程不安全时,会造成其中的值出现混乱,可能多个线程对当前变量的原来的值进行操作,对变量操作还是使用同步对象,加上一个锁,可以保证安全
/**
* volatile修饰词,变量值发生改变后对其他线程可见
* 可以保证可见性、有序性、不保证原子性
* 1、可见性
* 2、有序性
* 3、原子性
*
* synchronize关键字,保证原子性,有序性,
* 使得每个线程都可以单独调用该代码块
*/
private volatile int i;
volatile关键字
public class Test07 {
private static volatile boolean flag = true;
/**
* volatile关键字的使用:当main线程改变属性值时,子线程立马可见
* 主要为:让其他线程立马可见
* @param args
*/
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
System.out.println();
}
}).start();
Thread.sleep(1000);
flag = false;
}
}
线程安全的操作
检验i++不是原子操作,在多线程环境下需要同步,类似的还有:ArrayList、HashMap等都不是线程安全的,vector,Hashtable线程安全
public class ArrayListTest {
public static void main(String[] args) throws InterruptedException {
//使用ArrayList时会出现相加的时候,到达不了多线程所加的数值
// ArrayList<Integer> list = new ArrayList<Integer>();
Vector<Integer> list = new Vector<Integer>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
list.add(j);
}
}).start();
}
Thread.sleep(5000);//休眠五秒
System.out.println(list.size());
}
}
线程同步例子——唤醒线程和等待线程
创建两个线程,交替输出0,1数字,不带缓冲区
main函数
/**
* 创建两个线程,交替输出0,1数字,不带缓冲区
*/
public class Test08 {
public static void main(String[] args) {
NumberExchange ne = new NumberExchange();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
ne.incr(); //每次都只调用一次这个方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"加法").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
ne.decr(); //每次都只调用一次这个方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"减法").start();
}
}
操作类
但是当前类在多个线程同时调用时,会出现虚假唤醒
比如多个加法类同时调用时,if只会判断一次,会出现虚假唤醒,就会出现大于1时还会继续调用该方法,造成大于1出现
public class NumberExchange {
private int num;
//线程等待直到唤醒,必须写在同步方法或者同步代码块中,在指定的监听器对象上进行等待
public synchronized void incr() throws InterruptedException {
if (num > 0) //大于零则线程等待,否则加1
this.wait(); //需要同一个对象的方法,只有同一个对象才可以锁住
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
//唤醒减法线程,唤醒在制定监听器对象上等待的线程
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (num < 1)
wait();
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
this.notifyAll();
}
}
若是要解决虚假唤醒的情况,可以使用以下的方法
使用while循环判断是否满足条件,若是已经超过了,则继续等待
操作类
public class NumberExchange {
private int num;
//线程等待直到唤醒,必须写在同步方法或者同步代码块中,在指定的监听器对象上进行等待
public synchronized void incr() throws InterruptedException {
while (num > 0) //大于零则线程等待,否则加1
this.wait(); //需要同一个对象的方法,只有同一个对象才可以锁住
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
//唤醒减法线程,唤醒在制定监听器对象上等待的线程
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while (num < 1)
wait();
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
this.notifyAll();
}
}
今日总结
线程资源同步
线程中断
线程安全
线程等待
线程唤醒
线程虚假唤醒
线程完全唤醒