本文共 19218 字,大约阅读时间需要 64 分钟。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下, 在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
总结:
线程就是独立的执行路径;
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程, gc线程;main()称之为主线程,为系统的入口,用于执行整个程序;
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
创建线程有三种方法:1:继承Thread类 2:实现Runable接口 3:Callable接口(了解)
如下都会介绍
引入问题:如何在自定义代码中,自定义一个线程呢?通过对API文档查找,java已经提供了对线程(对象)这类事物的描述,Thread类。
注:JAVA API文档提供了很多官方的介绍和类、方法、变量的解释。一般很系统,涉及所有的方面,如果开发人员对正在使用的类不熟悉,想查看类里面的变量或者方法,就可以打开JavaAPI文档进行阅读和查看。步骤:
class Demo extends Thread{ //1:定义类继承Thread public void run() { //2:复写Thread类中的run()方法 for(int i=0;i<10;i++) { System.out.println("demo run"+i); } } }public class SetUpThreadMethods1 { public static void main(String[] args) { Demo demo =new Demo();//创建好一个线程(对象) demo.start(); //3:调用start()方法 方法作用:导致此线程开始执行; Java虚拟机调用此线程的run方法。 for(int i=0;i<10;i++) { System.out.println("hello java"+i); } }}
/* 该结果可以说明: * 1.这个代码中出现了两个线程 * 2.cpu在某一时刻只能执行一个程序,比如CPU在执行网易云以后,又执行网游,反复执行,CPU在这里面做快速切换(速度快)CPU其实在切换每一个进程中的线程,所以程序越多就越卡 * 3.发现每次运行结果都不同,因为多个线程都获取CPU的执行权,CPU执行谁,谁就运行。 * 多线程的特性:随机性。(线程)谁抢到,谁执行 * 教师:CPU 教室:内存 教师越多教室越大。 */
步骤:
1:定义类实现Runable接口。 2:覆盖Runable接口中的run()方法(将线程要运行的代码存放到run()方法中)。 3:通过Thread类建立线程对象。 4:将runable接口的子对象作为实际参数传递给Thread的构造函数。 理由:自定义的run方法()所属的对象是runable接口的子对象。所以要让线程去指定指定对象的run()方法,就必须明确该run()方法所属对象 Thread(Runnable target) //Thread类的构造方法 作用: 分配一个新的 Thread对象。 5:调用thread类的start()方法开启线程并调用runnable接口子类的run方法子类继承Thread类具备多线程能力
启动线程:子类对象.start()
不建议使用:避免OOP单继承局限性
实现接口Runnable具有多线程能力
启动线程:传入目标对象+ Thread对象,start()
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
案例说明
class Tickets implements Runnable{ // 1:定义类实现Runable接口。 private int num=10; public void run() { // 2:覆盖Runable接口中的run()方法(将线程要运行的代码存放到run()方法中)。 while(num>0) { System.out.println(Thread.currentThread().getName()+" "+"tickets:"+num--); } //} }}public class SetUpThreadMethods2 { public static void main(String[] args) { Tickets a=new Tickets(); Thread s1=new Thread(a);// 3:通过Thread类建立线程对象。 4:将runable接口的子对象作为实际参数传递给Thread的构造函数。 //Thread类的构造方法 作用: 分配一个新的 Thread对象。 Thread s2=new Thread(a); Thread s3=new Thread(a); s1.start(); // 5:调用thread类的start()方法开启线程并调用runnable接口子类的run方法 s2.start(); s3.start(); }}
/* * 该运行结果说明,三个窗口去卖了10张票(三个线程去指定同一个runable接口的子对象) * 发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 */
龟兔赛跑
public class RaceTurtleAndRabblt implements Runnable{ private static String winner; @Override public void run() { //模拟兔子休息 if (Thread.currentThread().getName().equals("兔子")){ try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i=0;i<=100;i++){ boolean flag=gameOver(i); if (flag){ break; } System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步"); } } public boolean gameOver(int step){ if (winner!=null){ return true;//已经存在胜利者 } if (step>=100){ winner=Thread.currentThread().getName(); System.out.println("获胜者:"+winner); return true; } return false; } public static void main(String[] args) { RaceTurtleAndRabblt raceTurtleAndRabblt=new RaceTurtleAndRabblt(); new Thread(raceTurtleAndRabblt,"乌龟").start(); new Thread(raceTurtleAndRabblt,"兔子").start(); }}
...乌龟-->跑了96步兔子-->跑了0步........乌龟-->跑了99步兔子-->跑了24步获胜者:乌龟
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了新建状态。
1.只是说你资格运行,调度程序没有挑选到你,你就永远是可运行状态。
2.调用线程的start()方法,此线程进入可运行状态。 3.当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。 4.当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。 5.锁池里的线程拿到对象锁后,进入可运行状态。线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
这是线程没有资格运行时所处状态。线程仍旧是活的,但是当前没条件运行。
例如: 1:线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。 2:等待阻塞状态的线程(wait()方法)不会被分配CPU执行时间,它们要等待被显式地唤醒(notify()或notifyAll()方法),否则会处于无限期等待的状态。** * @author 郑霖俊 * @create 2021-04-09 20:31 * 模拟网络延迟:放大问题的发生性 */public class ThreadSleep implements Runnable{ static ThreadSleep tickets=new ThreadSleep(); private int num=10; public void run() { // 2:覆盖Runable接口中的run()方法(将线程要运行的代码存放到run()方法中)。 try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } while (num > 0) { System.out.println(Thread.currentThread().getName() + " " + "tickets:" + num--); } } public static void main(String[] args) { Thread s1=new Thread(tickets);// 3:通过Thread类建立线程对象。 4:将runable接口的子对象作为实际参数传递给Thread的构造函数。 //Thread类的构造方法 作用: 分配一个新的 Thread对象。 Thread s2=new Thread(tickets); Thread s3=new Thread(tickets); s1.start(); // 5:调用thread类的start()方法开启线程并调用runnable接口子类的run方法 s2.start(); s3.start(); }}
模拟计时器和系统时间
/** * @author 郑霖俊 * @create 2021-04-09 20:50 * 模拟倒计时和打印当前系统时间 */public class ThreadSleep2 { //模拟倒计时 public static void tenDown() throws InterruptedException { int num=10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num<=0){ break; } } } public static void main(String[] args) throws InterruptedException { //模拟倒计时 tenDown(); //打印当前系统时间 Date date = new Date(System.currentTimeMillis());//获取当前系统时间 while (true){ Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(date)); date=new Date(System.currentTimeMillis());//更新当前时间 } }}
1.当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
2.在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常public class ThreadStop implements Runnable{ private boolean flag=true; @Override public void run() { int i=0; while (flag){ System.out.println("run.....Thread"+i++); } } public void stop(){ this.flag=false; } public static void main(String[] args) { ThreadStop threadStop = new ThreadStop(); new Thread(threadStop).start(); for (int i=0;i<1000;i++){ System.out.println("main"+i); if (i==900){ threadStop.stop(); System.out.println("线程该停止了"); } } }}
..main900run.....Thread471线程该停止了..
再讲这个问题前首先讲 多线程的命名问题
查阅api文档后发现class Threads extends Thread{ private String name; public Threads(String name){ //使用super()调用父类的构造方法之一如下(改变线程名称) super(name); } public void run() { for(int i=0;i<10;i++) { System.out.println((Thread.currentThread()==this)+" "+this.getName()+" "+i);//返回此线程的名称和 Thread currentThread() 方法作用: 返回对当前正在执行的线程对象的引用。 } } }public class ThreadNamedDemo1 { public static void main(String[] args) { Threads a=new Threads("one"); Threads b=new Threads("two"); a.setName("four");//方法作用:将此线程的名称更改为等于参数 name b.setName("five"); a.start(); b.start(); for(int i=0;i<10;i++) { System.out.println("three"+" "+i); } }}
上述的所有关于线程命名的方法api文档中都有,读者可以自行查找,自己实践
接着继续讲多线程安全问题:虽然多个线程间共享代码和数据可以节省系统开销(实现(接口)方式),提高运行效率,当同时导致了数据访问冲突问题。由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可.存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题.
如下举个代码例子
class Ticket implements Runnable { private int num=10; public void run() { while (true) { while (num > 0) { try { Thread.sleep(10); // 这一个代码过程模拟了cpu忽然切到其他进程上。该线程处于阻塞状态 } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + " " + "tickets:" + num--); } } }}public class ThreadSafetyProblem { public static void main(String[] args) { Ticket a = new Ticket(); Thread s1 = new Thread(a);// 这个代码执行了后面的步骤: Thread(Runnable target) //Thread类的构造方法 作用: 分配一个新的 Thread对象。 Thread s2 = new Thread(a); Thread s3 = new Thread(a); s1.start(); s2.start(); s3.start(); }}
在run()方法中写入了Thread.sleep(10);(这一个代码过程模拟了cpu忽然切到其他进程上。该线程处于阻塞状态)通过分析该结果发现:结果中有0,-1,-2等数字,多线程运行出现安全的问题(多线程中最怕出现的问题)(为什么要用while(true)?因为只要输出越多就越可以看见安全问题)出现该安全问题的原因:当多条语句(s1.start()和s2.start()和s3.start())在共享同一个数据(Ticket类)时,一个语句对线程只执行了一部分,还没执行完,另一个线程参与进来,导致共享数据(临界资源)错误(互斥)找到了原因对应的解决方法就出现了(这里不做详细介绍)解决方法:1:使用synchronized关键字解决互斥问题 2:使用lock解决临界资源互斥问题
出现了安全问题(多线程最怕的问题)我们就必须找到解决方法
解决方法:对多条操作共享的语句,只可以让一个线程都执行,在执行过程中,其他线程不可以参与 所以Java中就有两种解决安全问题的方法使用该方法的格式
synchronized (对象) { }
// 对象如同锁,持有锁的线程可以在同步中进行。没有持有锁的线程,即使获取CPU使用权也进不去,因为没有锁
//形如:火车上的卫生间——经典 注意:同步(synchronized)得前提; 1:必须有两个或两个以上的线程: 2:必须是多个线程中使用同一个runable接口的子对象。 ——加了同步(synchronized)还不安全:考虑前提如下 1:是否用同一个锁 2:是否用两个线程需要被同步的代码 //同步坏处: 多个线程要判断锁,要消耗资源(耗时间)class Ticket implements Runnable { private int num=10; Object obj = new Object(); public void run() { while (true) { synchronized (obj) { while (num > 0) { try { Thread.sleep(10); // 这一个代码过程模拟了cpu忽然切到其他进程上。该线程处于阻塞状态 } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + " " + "tickets:" + num--); } } } }}public class ThreadSafetyProblem { public static void main(String[] args) { Ticket a = new Ticket(); Thread s1 = new Thread(a);// 这个代码执行了后面的步骤: Thread(Runnable target) //Thread类的构造方法 作用: 分配一个新的 Thread对象。 Thread s2 = new Thread(a); Thread s3 = new Thread(a); s1.start(); s2.start(); s3.start(); }}
该结果成功运行没有出现错误为什么该结果只有一个Thread-0在卖票呢?理由:循环次数不够多,如果卖一千张就可以看到Thread-1,Thread-2都在卖,读者可以自行操作
同步函数用的是那一个锁呢?
函数需要被对象调用,那么函数都有一个所属的对象引用,就是this 所以同步函数用的是this这个锁如果同步函数被静态修饰后,使用的锁是什么?
通过验证发现不是this,(静态方法中没有this) 静态进内存,内存中没有本类对象,当一定有该类对应的字节码文件对象 类名.class静态的同步方法使用的是:类名.class
lock()方法用于锁定对象,unlock()方法用于释放对对象的锁定,他们都在lock()接口中定义的方法。
上术关于锁的方法读者可以自行查阅api文档(因为只有自己多看api文档才可以熟能生巧)效果等同于
synchronized (对象) { 需要被同步的代码 }
用法:
import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.Lock;class Ticket implements Runnable{ private final Lock lock = new ReentrantLock(); public void run(){ lock.lock(); try { 运行代码块 } }finally { lock.unlock(); } } }
使用使用lock解决临界资源互斥问题的示例代码在这里不多讲
线程间通讯:
其实就是多个线程操作同一个过程,当是操作不同。wait(),notify(),notifyAll()
这些方法都使用在同步中,因为要对持有监视器(锁)的线程操作(只有同步才有锁)为什么这些操作线程的方法要定义在object类中?
因为这些方法在操作同步线程时,都必须要标识他们所操作线程只有的锁。 只有同一个锁上的wait(),可以被同一个锁上的notify()唤醒 不可以对不同锁中线程唤醒(等待和唤醒必须是同一个锁) 而锁可以是任意对象,所以可以被任意对象调用方法(定义object类中)1: notify() 方法作用:
唤醒正在等待对象监视器(锁)的单个线程。 2: notifyAll() 方法作用: 唤醒正在等待对象监视器(锁)的所有线程。 3: wait() 方法作用: 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。假设A线程和B线程共同操作一个X对象(同步锁),A,B线程可以通过X对象的wait和notify方法来进行通信,流程如下: 1:当A线程执行X对象的同步方法时,A线程持有X对象的锁,B线程没有执行机会,B线程在X对象的锁池中等待。 2:A线程在同步方法中执行X.wait()方法时,A线程释放X对象的锁,进入A线程进入X对象的等待池中。 3:在X对象的锁池中等待锁的B线程获取X对象的锁,执行X的另一个同步方法. 4:B线程在同步方法中执行X.notify()方法时,JVM把A线程从X对象的等待池中移动到X对象的锁池中,等待获取锁。 5:B线程执行完同步方法,释放锁.A线程获得锁,继续执行同步方法。
我们根据上面的思路写出下列代码class Resource { private String name; private String sex; private Boolean flag = false; public synchronized void set(String name, String sex) { //同步函数 if (this.flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; this.sex = sex; this.flag=true; notify(); } public synchronized void out() { if (!this.flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("name:" + name + " " + "sex:" + sex); this.flag = false; try { notify(); } catch (IllegalMonitorStateException e) { } }}class Input implements Runnable { private Resource resource1; public Input(Resource resource) { this.resource1 = resource; } public void run() { int x = 0; while (true) { if (x == 0) { resource1.set("zlj", "man"); } else { resource1.set("zl", "woman"); } x = (1 + x) % 2; } }} class OutPut implements Runnable { Resource resource1 = new Resource(); public OutPut(Resource resource) { this.resource1 = resource; } public void run() { while (true) { resource1.out(); } } }public class MutiThreadCommunicationProblem { public static void main(String[] args) { Resource resource = new Resource(); Input s1 = new Input(resource); OutPut s2 = new OutPut(resource); Thread a = new Thread(s1); Thread b = new Thread(s2); a.start(); b.start(); }}
思路:1,定义一个资源类,里面有一个set,out方法包括了线程名称,name,count,
为了可以让1:一个生产者,对应一个消费者:2:确保不出现安全问题。解决方法:1,先定义同步函数2,然后在执行如下方法wait()notifyall()while() 2,定义一个实现runnable接口的生产者类,里面有run()运行方法,定义一个资源类的对象引用 3,定义一个实现runnable接口的消费者类,里面有run()运行方法,定义一个资源类的对象引用 4,实现主函数,写出如下代码
public class Resource { private int count=0; private String name; private boolean flag=false; public synchronized void out(String name) { while (flag) { try { wait(); } catch (Exception e) { } } this.name=name+(count++); System.out.println(Thread.currentThread().getName()+"生产......"+this.name); this.flag=true; this.notifyAll(); } public synchronized void input(String name) { while (!flag) { try { wait(); } catch (Exception e) { } } System.out.println(Thread.currentThread().getName()+"消费...................."+this.name); this.flag=false; this.notifyAll(); }}public class Producer implements Runnable{ private Resource resource; public Producer(Resource resource) { this.resource=resource; } public void run() { while (true) { resource.out("娃哈哈"); } }}public class Consumer implements Runnable{ private Resource resource; public Consumer(Resource resource) { this.resource=resource; } public void run() { while (true) { resource.input("娃哈哈"); } }}public class Demo { public static void main(String[] args) { Resource resource=new Resource(); new Thread(new Producer(resource)).start(); new Thread(new Producer(resource)).start(); new Thread(new Consumer(resource)).start(); new Thread(new Consumer(resource)).start(); }}
运行结果如下
所谓守护线程是指在程序运行时,在后台提供的一种通用服务线程。(当所有的非守护线程结束时,程序也就终止了,同时杀死进程中所有守护线程)
将线程转为守护线程的方法:调用Thread类的 setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程。 参数 :on - 如果 true ,将此线程标记为守护线程*代码示例
class ThreadDemo implements Runnable{ public void run () { while(true) { System.out.println("执行守护线程"); } }}public class DaemonDemo { public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); Thread a=new Thread(threadDemo); a.setDaemon(true); //创建守护线程 a.start(); }}
运行结果
此代码如果不加 a.setDaemon(true); //创建守护线程 的这一行将出现如下结果
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态 让cpu重新调度,礼让不一定成功!看CPU心情案例演示
public class ThreadYield implements Runnable{ private int num=10; @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"线程停止执行"); } public static void main(String[] args) { ThreadYield threadYield=new ThreadYield(); new Thread(threadYield,"a").start(); new Thread(threadYield,"b").start(); }}
运行结果
b线程开始执行a线程开始执行b线程停止执行a线程停止执行
join()方法作用:当主线程(A线程)执行到了B线程的join()方法时,主线程会等待(A线程),直到B线程执行完
join可以临时加入线程执行 案例演示public class ThreadJoin implements Runnable{ public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new ThreadJoin()); thread.start(); thread.join(); for (int i=0;i<10;i++){ System.out.println("main:"+i); } } private int num=3; @Override public void run() { while (num>0){ System.out.println(num--); if (num<=0){ break; } } }}
321main:0
1.当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
2.在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常终止状态演示
public class ThreadStop implements Runnable{ private boolean flag=true; @Override public void run() { int i=0; while (flag){ System.out.println("run.....Thread"+i++); } } public void stop(){ this.flag=false; } public static void main(String[] args) { ThreadStop threadStop = new ThreadStop(); new Thread(threadStop).start(); for (int i=0;i<1000;i++){ System.out.println("main"+i); if (i==900){ threadStop.stop(); System.out.println("线程该停止了"); } } }}
..main900run.....Thread471线程该停止了..
Thread. State
线程状态。线程 可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABL E
在Java虛拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED
已退出的线程处于此状态一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
线程的优先级用数字表示,范围从1~10. .
Thread.MIN PRIORITY = 1;
Thread.MAX_ PRIORITY = 10;
Thread.NORM PRIORITY = 5;
优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了。这都是看CPU的调度
使用以下方式改变或获取优先级 getPriority() setPriority(int xxx)优先级的设定建议在start()调度前
转载地址:http://iuhq.baihongyu.com/