怎样明白Java多线程?

什么是线程?

一个线程就是程序中的一条执行线索,每个线程上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,即 main 方法执行的那个线程。
Java语言中,线程有五种状态:新建、就绪、运行、阻塞及死亡

线程和进程的区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。
不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

为什么使用多线程?

【1】提高执行效率,减少程序的响应时间。因为单线程执行的过程只有一个有效的操作序列,如果某个操作很耗时(或等待网络响应),此时程序就不会响应鼠标和键盘等操作,如果使用多线程,就可以将耗时的线程分配到一个单独的线程上执行,从而使程序具备更好的交互性。
【2】与进程相比,线程的创建和切换开销更小。因开启一个新的进程需要分配独立的地址空间,建立许多数据结构来维护代码块等信息,而运行于同一个进程内的线程共享代码段、数据段、线程的启动和切换的开销比进程要少很多。同时多线程在数据共享方面效率非常高。
【3】目前市场上服务器配置大多数都是多CPU或多核计算机等,它们本身而言就具有执行多线程的能力,如果使用单个线程,就无法重复利用计算机资源,造成资源浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率
【4】利用多线程能简化程序程序的结构,是程序便于理解和维护。一个非常复杂的进程可以分成多个线程来执行

同步和异步有什么区别?

同步:(简单来说就是使线程进入一个排队的情况下,直到拿到锁就释放,防止了线程冲突)
在多线程的环境中,通常会遇到数据共享问题,为了确保共享资源的正确性和安全性,就必须对共享数据进行同步处理(也就是锁机制)。对共享数据进行同步操作(增删改),就必须要获得每个线程对象的锁(this锁),这样可以保证同一时刻只有一个线程对其操作,其他线程要想对其操作需要排队等候并获取锁。当然在等候队列中优先级最高的线程才能获得该锁,从而进入共享代码区。Eg:图片上传
Java语言在同步机制中提供了语言级的支持,可以通过使用synchronized关键字来实现同步,但该方法是以很大的系统开销作为代价的,有时候甚至可能造成死锁,所以,同步控制并不是越多越好,要避免所谓的同步控制。

实现同步的方法有两种:
①同步方法(this锁)。
②同步代码块(this锁或者自定义锁)当使用this锁时,就与同步方法共享同一锁,只有当①释放,②才可以使用。同时,同步代码块的范围也小于同步方法,建议使用,相比之下能够提高性能。

异步:
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

如何实现多线程?

【1】继承Thread类,重写run()方法
Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()方法,start()方法是一个本地(native)方法,它将启动一个新的线程,并执行run()方法(执行的是自己重写了Thread类的run()方法),同时调用start()方法并不是执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行多线程代码由操作系统决定。

class MyThread extends Thread{//创建线程类
    public void run(){
       System.out.println("Thread Body");//线程的函数体
    }
}

public class Test{
   public static void main(String[] args){
     MyThread thread = new Thread
     thread.run();//开启线程
   }

【2】实现Runnable接口,并实现该结构的run()方法
1)自定义实现Runnable接口,实现run()方法。
2)创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。
3)调用Thread的start()方法。

class MyThread implements Runnable{
   pulic void run(){
      System.out.println("Thread Body");
   }
}
public class Test{
  public static void main(String[] args){
     MyThread myThread = new MyThread;
     Thread thread = new Thread(myThread);
     thread.start();//启动线程
  }
}

其实,不管是哪种方法,最终都是通过Thread类的API来控制线程。
建议
当需要实现多线程时,一般推荐使用Runnable接口方式,因为Thread类定义了多种方法可以被派生类使用或重写,但是只有run()方法必须被重写,在run()方法中实现这个线程的主要功能,这当然也是实现Runnable接口所需的方法。
再者,我们很多时候继承一个类是为了去加强和修改这个类才去继承的
因此,如果我们没有必要重写Thread类中的其他方法,那么通过继承Thread类和实现Runnable接口的效果是相同的,这样的话最好还是使用Runnable接口来创建线程。

Thread类中的run()方法与start()方法有什么区别?

通常,系统通过调用线程类的start()方法启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以别JVM调用执行,执行的过程中,JVM通过调用Thread类的run()方法来完成实际的操作,当run()方法结束后,线程也就会终止。
如果直接调用线程类的run()方法,就会被当做一个普通函数调用,程序中仍然只有一个主程序,也就是说start()方法能够异步调用run()方法,但是直接调用run()方法却是同步的,也就无法达到多线程的目的

