赞
踩
什么多线程?
是指从软件或者硬件上实现多个线程并发执行的技术
具有多线程能力的计算机因为有硬件的支持而能够在同一时间执行多个线程,提高性能
并发和并行:
并行: 同一个时刻,多个指令在多个cpu上同时执行
并发: 同一个时刻,多个指令在一个cpu上交替执行
进程和线程 :
什么是进程
进程:正在运行的软件
>独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
>动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
>并发性:任何进程都可以同其他进程一起并发执行
什么是线程
线程:是进程中的单个顺序控制流程,是一条直行路径
>单线程:一个进程如果只有一条执行路径,则称为单线程程序 (之前写的程序)
>多线程:一个线程如果有多条执行路径,则称为多线程程序
多线程的实现方式-
1.继承Thread
java的jvm允运行多个线程,它通过java.lang.Thread类来体现
Thread类的特性:
>每个线程都是通过某个特定的Thread对象的run()方法来完成操作,经常把run()方 法的主体称为线程体
>通过Thread对象的start()方法来启动这个线程,而非是直接调用run()方法
- class MyThread extends Thread {
-
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.println(i);
- }
- }
- }
- public class Demo1 {
- public static void main(String[] args) {
- /* 现在只有1个线程,任何java程序起码有个主线程 main
- MyThread t1=new MyThread();并不是一个线程
- 只是创建了一个线程对象(线程对象和线程还是有很大区别的
- 对象是创建在虚拟机堆空间中的,
- 而线程是操作系统维护的一种资源 为什么说Thread对象代表一个线程?
- 因为把线程对象创建出来以后,以他为代表才能创建线程,
- 我们程序员不能绕开虚拟机工作,不能直接去访问操作系统),
- */
- MyThread t1=new MyThread();
- MyThread t2=new MyThread();
- //通过此对象调用start方法 : ①启动当前线程,② 调用当前线程的run()方法
- //start() 线程对象会帮你到操作系统中发出申请能不能为我创建一个线程
- //如果得到批准操作系统就创建一个新线程
- //这才是创建线程 或者是启动线程
- t1.start();
- //这句话是在主线程中执行的
- System.out.println("你好世界");
- t2.start();
-
- }
- }
- //cup的切换是随机的 所以看到的结果每次是不一样的
可以看到,第一条线程到25后,第二条线程执行.交替执行
cpu的切换是随机的 所以每次运行看到的结果每次是不一样的
为什么要重写run()方法
因为run()方法是用来封装被线程执行的代码
run()和start()方法的区别?
如果直接调用run()方法,表示的仅仅是创建对象,用对象去调用方法,并没有开启线程
相当于是普通方法的调用
native 表示的调用的本地方法 是和操作系统进行交互的方法
start():表示启动线程,然后由jvm调用此线程的run()方法
- class MyThread extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName()+"-"+i);
- }
- }
- }
- //注意点1 不能直接调用run方法的方式启动线程
- public class Demo1 {
- public static void main(String[] args) {
- new MyThread().run();
- //在主线程中调用run方法 还是单线程执行,没有开辟新的线程
- //这个打印的线程名 是主线程的
- }
- }
- //注意点2 不能同一个线程启动两次 如果想启动两个线程就只能创建一个新的线程对象
- public class Demo1 {
- public static void main(String[] args) {
- MyThread m1 = new MyThread();
- m1.start();
- m1.start();//不能同一个线程启动两次
- //java.lang.IllegalThreadStateException
- }
- }
多线程的实现方式-
2.实现Runnable接口
代码演示:
1 创建一个实现了Runnable接口的类
2 重写run()方法
3 创建此类的对象
4 创建Thread类的对象并以此类的对象作为构造参数
5 通过Thread类的对象调用start()方法
- class MyThread implements Runnable{
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName()+"-"+i);
- }
- }
- }
- public class Demo1 {
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- Thread thread1 = new Thread(myThread,"线程一");
- thread1.start();
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName()+"-"+i);
- }
-
- //如何再启动一个线程
- Thread thread2 = new Thread(myThread);
- thread2.setName("线程二");
- thread2.start();
-
- }
- }
运行结果:
cpu的切换是随机的 所以每次运行看到的结果每次是不一样的
多线程的实现方式-
3.实现callable接口
jdk5.0
实现Callable接口
与Runnable接口相比,callable功能更强大
1 相比run方法可以有返回值
2 方法可以抛出异常
3 支持泛型的返回值
4 需要借助FutureTask类
Future:
可以对具体的Runnable Callable任务的执行结果进行取消,查询是否完成,获取结果等
FutureTask是Future接口的唯一实现类
FutureTask同时扩展了Runnable Future接口,它既可以作为Runnable被线程执行,
又可以作为Future得到Callable的返回值
代码实现
- //1 创建一个实现了Callable接口的实现类
- //2 重写call()方法 执行的操作写在其中
- //3 创建实现了Callable接口的实现类对象
- //4 将实现类对象作为参数传递到FutureTask构造器中
- //5 将FutureTask对象作为参数传入Thread类的对象的构造器中,启动线程
- //6 可以获取call方法的返回值..
-
- class MyThread implements Callable{
- @Override
- public Object call() throws Exception {
- int num=0;
- for (int i = 0; i < 100; i++) {
- System.out.println(i);
- num+=i;
- }
- return num;
- }
- }
-
- public class Demo2 {
- public static void main(String[] args) {
- //需要执行call方法
- MyThread my =new MyThread();
- //可以获取线程执行完毕的结果,也可以作为参数传递给Thread
- FutureTask futureTask=new FutureTask(my);
- //启动线程
- new Thread(futureTask).start();
- try {
- //get();
- //返回值即为FutureTask构造方法参callable实现类重写的call()方法的返回值
- Object num = futureTask.get();
- System.out.println(num);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- }
- }
-
- /*注意:
- get();//不能在start方法之前调用 它获取的是线程结束之后的结果*/
三种实现方式的对比 :
优点 缺点
实现Runnable接口 扩展性强,实现该接口的同时 编程相对复杂不能直接使用
,Callable接口 还可以继承其他的类 Thread类中的方法
继承Thread类 编程相对简单,可以直接使用 可扩展性差
Thread类中的方法
不能在继承其他类
开发中优先选择实现Runnable接口的方式
1 实现的方式没有类的单继承的限制
2 实现的方式更适合处理多个线程有共享数据的情况
-获取多线程对象
- //Thread 中静态方法
- //返回对当前正在执行的线程对象的引用。 结果就是当前正在执行的线程。
- public static Thread currentThread()
-
- Thread.currentThread().getName() 获取当前线程的名字 默认名(Thread-0/1/2...)
- Thread.currentThread().setName() 设置当前线程的名字
-
- public static void main(String[] args) {
- //获取主线程名字
- System.out.println(Thread.currentThread().getName());
- //给主线程是指名字
- Thread.currentThread().setName("主线程");
- System.out.println(Thread.currentThread().getName());
-
- }
-sleep
- // 1秒=1000毫秒
- //使当前正在执行的线程停留(暂停执行)指定的毫秒数,这取决于系统定时器和调度程序的精度和准确性。
- //线程不会丢失任何显示器的所有权。
- //millis - 以毫秒为单位的睡眠时间长度 不能是负数 否则报错
- //通俗说法
- :让当前线程睡眠指定的m毫秒数,在指定的m毫秒时间内当前线程是阻塞状态
- public static void sleep(long millis)
-
- //导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),
- //这取决于系统定时器和调度器的精度和准确性。 线程不会丢失任何显示器的所有权。
- //millis - 以毫秒为单位的睡眠时间长度
- //nanos - 0-999999额外的纳秒睡眠
- public static void sleep(long millis,int nanos)
-
- public class Demo1 {
- public static void main(String[] args) throws InterruptedException {
- MyThread thread = new MyThread("线程一");
- thread.start();
- }
- }
-
- class MyThread extends Thread {
- @Override
- public void run() {
- for (int i = 1; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + "---" + i);
- try {
- this.sleep(1000);//阻塞1秒
- //Thread类中run方法没有抛异常 那么重写的方法也不能抛异常只能自己处理
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public MyThread(String name) {
- super(name);
- }
- }
-线程的优先级
线程调度:
多线程的并发运行:
计计算机中的CPU在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执 行代码,各个线程轮流获得CPU的使用权,分别执行各自的任务.
1 线程的调度模型有哪些
分时调度模:所有线程轮流使用cpu的使用权,平均分配每个线程占用CPU的时间片
时间片
- - - - - -
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一 个优先级高的线程获取的CPU时间片相对多一些
2 java的线程调度方式
抢占式调度模型
同优先级的线程组成先进先出对列(先到先得) 使用时间片策略
高优先级的,使用优先调度的抢占式策略
3 如何设置当前线程的优先级
优先级分档 一共10档
10表示最高优先级,1表示最低优先级
//三个静态常量
MAX_PRIORITY:10;
MIN_PRIORITY:1
NORM_PRIORITY:5 //默认优先级
设置当前线程的优先级
setPriority(int n);
获取线程的优先值
getPriority(); 默认5
注意点:
>线程创建的时候继承父线程的优先级
>低的优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
代码演示:
- //优先级更高只是说明抢夺CPU时间片的几率更高并不是说一定先执行完毕
- public class Demo2 {
- public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, ExecutionException {
- //需要执行call方法
- MyThread m1 = new MyThread();
- //可以获取线程执行完毕的结果,也可以作为参数传递给Thread
- FutureTask<String> ftt=new FutureTask(m1);
- Thread t=new Thread(ftt,"飞机1");
- t.setPriority(1);
- System.out.println(t.getPriority());
- t.start();
-
- MyThread m2 = new MyThread();
- FutureTask<String> ftt2=new FutureTask(m2);
- Thread t2=new Thread(ftt2,"大炮2");
- System.out.println(t2.getPriority());//默认优先级是5
- t2.start();
- System.out.println(ftt.get());
- //不能在start方法之前调用 它获取的是线程结束之后的结果
-
- }
- }
- class MyThread implements Callable<String> {
- @Override
- public String call() throws Exception {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName()+" "+i);
- }
- return "运行结束";//返回值表示线程运行完毕之后的结果 泛型表示返回值的类型
- }
- }
-守护线程
线程的分类有哪些?
守护线程
用户线程(普通线程)
1 几乎在每个方面都是相同的,唯一的区别是判断jvm何时离开
2 守护线程是用来服务用户线程的, 通过在start()方法之前调用
thread.setDaemon(true) 可以把一个用户线程变成一个守护线程
3 java的垃圾回收就是一个典型的守护线程
4 若是jvm中都是守护线程,当前jvm将退出
5 当普通线程执行完毕,守护线程就停止了
(但不是立即停止在有限的时间片内还会在执行一会儿)
- //将此线程标记为daemon线程或守护线程。
- //当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
- //线程启动前必须调用此方法
- //on - 如果是 true ,将此线程标记为守护线程
- public final void setDaemon(boolean on)
-
- //测试这个线程是否是守护线程。
- public final boolean isDaemon()
-
-
- public class Demo2 {
- public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, ExecutionException {
- MyThread m1 = new MyThread();
- m1.setName("女神");
- MyThread2 m2 = new MyThread2();
- m2.setName("添狗");
-
- m2.setDaemon(true);//把第2个线程设置为守护线程
- m1.start();
- m2.start();
- }
- }
-
- class MyThread extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- System.out.println(Thread.currentThread().getName() + " " + i);
- }
- }
- }
-
- class MyThread2 extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + " " + i);
- }
- }
- }
什么是同步代码块?
解决线程同步
特点:同步代码块默认情况下是打开的,只有当有一个线程进入了代码块去执行代码
锁才会关闭,当线程执行完毕, 锁才会自动打开
同步的好处和弊端:
好处:解决了多线程的数据安全问题
弊端:当线程很多时候,因为每个线程都会去判断同步上的锁
这是很耗费资源的,无形中会降低程序的运行效率
代码演示:
- //实现了Runnable接口
- public class Demo1 {
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- Thread t1 = new Thread(myThread,"窗口1");
- Thread t2 = new Thread(myThread,"窗口2");
- Thread t3 = new Thread(myThread,"窗口3");
- t1.start();
- t2.start();
- t3.start();
- }
- }
- class MyThread implements Runnable{
- private int ticket=100;
-
- Object o=new Object();
- @Override
- public void run() {
- //Object o=new Object(); 放在这就不是同一个对象了
- while (true){
- synchronized(o){
- //同步监视器 : 锁对象,任何一个类的对象都可以充当锁对象
- //要求多个线程 共用的是同一把锁 o 对象只是创建了一次
- //用类对象也可以MyThread.class
- //这个锁只有当某个线程 把完整的过程执行完毕之后才会释放
- //o 也可以写this 当前对象其实指的就是 myThread
- //同步代码块 不能包含多了 也不能包含少了
- //具体问题具体分析 如果包上循环这就有问题了
- if(ticket>0){
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } System.out.println(Thread.currentThread().
- getName()+" 正在卖出第"+ticket+"张票");
- ticket--;
- }else{
- break;
- }
- }
- }
- }
- }
- public class Java26 {
- public static void main(String b[]) {
- MyThread t1=new MyThread();
- MyThread t2=new MyThread();
- t1.setName("窗口1");
- t2.setName("窗口2");
- t1.start();
- t2.start();
- }
- }
- class MyThread extends Thread{
- //private int ticketcount=100;
- private static int ticketcount=100;
- private static Object o=new Object();
- //两个对象共用一份数据,同样两个对象也要共用一个所对象
-
- @Override
- public void run() {
- while (true){
- synchronized (o){//锁对象一定要唯一
- if(ticketcount<=0){
- break;
- }else{
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- ticketcount--;
- System.out.println(Thread.currentThread().getName()+"卖出一张票,还剩下"+ticketcount+"票");
- }
- }
- }
- }
- }
- //实现了Runnable接口
- public class Demo1 {
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- Thread t1 = new Thread(myThread, "窗口1");
- Thread t2 = new Thread(myThread, "窗口2");
- Thread t3 = new Thread(myThread, "窗口3");
- t1.start();
- t2.start();
- t3.start();
- }
- }
-
- class MyThread implements Runnable {
- private int ticket = 100;
- @Override
- public void run() {
- while (true) {
- show();
- if (ticket == 0)
- break;
- }
- }
- private synchronized void show() { //同步监视器是: this
- if (ticket > 0) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 正在卖出第" + ticket + "张票");
- ticket--;
- }
- }
- }
-
- //继承与Thread类
- public class Demo1 {
- public static void main(String[] args) throws InterruptedException {
- MyThread thread1 = new MyThread();
- MyThread thread2 = new MyThread();
- MyThread thread3 = new MyThread();
- thread1.setName("窗口1");
- thread2.setName("窗口2");
- thread3.setName("窗口3");
- thread1.start();
- thread2.start();
- thread3.start();
- }
- }
-
- class MyThread extends Thread {
- private static int ticket = 200;
-
- @Override
- public void run() {
- while (true) {
- show();
- if (ticket == 0)
- break;
- }
- }
- //如果不加静态默认还是this 也就是窗口 1,2,3
- private static synchronized void show() {
- //同步监视器是:MyThread.class
- if (ticket > 0) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 卖票, 号为:" + ticket);
- ticket--;
- }
- }
- }
- /*同步静态方法
- 修饰符 static synchronized 返回值类型 方法名(参数表){}
-
- 同步静态方法的锁对象是: 类名.class 是类对象 (反射讲解)*/
什么是lock锁
从jdk1.5开始,java提供了更强大的线程同步机制 通过显示的定义同步锁对象来实现同步, 同步锁使用Lock对象充当
- public class Java26 {
- public static void main(String[] args) {
- Ticket ticket = new Ticket();
- Thread t1 = new Thread(ticket, "窗口1");
- Thread t2 = new Thread(ticket, "窗口2");
- Thread t3 = new Thread(ticket, "窗口3");
- t1.start();
- t2.start();
- t3.start();
- }
- }
-
- class Ticket implements Runnable {
- private int ticket = 100;
- private ReentrantLock rtt = new ReentrantLock();
-
- @Override
- public void run() {
- while (true) {
- try {
- //第一步 加锁 以下代码都被锁住
- rtt.lock();
- //synchronized (this) {
- if (ticket <= 0) {
- //如果票数为0表示卖完了
- break;//跳出循环
- } else {
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- ticket--;
- System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩下" + ticket + "票");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //解锁
- rtt.unlock();
- }
- }
- }
- }
synchronized和lock的区别
同: 二者都可以解决线程同步问题
异:
synchronized 隐式锁 在执行完相应的同步代码之后,自动的释放锁对象
分为代码块锁 和方法锁
lock 显示锁 需要手动的启动同步 结束同步也需要手动实现
使用lock jvm将花费较少的时间来调度线程,性能更好,并且具有很好的拓展性(提供了更多的子类)
使用顺序 lock > 同步代码块 > 同步方法
1 什么是死锁
1不同的线程分别占用着对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,
2 死锁出现后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
2 死锁解决方案有哪些
1 专门的算法,原则
2 尽量减少同步资源的定义
3 尽量避免同步嵌套
- //代码演示
- public static void main(String[] args) {
- StringBuilder s1=new StringBuilder();
- StringBuilder s2=new StringBuilder();
- new Thread(){
- @Override
- public void run() {
- synchronized (s1){
- s1.append("a");
- s2.append("1");
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (s2){
- s1.append("b");
- s2.append("2");
- System.out.println(s1);
- System.out.println(s2);
- }
- }
- }
- }.start();
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (s2){
- s1.append("c");
- s2.append("3");
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (s1){
- s1.append("d");
- s2.append("4");
- System.out.println(s1);
- System.out.println(s2);
- }
- }
- }
- }).start();
- }
-
- /*有几种答案
- ab
- 12
- abcd
- 1234
- ----------
- cd
- 34
- cdab
- 3412
- ---------
- 没有结果*/
最后分享一个生产者和消费者的小案例:
生产者和消费者模式:等待唤醒机制
生产者消费者模式是一个十分经典的多线程协作的模式.
在软件开发过程中,有产生数据的代码模块,有处理数据的代码模块,
在生产者和消费者模式中,产生数据的模块就称之为生产者,
处理数据的模块就称之为消费者。
还有一个存储区连接着生产者和消费者,生产者生产的数据放到存储区中,
而消费者取数据也是从存储区中去取,这个存储区是有最大容量限制的,
如果存储区中的数据达到了最大容量,那么就要让生产者暂停生产数据,
等有容量了在进行生产。如果存储区中没有数据,
那么就要让消费者暂停处理数据,等到有数据了在进行消费。
生产者和消费者模式要解决的问题就是生产者和消费者之间处理数据的动态平衡问题。
例子:
有一个生产者生产包子,他将生产好的包子放到筐中,
放完包子由消费者从筐中拿出包子使用。
当然筐还有一个作用就是当筐中没有包子时便锁住筐,不让消费者去筐中再拿取东西,
当筐中有包子时,不让生产者再向筐中放入包子。
- //导致当前线程等到另一个线程调用该对象的notify()方法或notifyAll()方法。
- public final void wait()
- //导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
- //timeout - 以毫秒为单位等待的最长时间。
- public final void wait(long timeout)
- //唤醒正在等待对象监视器的单个线程,如果任何线程正在等待这个对象,
- //其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。
- public final void notify()
- //唤醒正在等待对象监视器的所有线程。 线程通过调用其中一个wait方法等待对象的监视器。
- public final void notifyAll()
-
- public class Java26 {
- public static void main(String[] args) {
- new Foodie().start();
- new Cooker().start();
- }
- }
- class Desk {
- //定一个标记
- //true就表示桌子上有汉堡,此时允许吃
- //false表示桌子上没有汉堡,此时厨师应该去制作汉堡
- public static boolean flag = false;
- //售卖汉堡包的总数量
- public static int count = 10;
- //生产者和消费者公用一把锁对象
- public static Object lock = new Object();
- }
- //消费
- class Foodie extends Thread {
- @Override
- public void run() {
- while (true) {
- synchronized (Desk.lock) {
- //判断桌子上是否有食物
- if(Desk.count==0){//判断今日限额是否售光
- break;
- }else{
- //判断是否已经有制作好的汉堡
- if(Desk.flag){
- //有 就消费
- System.out.println("吃完该汉堡包...");
- //吃完没有了
- Desk.flag=false;
- //叫醒厨师做
- Desk.lock.notifyAll();//叫醒所有等待的线程
- Desk.count--;//销售额-1
- }else{
- //没有等待 使用什么对象当锁,
- //那么就必须使用这个对象去掉用等待和唤醒的方法
- try {
- Desk.lock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- }
- //生产者
- class Cooker extends Thread{
- @Override
- public void run() {
- while(true){
- synchronized (Desk.lock){
- if(Desk.count==0){//判断今日限额是否售光
- break;//如果已经销售完了 退出
- }else{
- if(!Desk.flag){//还没有制作好
- //生产
- System.out.println("厨师正在制作.....");
- Desk.flag=true;
- //通知制作好了可以消费了
- Desk.lock.notifyAll();
- }else{
- try {
- //已经生产好了 等待消费者消费
- Desk.lock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。