首先了解一下什么是进程和线程
我们在windows上经常使用各类软件,举个最简单的例子,我们运行一个word软件,这时候系统就为word分配了一个进程,而当我们在打字的时候word会进行自动拼写检查,这个拼写检查程序就相当于一个线程。线程实际上就是在进程的基础上进一步划分,如果一个进程没有了,则线程就会消失,但是进程未必会消失,而且所有的线程都是在进程的基础上同时运行。
线程的状态:
在Java中的实现可以采用如下两种方式:
1.继承Thread类
Thread类是在java.lang包中定义的,一个类只要继承了Thread类,此类就称为多线程操作类。在Thread子类中,必须明确的覆写Thread类中run()方法,此方法为线程的主体。
多线程的定义格式:
class 类名称 extends Thread{//继承线程主体
属性://类中定义属性
方法://类中定义方法
//覆写Thread类中run()方法,此方法是线程的主体
public void run(){
线程主体;
}
}
例子:
class MyThread extends Thread{
private String name;//表示线程的名称
public MyThread(String name) {
this.name = name;//通过构造方法配置name属性
}
@Override
public void run() {
for(int i = 0 ; i < 10; ++i) {
System.out.println(name+"运行,i="+i);
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m1 = new MyThread("线程A");//实例化对象
MyThread m2 = new MyThread("线程B");//实例化对象
m1.run();
m2.run();
}
}
//结果:
线程A运行,i=0
线程A运行,i=1
线程A运行,i=2
线程A运行,i=3
线程A运行,i=4
线程A运行,i=5
线程A运行,i=6
线程A运行,i=7
线程A运行,i=8
线程A运行,i=9
线程B运行,i=0
线程B运行,i=1
线程B运行,i=2
线程B运行,i=3
线程B运行,i=4
线程B运行,i=5
线程B运行,i=6
线程B运行,i=7
线程B运行,i=8
线程B运行,i=9
上面的例子只是线程A执行完,然后B执行。并没有达到并发效果。以上程序实际上还是依靠 对象.方法() 。如果想要并发执行,则需要调用Thread类中定义的start()方法,实际上最终调用的还是run()方法。只是此时多个线程,那个线程抢到了资源,哪个就先运行。将上面的例子更改为:
class MyThread extends Thread{
private String name;//表示线程的名称
public MyThread(String name) {
super();
this.name = name;//通过构造方法配置name属性
}
@Override
public void run() {
for(int i = 0 ; i < 10; ++i) {
System.out.println(name+"运行,i="+i);
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m1 = new MyThread("线程A");//实例化对象
MyThread m2 = new MyThread("线程B");//实例化对象
m1.start();
m2.start();
}
}
//结果:
线程A运行,i=0
线程B运行,i=0
线程A运行,i=1
线程B运行,i=1
线程A运行,i=2
线程B运行,i=2
线程A运行,i=3
线程B运行,i=3
线程A运行,i=4
线程B运行,i=4
线程A运行,i=5
线程B运行,i=5
线程B运行,i=6
线程B运行,i=7
线程B运行,i=8
线程B运行,i=9
线程A运行,i=6
线程A运行,i=7
线程A运行,i=8
线程A运行,i=9
2.Runnable接口实现多线程
Runnable接口中只定义了一个抽象方法:
public void run();
通过Runnable接口实现多线程:
class 类名称 implements Runnable{
属性...;//实现Runnable接口
方法...;//类中定义方法
public void run(){//覆写Runnable接口中的run()方法
线程主体;
}
}
启动线程需要依靠Thread类,但是Runnable接口中并没有Thread类中的run()方法,因此有下面的代码:
例子:
class MyThread implements Runnable{
private String name;//表示线程的名称
public MyThread(String name) {
this.name = name;//通过构造方法配置name属性
}
@Override
public void run() {
for(int i = 0 ; i < 10; ++i) {
System.out.println(name+"运行,i="+i);
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m1 = new MyThread("线程A");//实例化对象
MyThread m2 = new MyThread("线程B");//实例化对象
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
t1.start();
t2.start();
}
}
Thread类的定义:
public class Thread extends Object implements Runnable
从此格式可以发现,Thread类也是Runnable接口的子类
使用Thread类的操作多线程,无法达到资源共享的目的,而是用Runnable接口实现的多线程操作可以实现资源共享。
例如:继承Thread类实现卖票过程
class MyThread extends Thread{
private int tickets = 5;
@Override
public void run() {
for(int i = 0 ; i < 100; ++i ) {
if(this.tickets>0) {
System.out.println("卖票:tickets"+tickets--);
}
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
MyThread m3 = new MyThread();
m1.run();
m2.run();
m3.run();
}
}
结果:
卖票:tickets5
卖票:tickets4
卖票:tickets3
卖票:tickets2
卖票:tickets1
卖票:tickets5
卖票:tickets4
卖票:tickets3
卖票:tickets2
卖票:tickets1
卖票:tickets5
卖票:tickets4
卖票:tickets3
卖票:tickets2
卖票:tickets1
我们会发现这一共卖出了15张票,这是不正确的,出现了丢失更新问题,没有达到共享资源的目的。
那么使用Runnable接口来实现共享资源的目的,代码如下:
class MyThread implements Runnable{
private int tickets = 5;
@Override
public void run() {
for(int i = 0 ; i < 100; ++i ) {
if(this.tickets>0) {
System.out.println("卖票:tickets"+tickets--);
}
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m = new MyThread();
new Thread(m).run();
new Thread(m).run();
new Thread(m).run();
}
}
结果:
卖票:tickets5
卖票:tickets4
卖票:tickets2
卖票:tickets1
但是我们又发现,此时并没有出现并行运行,因此只要将main()方法里的run()更改为start()方法,就可以了。
start与run的区别:
start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
run() : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
综上:Runnable接口比继承Thread类有着明显的优点,Runnable接口适合多个相同程序代码的线程去处理同一个资源,可以避免由于单继承局限性所带来的影响,增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。我们知道“一个类只能有一个父类,但是却能实现多个接口,因此Runnable具有更好的扩展性。
1,线程名称
线程的名字在线程启动之前设置名字,避免重名
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadNameDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
new Thread(mt).start() ; // 系统自动设置线程名称
new Thread(mt,"线程-A").start() ; // 手工设置线程名称
new Thread(mt,"线程-B").start() ; // 手工设置线程名称
new Thread(mt).start() ; // 系统自动设置线程名称
new Thread(mt).start() ; // 系统自动设置线程名称
}
};
结果不相同,因为是并行的
线程-A运行,i = 0
线程-B运行,i = 0
Thread-0运行,i = 0
Thread-1运行,i = 0
线程-B运行,i = 1
Thread-2运行,i = 0
线程-A运行,i = 1
Thread-2运行,i = 1
线程-B运行,i = 2
Thread-1运行,i = 1
Thread-1运行,i = 2
Thread-0运行,i = 1
Thread-0运行,i = 2
Thread-2运行,i = 2
线程-A运行,i = 2
2.currentThread(),获取当前线程
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class Demo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
new Thread(mt,"线程").start() ; // 系统自动设置线程名称
mt.run();
}
};
//结果:
main运行,i = 0
main运行,i = 1
线程运行,i = 0
main运行,i = 2
线程运行,i = 1
线程运行,i = 2
此时发现,程序中由主方法直接通过线程对象调用里面的run()方法,所有此时的结果包含一个"main",此线程就是由“mt.run()”产生的,因为调用此语句是由主方法完成的。也就是说,主方法本身也是一个线程---主线程。
既然主方法都是以线程的形式出现,那么JAVA启动时候运行了多少线程?
至少启动了两个。每当JAVA执行,都会启动一个JVM,每一个JVM都是在操作系统中启动一个线程。JAVA本身有垃圾回收机制,所以至少启动了两个线程:主线程,GC。
3.判断线程是否在执行:isAlive
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadAliveDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
System.out.println("线程开始执行之前 --> " + t.isAlive()) ; // 判断是否启动
t.start() ; // 启动线程
System.out.println("线程开始执行之后 --> " + t.isAlive()) ; // 判断是否启动
for(int i=0;i<3;i++){
System.out.println(" main运行 --> " + i) ;
}
// 以下的输出结果不确定
System.out.println("代码执行之后 --> " + t.isAlive()) ; // 判断是否启动
}
};
//结果
线程开始执行之前 --> false
线程开始执行之后 --> true
main运行 --> 0
main运行 --> 1
main运行 --> 2
代码执行之后 --> true
线程运行,i = 0
线程运行,i = 1
线程运行,i = 2
4,线程强制运行:join()
可以通过join()方法使得一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后,才可以继续运行。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class Demo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
for(int i=0;i<50;i++){
if(i>10){
try{
t.join() ; // 线程强制运行
}catch(InterruptedException e){}
}
System.out.println("Main线程运行 --> " + i) ;
}
}
};
5 .线程的休眠
在线程中允许一个线程进行暂时的休眠,直接使用Thread.sleep()方法即可。
sleep定义格式:
public static void sleep(long milis,int nanos)throws InterruptedException
首先,static,说明可以由Thread类名称调用,其次throws表示如果有异常要在调用此方法处处理异常。所以sleep()方法要有InterruptedException 异常处理,而且sleep()调用方法通常为Thread.sleep(500) ;形式。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<50;i++){
try{
Thread.sleep(500) ; // 线程休眠
}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class Demo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
}
};
6.线程的中断
一个线程可以被另一个线程中断其操作的状态,使用 interrupt()方法完成。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println("1、进入run()方法") ;
try{
Thread.sleep(10000) ; // 线程休眠10秒
System.out.println("2、已经完成了休眠") ;
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
System.out.println("4、run()方法正常结束") ;
}
};
public class Demo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
t.interrupt() ; // 中断线程执行
}
};
//结果:
1、进入run()方法
3、休眠被终止
4、run()方法正常结束
会看到,在1到3的时候会因为线程休眠2秒而卡顿了一下。但是,既然线程中断了,那么4,这句话不应该打出来的,因此要在3,线程被终止处添加一句话rutrun,表示返回调用处。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println("1、进入run()方法") ;
try{
Thread.sleep(10000) ; // 线程休眠10秒
System.out.println("2、已经完成了休眠") ;
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
return ; // 返回调用处
}
System.out.println("4、run()方法正常结束") ;
}
};
public class Demo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
t.interrupt() ; // 中断线程执行
}
};
//结果:
1、进入run()方法
3、休眠被终止
7.后台线程
在Java中,只要一个线程没有执行完(一个线程在运行),则整个Java的进程不会消失,所以此时可以设置一个后台线程,这样即使java线程结束了,则后台线程依旧会继续执行。要想实现这个操作,要使用setDaemon()方法完成。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
int i=0;
while(true){ //设置死循环,这样来实现线程不断运行,设置后台运行。
System.out.println(Thread.currentThread().getName() + "在运行。"+i) ;
}
}
};
public class ThreadDaemonDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.setDaemon(true) ; // 此线程在后台运行
t.start() ; // 启动线程
}
};
8.线程的优先级
获取优先级的方法:getPriority();
优先级分为最低,最高,普通三个(Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY),
设置优先级:
MyThread t1=new MyThread();
Thread t3 = new Thread(t1,"线程C") ;//实例化线程对象
t3.setPriority(Thread.MIN_PRIORITY) ;//设置优先级为最低
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(500) ; // 线程休眠
}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class Demo{
public static void main(String args[]){
Thread t1 = new Thread(new MyThread(),"线程A") ; // 实例化线程对象
Thread t2 = new Thread(new MyThread(),"线程B") ; // 实例化线程对象
Thread t3 = new Thread(new MyThread(),"线程C") ; // 实例化线程对象
t1.setPriority(Thread.MIN_PRIORITY) ; // 优先级最低
t2.setPriority(Thread.MAX_PRIORITY) ; // 优先级最低
t3.setPriority(Thread.NORM_PRIORITY) ; // 优先级最低
t1.start() ; // 启动线程
t2.start() ; // 启动线程
t3.start() ; // 启动线程
}
};
//结果:
线程B运行,i = 0
线程A运行,i = 0
线程C运行,i = 0
线程B运行,i = 1
线程C运行,i = 1
线程A运行,i = 1
线程B运行,i = 2
线程C运行,i = 2
线程A运行,i = 2
线程B运行,i = 3
线程C运行,i = 3
线程A运行,i = 3
线程B运行,i = 4
线程C运行,i = 4
线程A运行,i = 4
主方法的优先级
主方法的优先级是NORM_PRIORITY.
public class Demo{
public static void main(String args[]){
System.out.println("主方法的优先级:" +
Thread.currentThread().getPriority()) ; // 取得主方法的优先级
System.out.println("MAX_PRIORITY = " + Thread.MAX_PRIORITY) ;
System.out.println("NORM_PRIORITY = " + Thread.NORM_PRIORITY) ;
System.out.println("MIN_PRIORITY = " + Thread.MIN_PRIORITY) ;
}
};
//结果:
主方法的优先级:5
MAX_PRIORITY = 10
NORM_PRIORITY = 5
MIN_PRIORITY = 1
9.线程的礼让
yield()方法实现线程的礼让。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(500);//休眠一下
}catch(Exception e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
if(i==2){
System.out.print("线程礼让:") ;
Thread.currentThread();
Thread.yield() ; // 首先获取当前线程,然后线程礼让
}
}
}
};
public class Demo{
public static void main(String args[]){
MyThread my = new MyThread() ; // 实例化MyThread对象
Thread t1 = new Thread(my,"线程A") ;
Thread t2 = new Thread(my,"线程B") ;
t1.start() ;
t2.start() ;
}
};
//结果:
线程A运行,i = 0
线程B运行,i = 0
线程A运行,i = 1
线程B运行,i = 1
线程A运行,i = 2
线程B运行,i = 2
线程礼让:线程礼让:线程B运行,i = 3
线程A运行,i = 3
线程A运行,i = 4
线程B运行,i = 4
要求:设计一个线程操作类,要求可以生产三个线程对象,并可以分别设置三个线程的休眠时间,如下所示:
线程A ,休眠10秒
线程B,休眠20秒
线程C,休眠30秒
使用Runnable接口,则类中是没有线程名称存在的,所以应该单独建立一个name属性,已保存线程的名称。
class MyThread implements Runnable{ // 实现Runnable接口
private String name;
private int time;
public MyThread(String name, int time) {
super();
this.name = name;//设置线程名称
this.time = time;//设置休眠时间
}
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(this.time);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+"线程,休眠"+this.time+"毫秒");
}
}
}
public class Demo{
public static void main(String args[]){
MyThread m1 = new MyThread("线程A",10000) ; // 实例化MyThread对象
MyThread m2 = new MyThread("线程B",20000) ;
MyThread m3 = new MyThread("线程C",30000) ;
new Thread(m1).start();
new Thread(m2).start();
new Thread(m3).start();
}
}
//结果
线程A线程,休眠10000毫秒
线程A线程,休眠10000毫秒
线程B线程,休眠20000毫秒
线程A线程,休眠10000毫秒
线程C线程,休眠30000毫秒
线程A线程,休眠10000毫秒
线程B线程,休眠20000毫秒
线程A线程,休眠10000毫秒
线程C线程,休眠30000毫秒
线程B线程,休眠20000毫秒
线程B线程,休眠20000毫秒
线程C线程,休眠30000毫秒
线程B线程,休眠20000毫秒
以卖火车票为例,如果现在想要买火车票可以去很多售票点买票,但是一趟车的票数是固定的,所有的售票点的余票都是统一的,将各个售票点看作是多线程,所有的线程都应该改有同一份余票。
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for(int i = 0 ; i< 100; ++i) {
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket="+ticket--);
}
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m = new MyThread();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
//结果:
卖票:ticket=5
卖票:ticket=3
卖票:ticket=4
卖票:ticket=2
卖票:ticket=1
卖票:ticket=0
卖票:ticket=-1
上面的代码出现了负数的情况,这是因为加入了延迟操作,那么就有可能出现一个线程还没在对票数进行减的操作,其他线程就将票数减少了。
想要解决上面的问题,就必须使用同步,所谓同步就是只多个操作在同一个时间段内只能有一个线程在进行操作,其他线程要等待此线程完成之后才可以继续执行。
使用同步解决问题
方法一、使用同步代码块
同步代码块:使用synchronize关键字声明的代码块,称为同步块
同步代码块格式:
synchronized(同步对象){
需要同步的代码;
}
同步时,必须指明同步对象,一般情况下会将当前对象作为同步对象,使用this表示
之前学过的代码块:
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for(int i = 0 ; i< 100; ++i) {
synchronized (this) {
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket="+ticket--);
}
}
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m = new MyThread();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
//
结果卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=2
卖票:ticket=1
上面的代码虽然完成了同步,但是程序的效率会非常的低。因此我们将其更改为同步方法。
方法二、同步方法
同步方法定义格式:
synchronized 方法返回值 方法名称(参数列表){}
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for(int i = 0 ; i< 100; ++i) {
this.sale();
}
}
public synchronized void sale() {
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket="+ticket--);
}
}
}
public class Demo{
public static void main(String args[]) {
MyThread m = new MyThread();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
唤醒操作有两个方法:notify和notifyAll方法。两者的区别是:如果当前有很多个线程正在排队,使用notify方法,会唤醒第一个等待的线程执行,而如果使用了notifyall方法,则会唤醒所有的等待线程,哪个线程的优先级高,哪个线程就有可能先执行。