Java多线程入门
来源:互联网 发布:速卖通行业数据分析 编辑:程序博客网 时间:2024/06/06 07:06
说来惭愧,距离第一次发布Java的博客已经过去整整一个多月了,可恶的是我居然刚到多线程这里徘徊,这效率可真是急煞人也,兴许是中间参杂了一些其他的技术课程给耽搁了,感觉每天都很忙,备忘录里一大堆的事情数都数不过来,想好好总结一下刚结束的网络程序设计C#+SQLSever建站过程,又想将`scrapy框架添个redis再梳理梳理,还忙着复习复习下周就结课的操作系统,又要抽空看看多媒体的PCA,特征脸,K-L变换,最近又接了个牛客网大使的活,马上双十一也来了,又想去蹲点捡个垃圾建个NAS玩玩。。等等备忘录事件,所以 :) 很忙。忙归忙,活还是要干的,今天来梳理梳理Java的多线程知识
想要理解多线程先来了解多进程,线程和进程有很大的关系
多进程
进程是程序在操作系统上运行的过程,是系统进行资源分配和调度的独立单位,一个程序可由多个进程共用,一个进程活动时可顺序执行若干个程序,进程包括程序、数据和PCB(进程控制块) ,(并发是指一段时间内同时运行多个进程,不是同步;并行是指在一个时间点同时运行多个进程,同步)
多进程实际上是CPU交替轮流执行多个进程的结果,是并发的
多线程
在一个进程内部可执行多个任务,一般将进程内部的任务称为线程。
线程是在进程的概念基础上提出来的, 线程和进程都是与计算机中的并发执行相关概念
在一个进程内的多个线程是共享一个存储空间的,在多进程程序中通信切换进程时需要改变地址空间位置,而在多线程程序只需要改变执行次序,因为它们都位于同一个存储空间内。
Java多线程
在Java中只有实现类Runnable接口的类对象才能称为线程。Java提供了两种途径实现多线程:
一、继承Thread类(该类已实现Runnable接口),
二、直接实现Runnable接口。
一、Thread类
Thread类属于java.lang包,故系统运行时会自动导入,Thread已经实现Runnable接口,故只需要让一个类继承Thread类,并将线程的代码写在run()方法中,即重写Thread类的run()方法即可创建线程。
Thread多线程类定义如下
[public] class 类名 extends Thread{ 属性; 方法; public void run(){ //线程程序代码 }}
一看就懂,接下来实战一下
EG:
public class ThreadDemo1 extends Thread{ private String name; public ThreadDemo1(String name){ setName(name); } public void run(){ for (int i=0;i<10;i++) { System.out.println(getName()+" "+i); } } public static void main(String []args){ ThreadDemo1 t1=new ThreadDemo1("xiancheng1"); ThreadDemo1 t2=new ThreadDemo1("xiancheng2"); t1.start(); t2.start(); }}
上面的代码中值得注意的是start()方法和set Name以及getName方法,
因为线程的运行需要本机操作系统的支持,所以通过start()方法启动线程。而且一个线程对象只能调用一次start方法,否则会抛出“IllegalThreadStateException”异常,set Name和getName方法是Thread类的静态方法,用来设置和得到线程名称。
二、Runnable接口创建线程
通过实现Runnable接口的抽象方法run()方法即可
定义
[public] class 类名 implements Runnable{ 属性; 方法; public void run(){ //线程程序代码 }}
EG:
import java.util.Date;class ThreadDemo implements Runnable{ public void run(){ for(int i=1;i<=5;i++){ System.out.println("now "+Thread.currentThread().getName()+", "+i+" "+(new Date())); } }}public class ThreadDemo2 { public static void main(String []args){ ThreadDemo tm=new ThreadDemo(); Thread t1=new Thread(tm,"线程1"); Thread t2=new Thread(tm,"线程2"); t1.start(); t2.start(); }}
上面因为Runnable接口对线程没有任何支持,因此在获得线程实例后,必须通过Thread类的构造方法来实现。
说到这里就不得不说一说Runnable和Thread到底用哪个比较合适了,
比如:在一个售票系统中:
使用继承Thread类来设计
class ticket extends Thread{ private int tick=5; private String name; public ticket(String name){ setName(name); } public void run(){ while(tick>0) if(tick>0){ System.out.println(Thread.currentThread().getName()+"卖出"+(tick--)+"张票"); } }}public class ThreadDmo4 { public static void main(String []args){ ticket window1=new ticket("first"); ticket window2=new ticket("second"); window1.start(); window2.start(); }}
可以看出上面Thread实现的窗口售票显然是不合适的
用Runnable接口实现
class ticket1 implements Runnable{ private int tick=5; private String name; public void run(){ while(tick>0) if(tick>0){ System.out.println(Thread.currentThread().getName()+"卖出"+(tick--)+"张票"); } }}public class ThreadDemo5 { public static void main(String []args){ ticket1 window=new ticket1(); Thread t1=new Thread(window,"first"); Thread t2=new Thread(window,"second"); t1.start(); t2.start(); }}
上面虽然也是两个线程,但是每个线程都是调用同一个run()方法,访问的都是同样的资源,所以实现了共享。
其实,上述问题也可以用static变量来实现,只是容易发生资源被抢占的风险(即另一个窗口迟迟无法售票)。
线程的状态(参考Java并发编程:线程的基本状态)
一、线程的基本状态
线程基本上有5种状态,分别是:NEW、Runnable、Running、Blocked、Dead。
1)新建状态(New)
当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable)
当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running)
当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中
4)阻塞状态(Blocked)
处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1、等待阻塞
运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2、同步阻塞
线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3、其他阻塞
通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead)
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
注意:对于Java的状态是一个动态的概念,Java的application应用程序的主线程是main()方法的执行步骤,对于Java的applet小应用程序,其主线程是按其声明周期执行的步骤。
线程的部分基本方法
获取并设置线程的名称
Thread.currentThread() //可获得当前线程的对象引用
一般若线程没有设置线程名称,系统会自动命名,当然也可以使用setName去设置,使用getName去获得
线程的优先级
在Java中每个线程都有优先级,一般取值范围是:1~10,默认为5,可以使用Thread类中的setPriority()方法设置一个线程的优先级,当然啦范围必须在1~10内,否则会产生异常。在Java中定义了三种优先级,MIN_PRIORITY
(最低表示为1),MAX_PRIORITY
(最高表示为10),NORM_PRIORITY
(默认表示为5)。
示例:
class ThreadDemosix implements Runnable{ public void run(){ for(int i=1;i<=10;i++){ System.out.println("now:"+Thread.currentThread().getName()+", i="+i); } }}public class ThreaDemo6 { public static void main(String []args){ ThreadDemosix tm=new ThreadDemosix(); Thread t1=new Thread(tm,"name-1"); Thread t2=new Thread(tm,"name-2"); Thread t3=new Thread(tm,"name-3"); t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(2); t3.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); t3.start(); }}
事实上从程序的运行结果来看,线程的优先级似乎并没有起到什么太大的作用。
故注意:优先级高的线程只是优先级获得CPU,不是绝对获得CPU。此外主线程的优先级为默认值5。
线程的休眠
说白了就是指线程暂时处于阻塞状态,使用sleep,join,yield方法可以使得线程阻塞,然而它们又有着很大的不同。
sleep
1、 sleep是静态方法,在主方法内无论通过线程对象去调用sleep还是直接通过Thread.sleep形式去调用sleep方法,其都是休眠主线程;如果想要线程实例休眠,sleep方法应该放入run()方法里。
2、 当线程处于sleep时,如果线程被中断,则会抛出Interrupted Exception异常,中断线程可以使用Thread类提供的interrupt方法。
join
假设当前运行的线程为A中调用了线程B的join()方法,则A会等待B的执行,
1、如果join中没有指定时间,则线程A会等待B运行结束后才由阻塞转变为就绪状态,然后等待获取CPU。
2、如果join中指定了时间,且线程B还没有运行完,则线程A也会在时间结束时,从阻塞状态转变为就绪状态。
3、如果join指定了时间,且线程B已经执行完了,则线程A立马从阻塞状态转变为就绪状态。
yield
yield方法指当前正在运行的线程退出运行状态,暂时让给其他线程先执行,可通过Thread.yield()方法实现,该方法只能把运行权让出来,让出后,哪个抢到就是哪个的,且抢到的优先级只能大于等于当前的。而sleep则不管。
sleep是让当前线程转到阻塞状态,而调用yield则将当前线程转到就绪状态
sleep会抛出异常,而yield不会抛出任何异常
sleep具有更好的移植性。
线程同步
线程安全指多线程访问同一代码,不会产生不确定的结果
回到那个售票系统
得到结果
只是添加一个0.1秒的休眠竟然会出现这种情况,当然是不能够容忍的,假若这个休眠模拟的是网络延迟,那这个售票系统无疑得跪,故而寻找解决办法。
在Java中解决同步问题的方法有三种,其一为同步代码块,其二为同步方法,其三是JDK1.5之后加入的同步锁(需要引入包java.util.concurrent.locks.ReentrantLock)
同步代码块
synchronized(Object obj){ //同步的代码 }
package test;class ticket1 implements Runnable{ private int tick=5; private String name; public void run(){ while(tick>0) { synchronized (this) { //添加同步代码块 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (tick > 0) { System.out.println(Thread.currentThread().getName() + "卖出" + (tick--) + "张票"); } } } }}public class ThreadDemo5 { public static void main(String []args){ ticket1 window=new ticket1(); Thread t1=new Thread(window,"first"); Thread t2=new Thread(window,"second"); t1.start(); t2.start(); }}
只需添加进同步代码块,问题即解决,十分方便
同步方法
[访问控制符] synchronized 返回类型 方法名(参数列表){ //需要同步的代码 [return 返回值] }
package test;class ticket1 implements Runnable{ private int tick=5; private String name; public void run(){ while(tick>0){ this.sa(); //调用同步方法 }} public synchronized void sa(){ //同步方法 while(tick>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (tick > 0) { System.out.println(Thread.currentThread().getName() + "卖出" + (tick--) + "张票"); } } }}public class ThreadDemo5 { public static void main(String []args){ ticket1 window=new ticket1(); Thread t1=new Thread(window,"first"); Thread t2=new Thread(window,"second"); t1.start(); t2.start(); }}
资源问题也得到了解决
同步锁
在Java中,任何一个对象都有一个同步锁(设O为对象)
- 对象O的同步锁在任何时刻最多只能被一个线程拥有
若对象O的同步锁被线程T 拥有,则当其他线程访问O时,线程将被放到O的锁池中,并将它们转化为同步阻塞状态。
拥有O的锁的线程T执行完后,会自动释放O的锁,若执行中,线程T发生异常退出,则也将自动释放O的锁
若线程T在执行同步代码块时,调用了O的wait()方法,则线程T同样会是否O的锁,线程T也将进入阻塞状态
如果线程T在执行同步代码块时,调用了Thread类的sleep方法,线程T将放弃运行权,即放弃CPU,但线程T不会放弃对象O的锁,即其他线程无法执行此同步代码块
- 如果线程T释放了对象O的锁,并放弃了运行权,则CPU将会随机分配给对象O锁池中的线程,该线程也将获得对象O的锁。
以上述售票系统为例
import java.util.concurrent.locks.*;class ticket2 implements Runnable{ private int tick=5; private String name; private final ReentrantLock lock=new ReentrantLock(); public void run(){ while(tick>0) { lock.lock(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (tick > 0) { System.out.println(Thread.currentThread().getName() + "卖出" + (tick--) + "张票"); } lock.unlock(); } }}public class ThreadDemo7 { public static void main(String []args){ ticket2 window=new ticket2(); Thread t1=new Thread(window,"first"); Thread t2=new Thread(window,"second"); t1.start(); t2.start(); }}
完美实现了同步问题
sleep和wait
- sleep和wait都会让出运行权,且都会使得当前线程进入阻塞状态
- sleep属于Thread静态方法,而wait方法属于Object方法
- 定义sleep必须设置时间,而wait则可以不用
- 使用sleep可以使用interrupt方法唤醒,而执行wait方法则使用notify和notifyAll随机取出锁池中的线程唤醒
- 若线程T拥有对象O的对象锁时,执行sleep,线程T将会进入对象O的锁池,但不会释放对象O的锁,而wait,进入锁池后会释放对象O的锁。
小结
终于终于突破100了,这是第100篇博文,很开心,时间:2017年10月28日星期六16点31分,字数,嗯,这是一篇高质量的Java多线程博文,多线程应用在很多工程领域,其中面试也是少不了的一个环节,先mark,以后再来复习
- Java 多线程入门大全
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- java 多线程入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java多线程程序设计入门
- Java 多线程入门
- Cross-Company Code Depreciation Postings with RAPOST2000
- 给定一组不重叠的间隔,在间隔中插入一个新的间隔(如有必要,合并)。间隔最初按照起始时间进行排序。
- Messenger实现多进程通信
- java System.arrayCopy 参数意义,使用
- 可凝儿贵族香氛系列 嫩肤补水沐浴露
- Java多线程入门
- 百度糯米音乐盛典深圳站完美收官 小猪罗志祥嗨爆全场
- ActionContext和ServletActionContext小结
- iOS 10有啥新功能?看这一篇就够了
- 慑人于千里之外,一个新的安全巡查无人机的思路 | 新智造
- 除了科比,还有哪些NBA球星叱咤在硅谷风投圈
- linux目录结构及文件基本操作
- HDU 3685 Rotational Painting(多边形重心+凸包)
- The addressing-1.5.mar module, which is not valid, caused null java.lang.ExceptionInInitializerError