多线程数据同步实现的方法有哪些?

当使用多线程访问同一数据时,非常容易出现线程安全问题,因此采用同步机制解决。Java提供了三种方法:
【1】synchronized关键字
在Java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程调用对象的synchronize代码时,需要先获取这个锁,然后再去执行相应的代码,执行结束后,释放锁。
synchronize关键字主要有两种用法(synchronize方法和synchronize代码块)

1)synchronized方法:在方法的声明前加synchronize关键字:

    public synchronize void test();

将需要对同步资源的操作放入test()方法中,就能保证此资源在同一时刻只能被一个线程调用,从而保证资源的安全性。然而当此方法体规模非常大时,会影响系统的效率。

2)synchronized块:既可以把任意的代码段声明为synchronized,也可以指定上锁的对象,有非常高的灵活性。

    synchronized(syncObject){
    //访问syncObject的代码块
    }

【2】wait()方法与notify()方法
当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,线程A2也要执行同一对象的统同一synchronize的代码,线程A2将要等到线程A1执行完后执行,这种情况可以使用wait()和notify()。必须是统一把锁,才生效。

 class NumberPrint implements Runnable{  
        private int number;  
        public byte res[];  
        public static int count = 5;  
        public NumberPrint(int number, byte a[]){  
            this.number = number;  
            res = a;  
        }  
        public void run(){  
            synchronized (res){  
                while(count-- > 0){  
                    try {  
                        res.notify();//唤醒等待res资源的线程,把锁交给线程(该同步锁执行完毕自动释放锁)  
                        System.out.println(" "+number);  
                        res.wait();//释放CPU控制权,释放res的锁,本线程阻塞,等待被唤醒。  
                        System.out.println("------线程"+Thread.currentThread().getName()+"获得锁,wait()后的代码继续运行:"+number);  
                    } catch (InterruptedException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }//end of while  
                return;  
            }//synchronized  
              
        }  
    }  
    public class WaitNotify {
        public static void main(String args[]){  
            final byte a[] = {0};//以该对象为共享资源  
            new Thread(new NumberPrint((1),a),"1").start();  
            new Thread(new NumberPrint((2),a),"2").start();  
        }  
    }  

输出结果:
1
2
------线程1获得锁,wait()后的代码继续运行:1
1
------线程2获得锁,wait()后的代码继续运行:2
2
------线程1获得锁,wait()后的代码继续运行:1
1
------线程2获得锁,wait()后的代码继续运行:2

stop()和 suspend()方法为何不推荐使用?

stop()不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。
suspend() 方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果 它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend()。
而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。

wait()与sleep()的区别

关于这两者已经在上面进行详细的说明,这里就做个概括好了:
(1)继承类sleep()来自Thread类,wait()来自Object类.
(2)是否释放锁:调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
(3)系统资源占用:sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU
(4)使用方式:sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用

sleep()与yield()的区别?

1)sleep()给其他线程运行机会时,不考虑线程的优先级,因此会给低优先级的线程以运行的机会,而yield()方法只会给相同优先级或更高优先级的线程以运行的机会
2)sleep()方法会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程很可能在进入到可执行状态后马上又被执行

终止线程的方法有哪些?

1)stop()方法,它会释放已经锁定的所有监视资源,如果当前任何一个受监视资源保护的对象处于一个不一致的状态(执行了一部分),其他线程线程将会获取到修改了的部分值,这个时候就可能导致程序执行结果的不确定性,并且这种问题很难被定位。
2)suspend()方法容易发生死锁。因为调用suspend()方法不会释放锁,这就会导致此线程挂起。
鉴于以上两种方法的不安全性,Java语言已经不建议使用以上两种方法来终止线程了。
3)一般建议采用的方法是让线程自行结束进入Dead状态。一个线程进入Dead状态,既执行完run()方法,也就是说提供一种能够自动让run()方法结束的方式,在实际中,我们可以通过flag标志来控制循环是否执行,从而使线程离开run方法终止线程。

public class MyThread implements Runnable{
 private volatile Boolean flag;
 public void stop(){
     flag=false;
 }
 public void run(){
  while(flag);//do something
 }
}

后期可能还会补充,尽请期待吧~1


  1. 动力源泉:编写不易,如果有帮助到的话,可以关注,点赞和收藏哦~ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值