多线程
线程:一个顺序的单一的程序执行流程就是一个线程,代码一句一句的有先后顺序执行.
多线程:多个单一顺序执行的流程并发运行,造成"感官上同时允许"的效果
并发
多个线程实际运行时走走停停的,线程调度程序会将CPU允许时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间,当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它.如此反复,由于CPU执行时间在纳秒级别,我们感觉不到切换线程允许的过程,所以微关上走走停停,宏观上感觉一起允许的现象称为并发
用途
当出现多个代码片段执行顺序哟冲突时,希望他们各干各的收就应当放在不同线程上"同时"运行
一个线程可以运行,但是多个线程也可以更快,可以使用多个线程运行.
创建线程的两种方式
方式一:继承Thread并重写run方法
定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他县城并发执行的任务)
注:启动该线程要调用该线程的start方法,而不是run方法!!!
package thread;
/**
* 多线程
* 线程:程序中一个单一的顺序执行流程
* 多线程:多个单一顺序执行流程"同时"执行
*
* 多线程改变了代码的执行方式,从原来的单一顺序执行流程变为多个执行流程"同时"执行。
* 可以让多个代码片段的执行互不打扰。
*
* 线程之间是并发执行的,并非真正意义上的同时运行。
* 常见线程有两种方式:
* 1:继承Thread并重写run方法
*
*/ public class ThreadDemo1 {
public static void main(String[] args) {
//创建两个线程
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/* 启动线程,注意:不要调用run方法!!
线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。
线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后
线程调度器会再分配一个时间片段给一个线程,
如此反复,使得多个线程 都有机会执行一会,
做到走走停停,并发运行。
线程第一次被分配到时间后会执行它的run方法开始工作。
*/
t1.start();
t2.start();
}
}
/**
* 第一种创建线程的优点:
* 结构简单,利于匿名内部类形式创建。
*
* 缺点:
* 1:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法
* 2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致
* 线程只能干这件事。重(chong)用性很低。
*/
class MyThread1 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("hello姐~");
}
}
}
class MyThread2 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("来了~老弟!");
}
}
}
方式二:实现Runnable接口单独定义线程任务
package thread;
/**
* 第二种创建线程的方式
*实现Runnable接口单独定义线程任务
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//实例化任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程并指派任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}}
class MyRunnable1 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("你是谁啊?");
} } }
class MyRunnable2 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("开门!查水表的!");
} } }
/*
优点:由于是实现接口,没有继承冲突
线程与任务没有耦合关系
缺点:就是创建线程麻烦一些,不能算缺点
*/
用匿名内部列完成线程两种方式的创建
package thread;
/**
* 用匿名内部类完成线程的两种创建形式
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//继承Thread重写run方法
Thread t1=new Thread(){
public void run(){
for(int i=0;i<2000;i++){
System.out.println("简单");
}
}
};
//实现RUN那边了接口重写run方法
Runnable r2=new Runnable() {
@Override
public void run() {
for(int i=0;i<2000;i++){
System.out.println("===============");
}
}};
Thread t2=new Thread(r2);
//lambda表达式
Runnable r3=()-> {
for(int i=0;i<2000;i++) {
System.out.println("??????");
}
};
Thread t3=new Thread(r3);
Thread t4=new Thread(()->{
for(int i=0;i<1000;i++){
System.out.println("t4不执行吗");
}
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"
线程提供了一个方法
static Thread currentThread()
该方法可以获取运行这个方法的线程
package thread;
/**
* java中所有的代码都是靠线程执行的,main方法也不例外。JVM启动后会创建一条线程来执行main
* 方法,该线程的名字叫做"main",所以通常称它为"主线程"。
* 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,格式为"thread-x"(x是一个数)。
*
* Thread提供了一个静态方法:
* static Thread currentThread()
* 获取执行该方法的线程。
*
*/
public class CurrentThreadDemo {
public static void main(String[] args) {
/* 后期会学习到一个很重要的API:ThreadLocal,它可以使得我们在一个线程上跨越多个
方法时共享数据使用,其内部要用到currentThread方法来辨别线程。
如spring的事物控制就是靠ThreadLocal实现的。
*/
Thread main = Thread.currentThread();//获取执行main方法的线程(主线程) System.out.println("线程:"+main);
dosome();//主线程执行dosome方法
}
public static void dosome(){
Thread t = Thread.currentThread();//获取执行dosome方法的线程
System.out.println("执行dosome方法的线程是:"+t);
}
}
运行结果如下
获取线程相关信息的方法
package thread;
import com.sun.media.sound.SoftTuning;
/**
* 获取线程信息的相关方法
*/
public class ThreadInfoDemo {
public static void main(String[] args) {
Thread ma=Thread.currentThread();
String name=ma.getName();//获取线程的名字
System.out.println("名字:"+name);//main
long id=ma.getId();//获取唯一标识
System.out.println("唯一标识:"+id);
System.out.println("==============");
int priority=ma.getPriority();//获取优先级
System.out.println("优先级是:"+priority);
System.out.println("=============");
boolean isAlive=ma.isAlive();//是否活着
System.out.println("是否活着:"+isAlive);
System.out.println("===============");
boolean isDeamon=ma.isDaemon();//是否守护线程
System.out.println("是否为守护线程"+isDeamon);
System.out.println("================");
boolean isInterrupted =ma.isInterrupted();//是否被中断
System.out.println("是否被中断:"+isInterrupted);
}
}
线程优先级
线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片,线程调度器竟可能均匀的将时间分配给每个线程.
线程有10个优先级.使用整数1-10表示
1为最小优先级,10为最高优先级,5为默认取值
调整线程的优先级可以最大程度的干涉获取时间片的几率,优先级越高的线程获取时间片的次数越多,反之越少.
public class PriorityDemo {
public static void main(String[] args) {
Thread max=new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("max");
}
}
};
Thread min=new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("min");
}
}
};
Thread norm=new Thread(){
public void run(){
for(int i=0;i<10000;i++){
System.out.println("norm");
}
}
};
min.setPriority(Thread.MIN_PRIORITY);//1
max.setPriority(Thread.MAX_PRIORITY);//10
System.out.println(max.getPriority());
min.start();
max.start();
norm.start();
}
}
注意,他们要进行比较的前提是在同一个CPU上,线程的优先级越高获取时间片的次数越多,但是如果不在同一个CPU上就没有意义,现在的电脑都是多核即多CPU,所有运行的结果不一定准确,不一定是max线程先运行完.
sleep阻塞
线程提供了一个静态方法
static void sleep(long ms)
使用运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.
public class SleepDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
/*
做一个简易的倒计时程序。
要求:程序启动后输入一个数字,从该数字开始每秒递减并输出,当到0时输出时间到
*/
Scanner sanc=new Scanner(System.in);
System.out.println("请输入数字:");
int s=sanc.nextInt();
while(s!=0) {
try {
Thread.sleep(1000);//停止1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
s--;
System.out.println(s);
}
System.out.println("时间到");
}
}
sleep方法处理异常:InterruptedException
当一个线程调用sleep方法处于睡眠阻塞状态的过程中,该线程的interrupt()方法被调用时,Sleep方法会抛出异常从而打断睡眠阻塞.
package thread;
/**
* sleep方法要求我们必须处理中断异常
*
*/
public class SleepDemo2 {
public static void main(String[] args) {
Thread lin=new Thread("妲己") {
public void run(){
System.out.println(getName()+"刚美完容,睡一会吧");
try {
Thread.sleep(500000000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(getName()+"生气了,你在干嘛");
}
System.out.println(getName()+"醒了");
}
};
Thread hang=new Thread("钟无辣"){
public void run(){
System.out.println(getName()+":大锤80,小锤40");
for(int i=0;i<5;i++){
System.out.println(getName()+":80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("哐当");
System.out.println(getName()+":大哥!赢了");
lin.interrupt();//中断lin线程的睡眠阻塞
}
};
lin.start();
hang.start();
}
}
守护线程:也称后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异
- 守护线程的结束时间上有一点与普通线程不同,即:进程的结束
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
package thread;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
/**
* 守护线程
* 守护线程是通过线程的方法setDaemon(boolean on)传入参数true将一个普通
* 线程设置而转变的
* 守护线程有一点雨普通线程不同的。就是进程的结束
* 当java进程中所有的普通线程都退出时,java进程就会退出,此时杀死所有还在运行的守护线程
*
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose= new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("rose:啊啊啊啊啊aaa");
System.out.println("扑通,落水");
}
};
Thread jack = new Thread(){
public void run() {
while (true) {
System.out.println("jack:you jump! I jump");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
rose.start();
jack.setDaemon(true);//设置守护线程必须在启动线程之前
jack.start();
// while(true);//rose线程结束后如果主线程不结束,进程也不会结束,那么jack就不会被进程杀死
}
}
多线程并发安全问题
- 当多个线程并发操作同一个临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现严重时可能导致系统瘫痪,
- 临界资源:操作该资源的全过程同时只能被单个线程完成
下面这段代码进行运行可能不会报错,就是进入无限循环,当线程1拿到第20颗豆子,只是拿了,但是线程2不知道线程1拿了,它也拿了,然后让豆子直接跳过了它等于0的时候,直接就如无线循环,一直拿豆子,产生并发安全,这里只是一个例子,不然纠结为什么不设置为<0.只是为了让我们知道线程的并发安全如何产生.
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源由于线程切换时机不确定,导致执行顺序出现混乱从而导致不良后果
* 临界资源:操作该资源的完成过程同一时刻只能被单一线程进行的。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table=new Table();
Thread t1=new Thread(){
public void run(){
while (true){
int bean=table.getBean();//从桌子上拿一个豆子
/* static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。 */
Thread.yield();
System.out.println(getName()+";"+bean);
}
}
};
Thread t2=new Thread(){
public void run(){
while (true){
int bean=table.getBean();//从桌子上拿豆子
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans=20;//桌子上有20颗豆子
public int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了");
}
//主动放弃本次事件片用来模拟线程执行到这里是cpu事件用完了
Thread.yield();//该方法可以让允许该方法的线程主动放弃本次剩余的时间片
return beans--;
}
}
synchronized关键字
synchronized有两种使用方法
- 在方法上修饰,此时方法变为一个同步方法
- 同步块,可以更准确的锁定需要排队的代码片段
同步方法
当一个方法使用synchronized修饰后,这个方法称为"同步方法";即:锁哥线程不能同时在方法内部执行,只能有先后顺序的进行,将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源由于线程切换时机不确定,导致执行顺序出现混乱从而导致不良后果
* 临界资源:操作该资源的完成过程同一时刻只能被单一线程进行的。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table=new Table();
Thread t1=new Thread(){
public void run(){
while (true){
int bean=table.getBean();//从桌子上拿一个豆子
/* static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。 */
Thread.yield();
System.out.println(getName()+";"+bean);
}
}
};
Thread t2=new Thread(){
public void run(){
while (true){
int bean=table.getBean();//从桌子上拿豆子
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans=20;//桌子上有20颗豆子
/*
synchronized关键字:该方法称为"同步方法",多个线程不能同时在方法
内部执行
将多个线程并发执行改为同步(有先后顺序)的指向可有效的解决并发安全问题
*/
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了");
}
//主动放弃本次事件片用来模拟线程执行到这里是cpu事件用完了
Thread.yield();//该方法可以让允许该方法的线程主动放弃本次剩余的时间片
return beans--;
}
}
同步块
有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率,同步块可以更准确的控制需要多个线程排队执行的代码块
语法:
synchronized(同步监视对象){
需要多线程同步执行的代码片段
}
同步监视器对象即上锁对象,要想保证同步块中的代码报备多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.
package thread;
/**
* 同步块
* 有效的缩小同步范围可以在保证并发安全的前提下竟可能的提高并发率
*
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shoop shoop=new Shoop();
Shoop shoop1=new Shoop();
Shoop shoop2=new Shoop();
Thread t1=new Thread("小红"){
public void run(){
// shoop.buy();
shoop1.buy();
}
};
Thread t2=new Thread("小南"){
public void run(){
// shoop.buy();
shoop2.buy();
}
};
t1.start();
t2.start();
}
}
class Shoop{
/*
在成员方法上是使用synchronized后,同步监视器对象不可选,即使this
*/
// public synchronized void buy(){
public void buy(){
try {
Thread t=Thread.currentThread();
System.out.println(t.getName()+"正在挑衣服……");
Thread.sleep(5000);
/**
* 同步使用时要指定同步监视器对象,该对象的选取要同时满足:
* 1:必须是一个引用类型实例
*多个需要同步执行该代码片段的线程看到的该对象必须是同一个
*
* 合适的锁对象应当是存在并发安全问题时可以限制多个线程同步执行
* 不存在并发安全时可以同时执行
* 例如上面的shoop1与shoop2,它们相当于进入的是不同的两家店,所有不会锁
*
* 但是如果我们synchronized ("hello") {
* 这个不是合适的锁对象,因为字面量是在常量池中创建,以后直接调用,
* 就算不是同一个店,也会进行排队
*/
synchronized (this) {
//不可以,多个线程都会new对象,看到的是不同一个锁
// synchronized (new Object()){
System.out.println("正在试衣服……");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在静态方法上使用Synchronized
当静态方法上使用synchronized后,该方法是一个同步方法,由于静态方法所属类,所以一定具有同步效果,静态方法使用的同步监视器对象为当前类对象(Class的实例).
注:类对象会在后期的反射知识介绍
静态方法中使用同步块时,指定的锁对象通常也是当前类对象(类名.Class)
package thread;
/**
* 静态方法上如果使用了synchronized,那么该方法一定具有同步效果
*/
public class SyncDemo3 {
public static void main(String[] args) {
Foo s1=new Foo();
Foo s2=new Foo();
Thread t1=new Thread(){
public void run(){
Foo.dosome();
// s1.dosome();
/*即使是两家店,也不会同时干
因为静态方法全局一份,而且是属于类的,用类名调用
即使是分开创建,但是全局也只有一份。
*/
}
};
Thread t2=new Thread(){
public void run(){
Foo.dosome();
// s2.dosome();
}
};
t1.start();
t2.start();
}
}
class Foo {
/*
如果这里将static去掉会如何?
此时dosome变成一个成员方法,因此synchronized对应的锁对象就是this。
那么上面两个线程t1和t2调用时由于:
t1调用的是f1.dosome(),那么dosome中的this就是f1
t2调用的是f2.dosome(),那么dosome中的this就是f2
由于两个线程看到的并非同一个锁对象,因此可以同时执行该该方法。
static方法则不同,由于该方法全局唯一,因此无论什么时候以何种方式调用
都能做到同步效果(多个线程分开执行)。
静态方法上使用synchronized后锁对象为当前类的类对象(Class的实例)
JVM中每个被加载的类都有且只有一个Class实例与之对应,类对象会在后续反射
知识点说明。
在静态方法中如果使用同步块时,指定锁对象通常也用该对象,那么引用类对象的
格式可以为:类名.class
*/
// public synchronized static void dosome(){
public static void dosome() {
synchronized (Foo.class) {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行dosome方法");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
互斥锁
- 当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的
- 使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的
package thread;
/**
* 互斥锁
* 当使用多个synchronized锁定多个代码片段,并且指定的同步监视对象是同一个时
* 那么这些代码片段之间是互斥的,多个线程不能同时执行它们
*/
public class SyncDemo4 {
public static void main(String[] args) {
Boo boo=new Boo();
Thread t1=new Thread(){
public void run(){
boo.methodA();
}
};
Thread t2=new Thread(){
public void run(){
boo.methodB();
}
};
t1.start();
t2.start();
}
}
class Boo {
public synchronized void methodA() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行a方法");
Thread.sleep(5000);
System.out.println(t.getName()+":a方法执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// public synchronized void methodB() {//互斥
public void methodB() {
// synchronized (this){//互斥
synchronized ("this"){//不互斥,因为这个是String,在常量池中有
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行B方法");
Thread.sleep(5000);
System.out.println(t.getName()+":B方法执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
}
同步监视器选取对象
- 对于同步成员方法而已,同步监视器不可指定,只能是this
- 对于同步静态方法而言,同步监视器对象也不可指定,只能是类对象
- 对于同步块而已,需要自行指定同步监视器对象,选取原则:
- 1,必须是引用类型
- 2,多个需要同步执行该同步块的线程看到的对象必须是同一个
同步监视器选取原则:通常选取临界资源
package thread;
import java.util.ArrayList;
import java.util.List;
/**
* 同步监视器对象选取原则:通常选取临界资源即可
*/
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Thread t1 = new Thread(){
public void run(){
for(int i=0;i<200;i++){
synchronized (list){
list.add(i);}
}
System.out.println("添加完毕");
}
};
Thread t2 = new Thread(){
public void run(){
for(int i=0;i<200;i++){
synchronized (list){
list.add(i);}
}
System.out.println("添加完毕");
}
};
t1.start();
t2.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
线程池
线程池是线程管理类,主要解决两个问题
- 控制线程数量
- 重用线程
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
* 线程池是线程管理类,主要解决两个问题
* 1.控制线程数量
* 2.重用线程
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建一个线程池
ExecutorService threadPool= Executors.newFixedThreadPool(3);
//指派任务
for(int i=0;i<5;i++){
Runnable runnable=new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();
System.out.println(t.getName()+"正在执行一个任务");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getName()+"执行任务完毕");
}
};
threadPool.execute(runnable);
System.out.println("将一个任务交给了线程池");
}
// threadPool.shutdown();
threadPool.shutdownNow();
System.out.println("线程池停止了");
}
}
shutdown与shutdownNow
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
说白了就是shutdown会把手里的任务完成,然后停止,但是shutdownNow就是手里的任务就是没有完成,也进行停止,在控制抬明显的输出就是:shutdown不会报错,但是shutdownNow会报错.