多线程快速入门
一、线程与进程的区别
二、为什么要用到多线程
三、多线程应用场景
四、使用继承方式创建线程
五、使用Runnable接口方式创建线程
六、使用匿名内部类方式创建线程
七、多线程常用api
八、守护线程与非守护线程
九、多线程几种状态
十、join方法介绍
十二、使用多线程分批处理信息
多线程之间通讯
一、多线程之间通讯
多线程之间通讯需求
需求:第一个线程写入(input)用户,另一个线程读取(output)用户,实现一个读,一个写操作。
/** * 共享资源实体类 */ class Res{ public String userName; public String sex; } /** * 写的线程 */ class Input extends Thread{ private Res res; public Input(Res res){ this.res = res; } @Override public void run() { // 写的操作 int count = 0; while (true){ if(count == 0){ res.userName = "小红"; res.sex = "女"; }else{ res.userName = "小明"; res.sex = "男"; } // 计算奇数或者偶数公式 count=(count+1)%2; } } } /** * 读的线程 */ class Output extends Thread{ private Res res; public Output(Res res){ this.res = res; } @Override public void run() { while (true){ System.out.println(res.userName+","+res.sex); } } } /** * Created by yz on 2018/04/02. */ public class OutputAndInputThread { public static void main(String[] args) { // 多个线程共享同一资源 Res res = new Res(); Input input = new Input(res); Output output = new Output(res); input.start(); output.start(); } }
运行结果:
原因解析:小红明明定义的是女的,但是因为两个线程同时操作Res对象,一个写,一个读,当读到小红性别时,这时候写的线程将姓名赋值为小明,性别赋值为男,读的线程读取到性别男,所以产生线程安全问题。
解决:两个线程不能在同一时刻操作资源,读的时候,不要写,使用同一把锁。
/** * 写的线程 */ class Input extends Thread{ private Res res; public Input(Res res){ this.res = res; } @Override public void run() { // 写的操作 int count = 0; while (true){ synchronized (res){ if(count == 0){ res.userName = "小红"; res.sex = "女"; }else{ res.userName = "小明"; res.sex = "男"; } // 计算奇数或者偶数公式 count=(count+1)%2; } } } } /** * 读的线程 */ class Output extends Thread{ private Res res; public Output(Res res){ this.res = res; } @Override public void run() { while (true){ synchronized (res){ System.out.println(res.userName+","+res.sex); } } } }
二、wait()、notify()区别
wait()、notify()、notifyAll()这三个定义Object类里的方法,可以用来控制线程状态。
这三个方法都是jvm里native方法。同步中使用。
如果对象调用了wait方法,就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
如果对象调用了notify方法,就会通知某个正在等待的这个对象的控制权的线程可以继续运行。
如果对象调用了notifyAll方法,就会通知所有等待这个对象控制权的线程继续运行。
使用synchronized关键字虽然解决了线程同步,但是现在没有按照写一个读一个的顺序执行:
解决办法:生产者线程生产一个,消费者线程立马消费。生产者没有任何生产时,消费者不能读。消费者没有消费完,生产者不能再继续生产。wait()和notify() 一起使用,一个当前线程等待,一个唤醒线程
/** * 共享资源实体类 */ class Res{ public String userName; public String sex; // true 生产者线程等待,消费者线程消费; false 生产者线程生产,消费者线程等待 public boolean flag = false; } /** * 写的线程 */ class Input extends Thread{ private Res res; public Input(Res res){ this.res = res; } @Override public void run() { // 写的操作 int count = 0; while (true){ synchronized (res){ if(res.flag){ try { // 让当前线程从运行状态变为休眠状态 并且释放锁的资源 res.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(count == 0){ res.userName = "小红"; res.sex = "女"; }else{ res.userName = "小明"; res.sex = "男"; } // 计算奇数或者偶数公式 count=(count+1)%2; res.flag = true; // 唤醒对方等待的线程 res.notify(); } } } } /** * 读的线程 */ class Output extends Thread{ private Res res; public Output(Res res){ this.res = res; } @Override public void run() { while (true){ synchronized (res){ if(!res.flag){ try { // 生产者线程生产时,消费者线程等待 res.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(res.userName+","+res.sex); res.flag = false; // 唤醒对方等待的线程 res.notify(); } } } } /** * Created by yz on 2018/04/02. */ public class OutputAndInputThread { public static void main(String[] args) { // 多个线程共享同一资源 Res res = new Res(); Input input = new Input(res); Output output = new Output(res); input.start(); output.start(); } }
实现效果,生产一个,读取一个:
wait与sleep区别是什么?
wait 用于同步中,可以释放锁的资源。Object类。
sleep 不会释放锁的资源。Thread类
wait 需要notify才能从休眠状态变为运行状态
sleep 时间到期,从休眠状态变为运行状态
wait和sleep相同功能都是做休眠。
三、jdk1.5Lock锁
synchronized(res){
}
从什么时候开始上锁 代码开始
从什么时候释放锁 代码结束
synchronized是内置锁 自动锁
缺点:效率低、扩展性不高、不能自定义
JDK1.5并发包
java.util.concurrent.atomic
java.util.concurrent.locks
Lock锁 保证线程安全问题
lock锁与synchronized同步锁区别:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
lock锁手动开启上锁,手动释放锁 是接口
synchronized 程序有异常自动释放锁 是jvm关键字
多线程并发(多个Thread操作同一资源)与网站并发(多个请求同时请求一台服务器)
Lock lock = new ReentrantLock(); 重入锁,重复使用
wait、notify只能在synchronized中使用
Condition用法
Condition的功能类似于在传统的线程技术中Object.wait()和Object.notify()的功能。
代码:
Condition condition = lock.newCondition();
res.condition.await();类似wait
res.Condition.Signal(); 类似notity
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 共享资源实体类 */ class Res{ public String userName; public String sex; // true 生产者线程等待,消费者线程消费; false 生产者线程生产,消费者线程等待 public boolean flag = false; // ReentrantLock重入锁 Lock lock = new ReentrantLock(); } /** * 写的线程 */ class Input extends Thread{ private Res res; // Condition的功能类似于在传统的线程技术中Object.wait()和Object.notify()的功能 Condition condition; public Input(Res res,Condition condition){ this.res = res; this.condition = condition; } @Override public void run() { // 写的操作 int count = 0; while (true){ try { // 表示开始上锁 res.lock.lock(); if(res.flag){ condition.await(); // 类似wait } if(count == 0){ res.userName = "小红"; res.sex = "女"; }else{ res.userName = "小明"; res.sex = "男"; } // 计算奇数或者偶数公式 count=(count+1)%2; res.flag = true; condition.signal(); // 类似notity } catch (Exception e) { e.printStackTrace(); }finally { // 释放锁 如果异常永远释放不了一定要try catch res.lock.unlock(); } } } } /** * 读的线程 */ class Output extends Thread{ private Res res; // Condition的功能类似于在传统的线程技术中Object.wait()和Object.notify()的功能 Condition condition; public Output(Res res,Condition condition){ this.res = res; this.condition = condition; } @Override public void run() { while (true){ try { // 表示开始上锁 res.lock.lock(); if(!res.flag){ condition.await(); // 类似wait } System.out.println(res.userName+","+res.sex); res.flag = false; condition.signal();// 类似notity } catch (Exception e) { e.printStackTrace(); }finally { // 释放锁 如果异常永远释放不了一定要try catch res.lock.unlock(); } } } } /** * Created by yz on 2018/04/02. */ public class OutputAndInputThread { public static void main(String[] args) { // 多个线程共享同一资源 Res res = new Res(); Condition condition = res.lock.newCondition(); Input input = new Input(res,condition); Output output = new Output(res,condition); input.start(); output.start(); } }
lock锁比同步代码块效率高
四、怎么停止线程
为什么stop方法不好?
程序没有执行完,stop的话,程序不会回滚,不可恢复,不安全。
设计怎么停止线程思路?
代码执行完毕。经常循环、无非让循环停止,这个线程就停止。
/** * Created by yz on 2018/4/3. */ public class StopThread { public static void main(String[] args) throws InterruptedException { StopThreadDemo stopThreadDemo = new StopThreadDemo(); stopThreadDemo.start(); for (int i = 1; i < 10; i++) { System.out.println("我是主线程,i:"+i); Thread.sleep(1000); if(i == 3){ // 停止线程 interrupt()让当前等待的线程直接抛出异常,并不是停止线程 stopThreadDemo.interrupt(); // stopThreadDemo.stopThread(); //stopThreadDemo.stopThread(); } } } } class StopThreadDemo extends Thread{ private volatile boolean flag = true; @Override public synchronized void run() { System.out.println("子线程开始执行..."); while (flag){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); // 抛出异常后,修改flag值 线程停止有延迟 stopThread(); } } System.out.println("子线程结束执行..."); } public void stopThread(){ System.out.println("调用stopThread方法..."); this.flag = false; System.out.println("已经将flag修改为"+flag); } }
多线程基础
内容:
1.线程与进程
2.创建线程的两种方式
3.线程的生命周期
4.cup时间片切换原理
5.线程休眠之sleep与interrupt
6.线程阻塞之join
7.线程阻塞之yield与线程优先级
8.线程练习
9.线程同步和线程同步块
10.生产者与消费者
1.线程与进程
进程Process:操作系统中正在运行的程序就是进程
java程序是运行在jvm之上,因此这个进程肯定是和jvm有关,这个进程的名字是:javaw.exe
我们是站在操作系统的角度看进程
进程(就是运行中的程序) 一个程序两种状态:静止状态(可执行文件,不是进程),运行中状态(进程).
线程
线程(Thread):进程中命令的执行轨迹,因为它是线状的,所以称为线程.
单线程:如果一个进程中命令的执行轨迹只有一条,那就是单线程.
多线程:如果一个进程中命令的执行轨迹有两条或两条以上,那就是多线程(迅雷同时下载多个文件).
2.创建线程的两种方式
继承Thread类 或 实现Runable接口
Runable接口实现类:
Thread(线程) run()
MyTask(任务) run()
代码:
/**
* Created by yz on 2018/2/28.
*/
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("test Thread 当前线程是:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("yz");
myThread.start();
}
}
/**
* Created by yz on 2018/2/28.
*/
public class MyRunnable implements Runnable{
public void run() {
System.out.println("test Runnable 当前线程是:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyRunnable myTask = new MyRunnable();
Thread t = new Thread(myTask);
t.setName("yz");
t.start();
}
}
调用关系图:
3.线程的生命周期
一个线程Thread(一生)有5个阶段即生命周期(由jvm规定)
初始阶段 刚new出线程对象 MyThread myThread = new MyThread();
就绪阶段 myThread.start();
运行阶段 run()
阻塞阶段 可人为干涉
终止阶段
4.cup时间片切换原理
cup时间片用完了,任务没完成,该任务返回就绪,cup继续加载另一个线程,来来回回切换线程.
cup时间片用完了,任务也完成,任务终止
5.线程休眠之sleep与interrupt
sleep(被动放弃cpu到阻塞状态);interrupt(中断该线程)
6.线程阻塞之join
/** * Created by yz on 2018/2/28. */ public class MyThread extends Thread{ @Override public void run() { for(int i=0;i<=10000;i++){ System.out.println("Thread 当前线程是:"+Thread.currentThread().getName()+ "i="+i); } } /** * 两个线程并发执行测试 * @param args */ public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("yz"); myThread.start(); for(int i=0;i<=10000;i++){ System.out.println("Main 当前线程是:"+Thread.currentThread().getName()+ "i="+i); if(i==5){ System.out.println("现在是"+Thread.currentThread().getName()+"它开始等待myThread线程任务全部完成再继续自己的任务"); try { //一直等到myThread线程任务完成,再继续运行main线程 myThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
7.线程阻塞之yield与线程优先级
yield(被动放弃cpu,被踢出后到运行阶段,继续抢线程)
线程优先级:
static int MAX_PRIORITY 线程可以拥有的最大优先级。 static int MIN_PRIORITY 线程可以拥有的最小优先级。 static int NORM_PRIORITY 分配给线程的默认优先级。
8.线程练习
package com.yz.thread; /** * 在一个线程中算出数组元素之和 * Created by yz on 2018/2/28. */ public class Addition { public static void main(String[] args) throws InterruptedException { int[] nums = {1,2,3,4,5,6}; int[] result = new int[1]; //希望你再启动一个线程,在这个线程中算出nums数组中元素之和 //把结果放入result数组中,最后在main线程中打印result[0] YzThread yt = new YzThread(nums,result); yt.start(); //先让yt线程运行完毕,在运行main线程 Thread.sleep(1000); System.out.println(result[0]); } } class YzThread extends Thread{ private int[] nums; private int[] result; YzThread(int[] nums,int[] result){ this.nums = nums; this.result = result; } @Override public void run() { int sum = 0; for (int n:nums){ sum+=n; } result[0] = sum; } }
9.线程同步和线程同步块
线程同步区分:多个线程访问同一个资源动作是否一致?因为只有动作一致,数据才安全
synchronized 同步锁(线程排队) 同步的代码越多,效率越低,使用同步代码块
//同步代码块 唯有同步代码块内容,线程只有一个一个访问 缩小同步范围,效果更高
synchronized(this){
if(number>0){
number--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("购票成功,现在还有票"+number+"张");
}else{
System.out.println("对不起,票已售完");
}
}
10.生产者与消费者
this.wait()
this.notify()
wait和notify方法必须在synchronized内部
public static void main(String[] args) throws Exception { Object object = new Object(); Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+",i="+i); if (i==5){ synchronized (object) { try { System.out.println(Thread.currentThread().getName()+" 开始等等。。。"); object.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } }); Thread t2 = new Thread(() -> { synchronized (object) { System.out.println(Thread.currentThread().getName()+" 发送notify通知。。。"); object.notify(); } }); // 启动第一个线程 t1.start(); Thread.sleep(1000); System.out.println(t1.getName()+" 状态:"+t1.getState()); // 启动第二个线程 t2.start(); }