java中多线程的超详细介绍

1、线程概述

几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。

2、线程与进程

进程概述:

几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程( Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

进程特征:

1、独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间

2、动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的

3、并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

线程:

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

并发和并行:

并发:同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行

并行:同一时刻,有多条指令在多个处理器上同时执行  

多线程:

概述:

多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。

多线程优点:

1、进程之间不能共享内存,但线程之间共享内存非常容易。

2、系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高

3、Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程

3、使用多线程:

多线程的创建:

(1)、继承Thread类:

第一步:定义Thread类的之类,并重写run方法,该run方法的方法体就代表了线程需要执行的任务

第二步:创建Thread类的实例

第三步:调用线程的start()方法来启动线程

public class FirstThread extends Thread {

 private int i;
 public void run() {
  for(;i<100;i++) {
   System.out.println(getName()+" "+i);
  }
 }

 public static void main(String[] args) {
  for(int i=0;i<100;i++) {
   //调用Thread的currentThread方法获取当前线程
   System.out.println(Thread.currentThread().getName()+" "+i);
   if(i==20) {
    new FirstThread().start();
    new FirstThread().start();
   }
  }

 }

}

(2)、实现Runnable接口:

第一步:定义Runnable接口的实现类,并重写该接口的run方法,该run方法同样是线程需要执行的任务

第二步:创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

public class SecondThread implements Runnable {

 private int i;

 @Override
 public void run() {
  for(;i<100;i++) {
   System.out.println(Thread.currentThread().getName()+" "+i);
  }
 }

 public static void main(String[] args) {
  for(int i=0;i<100;i++) {
   System.out.println(Thread.currentThread().getName()+" "+i);
   if(i==20) {
    SecondThread s1=new SecondThread();
    new Thread(s1,"新线程1").start();;
    new Thread(s1,"新线程2").start();
   }
  }
 }

}

(3)、使用Callable和Future创建线程

细心的读者会发现,上面创建线程的两种方法。继承Thread和实现Runnable接口中的run都是没有返回值的。于是从Java5开始,Java提供了Callable接口,该接口是Runnable接口的增强版。Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

创建并启动有返回值的线程的步骤如下:

第一步:创建 Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建 Callable实现类的实例。从Java8开始,可以直接使用 Lambda表达式创建 Callable对象

第二步:使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值

第三步:使用FutureTask对象作为Thread对象的target创建并启动新线程

第四步:通过FutureTask的get()方法获得子线程执行结束后的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThirdThread {

 public static void main(String[] args) {
  //ThirdThread rt=new ThirdThread();
  FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
   int i=0;
   for(;i<100;i++) {
    System.out.println(Thread.currentThread().getName()+"的循环变量i"+i);
   }
   return i;
  }) ;

  for(int i=0;i<100;i++) {
   System.out.println(Thread.currentThread().getName()+"的循环变量i为"+i);
   if(i==20) {
    new Thread(task,"有返回值的线程").start();;
   }
  }
  try {
   System.out.println("子线程的返回值"+task.get());
  }catch(Exception e) {
   e.printStackTrace();
  }
 }

}

创建线程的三种方式的对比:

采用Runnable、Callable接口的方式创建多线程的优缺点:

优点:

1、线程类只是实现了 Runnable接口或 Callable接口,还可以继承其他类

2、在这种方式下,多个线程可以共享同一个 target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

缺点:

编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承 Thread类的方式创建多线程的优缺点:

优点:

编写简单,如果需要访问当前线程,则无须使用 Thread.current Thread()方法,直接使用this即可获得当前线程

缺点:

因为线程已经继承了Thread类,所以不能再继承其他类

线程的生命周期:

新建和就绪状态:

当程序使用new关键字创建一个线程后,该线程就处于新建状态。

当线程对象调用了start()方法后,该线程就处于就绪状态。

运行和阻塞状态:

如果处于就绪状态的线程获取了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。

当线程调用sleep(),调用一个阻塞式IO方法,线程会被阻塞

死亡状态:

1、run()或者call()方法执行完成,线程正常结束

2、线程抛出一个未捕获的Exception或Error

3、直接调用该线程的stop方法来结束该线程——该方法容易导致死锁,不推荐使用

线程状态转化图

4、控制线程:

(1)、join线程

Thread提供了让一个线程等待另一个线程完成的方法——join方法。当在某个程序执行流中调用其直到被 join方法加入的join线程执行完为止

public class JoinThread extends Thread {

 //提供一个有参数的构造器,用于设置该线程的名字
 public JoinThread(String name) {
  super(name);
 }

 //重写run方法,定义线程体
 public void run() {
  for(int i=0;i<10;i++) {
   System.out.println(getName()+" "+i);
  }
 }

 public static void main(String[] args) throws InterruptedException {
  //启动子线程
  new JoinThread("新线程").start();
  for(int i=0;i<10;i++) {
   if(i==5) {
    JoinThread jt=new JoinThread("被join的线程");
    jt.start();
    //main线程调用了jt线程的join方法,main线程
    //必须等jt执行结束才会向下执行
    jt.join();
   }
   System.out.println(Thread.currentThread().getName()+" "+i);
  }
 }

}

运行结果:

main 0
main 1
main 2
main 3
main 4
新线程 0
新线程 1
新线程 2
新线程 3
被join的线程 0
新线程 4
被join的线程 1
新线程 5
被join的线程 2
新线程 6
被join的线程 3
新线程 7
被join的线程 4
新线程 8
被join的线程 5
新线程 9
被join的线程 6
被join的线程 7
被join的线程 8
被join的线程 9
main 5
main 6
main 7
main 8
main 9  

(2)、后台线程:

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程( Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。

后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用 Thread对象的 setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

public class DaemonThread extends Thread {

 //定义后台线程的线程体与普通线程没有什么区别
 public void run() {
  for(int i=0;i<1000;i++) {
   System.out.println(getName()+" "+i);
  }
 }

 public static void main(String[] args) {
  DaemonThread t=new DaemonThread();
  //将此线程设置为后台线程
  t.setDaemon(true);
  t.start();
  for(int i=0;i<10;i++) {
   System.out.println(Thread.currentThread().getName()+" "+i);
  }
  //程序到此执行结束,前台线程(main)结束,后台线程也随之结束
 }

}

运行结果:

main 0
Thread-0 0
main 1
Thread-0 1
Thread-0 2
main 2
Thread-0 3
Thread-0 4
Thread-0 5
main 3
main 4
Thread-0 6
main 5
Thread-0 7
Thread-0 8
main 6
main 7
main 8
Thread-0 9
main 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21

(3)、线程睡眠:

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用 Thread类的静态 sleep方法来实现。 sleep方法有两种重载形式

static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态

static void sleep(long millis,int nanos):让当前正在执行的线程暂停millis毫秒加上nanos毫微秒,并进入阻塞状态,通常我们不会精确到毫微秒,所以该方法不常用

import java.util.Date;

public class SleepTest {

 public static void main(String[] args) throws InterruptedException {
  for(int i=0;i<10;i++) {
   System.out.println("当前时间"+new Date());
   Thread.sleep(1000);
  }
 }

}

(4)、改变线程优先级:

每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的线程则获得较少的执行机会。

每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。

Thread类提供了 setPriority(int newPriority)、 getPriority()方法来设置和返回指定线程的优先级,其中 setPriority()方法的参数可以是一个整数,范围是1-10之间,也可以使用 Thread类的如下三个静态常量

MAX_PRIORITY:其值是10

MIN_PRIORITY:其值时1

NORM_PRIPRITY:其值是5

public class PriorityTest extends Thread {

 //定义一个构造器,用于创建线程时传入线程的名称
 public PriorityTest(String name) {
  super(name);
 }

 public void run() {
  for(int i=0;i<50;i++) {
   System.out.println(getName()+",其优先级是:"+getPriority()+"循环变量的值:"+i);
  }
 }

 public static void main(String[] args) {
  //改变主线程的优先级
  Thread.currentThread().setPriority(6);
  for(int i=0;i<30;i++) {
   if(i==10) {
    PriorityTest low=new PriorityTest("低级");
    low.start();
    System.out.println("创建之初的优先级:"+low.getPriority());
    //设置该线程为最低优先级
    low.setPriority(Thread.MIN_PRIORITY);
   }
   if(i==20) {
    PriorityTest high=new PriorityTest("高级");
    high.start();
    System.out.println("创建之初的优先级"+high.getPriority());
    high.setPriority(Thread.MAX_PRIORITY);
   }
  }
 }
}

5、线程同步:

(1)、线程安全问题:

现有如下代码:

public class Account {

 private String accountNo;
 private double balance;

 public Account() {}

 public Account(String accountNo, double balance) {
  super();
  this.accountNo = accountNo;
  this.balance = balance;
 }

 public String getAccountNo() {
  return accountNo;
 }

 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }

 public double getBalance() {
  return balance;
 }

 public void setBalance(double balance) {
  this.balance = balance;
 }

 public int hashCode() {
  return accountNo.hashCode();
 }

 public boolean equals(Object obj) {
  if(this==obj) {
   return true;
  }
  if(obj!=null&&obj.getClass()==Account.class) {
   Account target=(Account)obj;
   return target.getAccountNo().equals(accountNo);
  }
  return false;
 }

}
import com.alibaba.util.Account;

public class DrawThread extends Thread{

 //模拟用户账户
 private Account account;

 //当前取钱线程所希望的钱数
 private double drawAmount;

 public DrawThread(String name,Account account,double drawAmount) {
  super(name);
  this.account=account;
  this.drawAmount=drawAmount;
 }

 //多个线程修改同一个共享数据,可能发生线程安全问题
 @Override
 public void run() {
   if(account.getBalance()>drawAmount) {
    System.out.println(getName()+"取钱成功"+" "+drawAmount);
    try {
     Thread.sleep(1);
    }catch(Exception e) {
     e.printStackTrace();
    }
    account.setBalance(account.getBalance()-drawAmount);
    System.out.println("\t余额为"+" "+account.getBalance());
   }else {
    System.out.println("余额不足,取钱失败");
   }

 }

}
import com.alibaba.util.Account;

public class DrawTest {

 public static void main(String[] args) {
  Account account=new Account("1234567",1000);
  //模拟两个线程同时操作账号
  new DrawThread("甲", account, 800).start();;
  new DrawThread("乙", account, 800).start();;
 }

}

现在我们来分析一下以上代码:

我们现在希望实现的操作是模拟多个用户同时从银行账户里面取钱,如果用户取钱数小于等于当前账户余额,则提示取款成功,并将余额减去取款钱数,如果余额不足,则提示余额不足,取款失败。

Account 类:银行账户类,里面有一些账户的基本信息,以及操作账户信息的方法

DrawThread类:继承了Thread,是一个多线程类,用于模拟多个用户操作同一个账户的信息

DrawTest:测试类

这时我们运行程序可能会看到如下运行结果:

甲取钱成功 800.0
乙取钱成功 800.0
余额为 200.0
余额为 -600.0

余额竟然为-600,余额不足也能取出钱来,这就是线程安全问题。因为线程调度的不确定性,出现了偶然的错误。

(2)、如何解决线程安全问题:

①、同步代码块:

为了解决线程问题,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:

synchronized(obj){

  //此处的代码就是同步代码块

}

我们将上面银行中DrawThread类作如下修改:

import com.alibaba.util.Account;

public class DrawThread extends Thread{

 //模拟用户账户
 private Account account;

 //当前取钱线程所希望的钱数
 private double drawAmount;

 public DrawThread(String name,Account account,double drawAmount) {
  super(name);
  this.account=account;
  this.drawAmount=drawAmount;
 }

 //多个线程修改同一个共享数据,可能发生线程安全问题
 @Override
 public void run() {
  //使用account作为同步监视器,任何线程在进入下面同步代码块之前
  //必须先获得account账户的锁定,其他线程无法获得锁,也就无法修改它
  //这种做法符合:"加锁-修改-释放锁"的逻辑
  synchronized(account) {
   if(account.getBalance()>drawAmount) {
    System.out.println(getName()+"取钱成功"+" "+drawAmount);
    try {
     Thread.sleep(1);
    }catch(Exception e) {
     e.printStackTrace();
    }
    account.setBalance(account.getBalance()-drawAmount);
    System.out.println("\t余额为"+" "+account.getBalance());
   }else {
    System.out.println("余额不足,取钱失败");
   }
  } 

 }
}

我们来看这次的运行结果:

甲取钱成功 800.0
余额为 200.0
余额不足,取钱失败

我们发现结果变了,是我们希望看到的结果。因为我们在可能发生线程安全问题的地方加上了synchronized代码块

②:同步方法:

与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用 synchronized关键字来修饰某个方法,则该方法称为同步方法。对于 synchronized修饰的实例方法(非 static方法)而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。同步方法语法格式如下:

public synchronized void 方法名(){

  //具体代码

}

③、同步锁:

从Java5开始,Java提供了一种功能更强大的线程同步机制—一通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。

Lock提供了比 synchronized方法和 synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的 Condition对象。

在实现线程安全的控制中,比较常用的是 ReentrantLock(可重入锁)。使用该Lock对象可以显式加锁、释放锁,通常使用ReentrantLock的代码格式如下:

class X{
 //定义锁对象
 private final ReentrantLock lock=new ReentrantLock();
 //...

 //定义需要保护线程安全的方法
 public void m() {
  //加锁
  lock.lock();
  try {
   //需要保证线程安全的代码
   //...method body
  }finally {
   //释放锁
   lock.unlock();
  }
 }
}

死锁:

当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁岀现。一旦岀现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下,如下程序将会出现死锁

class A{
 public synchronized void foo(B b) {
  System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入A实例的foo方法");//①

  try {
   Thread.sleep(200);
  }catch(InterruptedException e) {
   e.printStackTrace();
  }
  System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B的方法");//③
  b.last();
 }

 public synchronized void last() {
  System.out.println("进入了A类的last方法");
 }
}

class B{

 public synchronized void bar(A a) {
  System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入B实例的bar方法");//②
  try {
   Thread.sleep(200);
  }catch(InterruptedException e) {
   e.printStackTrace();
  }
  System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A的方法");//④
  a.last();
 }

 public synchronized void last() {
  System.out.println("进入了B类的last方法");
 }

}

public class DeadLock implements Runnable {

 A a=new A();
 B b=new B();

 public void init() {
  Thread.currentThread().setName("主线程");
  a.foo(b);
  System.out.println("进入了主线程之后");
 }

 @Override
 public void run() {
  Thread.currentThread().setName("副线程");
  b.bar(a);
  System.out.println("进入副线程之后");
 }

 public static void main(String[] args) {
  DeadLock d=new DeadLock();
  new Thread(d).start();
  d.init();
 }

}

运行结果:

从图中可以看出,程序既无法向下执行,也不会抛出任何异常,就一直“僵持”着。究其原因,是因为:上面程序中A对象和B对象的方法都是同步方法,也就是A对象和B对象都是同步锁。程序中两个线程执行,副线程的线程执行体是 DeadLock类的run()方法,主线程的线程执行体是 Deadlock的main()方法(主线程调用了init()方法)。其中run()方法中让B对象调用b进入foo()方法之前,该线程对A对象加锁—当程序执行到①号代码时,主线程暂停200ms:CPU切换到执行另一个线程,让B对象执行bar()方法,所以看到副线程开始执行B实例的bar()方法,进入bar()方法之前,该线程对B对象加锁——当程序执行到②号代码时,副线程也暂停200ms:接下来主线程会先醒过来,继续向下执行,直到③号代码处希望调用B对象的last()方法——执行该方法之前必须先对B对象加锁,但此时副线程正保持着B对象的锁,所以主线程阻塞;接下来副线程应该也醒过来了,继续向下执行,直到④号代码处希望调用A对象的 last()方法——执行该方法之前必须先对A对象加锁,但此时主线程没有释放对A对象的锁——至此,就出现了主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。

6、线程池:

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个 Runnable对象或 Callable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。

创建线程池的几个常用的方法:

1.newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3.newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {

 public static void main(String[] args) {
  ExecutorService pool=Executors.newFixedThreadPool(6);
  Runnable target=()->{
   for(int i=0;i<10;i++) {
    System.out.println(Thread.currentThread().getName()+"的i的值"+i);
   }
  };
  pool.submit(target);
  pool.submit(target);
  pool.submit(target);
  //关闭线程池
  pool.shutdown();

 }

}

运行结果:

pool-1-thread-1的i的值0
pool-1-thread-2的i的值0
pool-1-thread-3的i的值0
pool-1-thread-2的i的值1
pool-1-thread-1的i的值1
pool-1-thread-2的i的值2
pool-1-thread-3的i的值1
pool-1-thread-2的i的值3
pool-1-thread-1的i的值2
pool-1-thread-2的i的值4
pool-1-thread-3的i的值2
pool-1-thread-2的i的值5
pool-1-thread-1的i的值3
pool-1-thread-2的i的值6
pool-1-thread-3的i的值3
pool-1-thread-2的i的值7
pool-1-thread-1的i的值4
pool-1-thread-2的i的值8
pool-1-thread-3的i的值4
pool-1-thread-2的i的值9
pool-1-thread-1的i的值5
pool-1-thread-3的i的值5
pool-1-thread-1的i的值6
pool-1-thread-1的i的值7
pool-1-thread-1的i的值8
pool-1-thread-1的i的值9
pool-1-thread-3的i的值6
pool-1-thread-3的i的值7
pool-1-thread-3的i的值8
pool-1-thread-3的i的值9

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Java多线程下载的实现方法

    复制代码 代码如下: package cn.me.test; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; /** * 多线程下载 * 1:使用RandomAccessFile在任意的位置写入数据. * 2:需要计算第一个线程下载的数据量,可以平均分配.如果不够平均时, *    则直接最后一个线程处理相对较少

  • java基本教程之join方法详解 java多线程教程

    本章涉及到的内容包括:1. join()介绍2. join()源码分析(基于JDK1.7.0_40)3. join()示例 1. join()介绍join() 定义在Thread.java中.join() 的作用:让"主线程"等待"子线程"结束之后才能继续运行.这句话可能有点晦涩,我们还是通过例子去理解: 复制代码 代码如下: // 主线程public class Father extends Thread {    public void run() {     

  • 15个高级Java多线程面试题及回答

    Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题.在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的.他们会问面试者很多令人混淆的Java线程问题.面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面.用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的.下面这些是我在不同时间不同地点喜欢问的Jav

  • 详解三种java实现多线程的方式

    java中实现多线程的方法有两种:继承Thread类和实现runnable接口. 1.继承Thread类,重写父类run()方法 public class thread1 extends Thread { public void run() { for (int i = 0; i < 10000; i++) { System.out.println("我是线程"+this.getId()); } } public static void main(String[] args) {

  • Java多线程的用法详解

    1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( );  public Thread(Runnable target);  public Thread(String name);  public Thread(Runnable target

  • java多线程和并发包入门示例

    一.java多线程基本入门java多线程编程还是比较重要的,在实际业务开发中经常要遇到这个问题. java多线程,传统创建线程的方式有两种. 1.继承自Thread类,覆写run方法. 2.实现Runnable接口,实现run方法. 启动线程的方法都是调用start方法,真正执行调用的是run方法.参考代码如下: 复制代码 代码如下: package com.jack.thread; /** * 线程简单演示例子程序 *  * @author pinefantasy * @since 2013-

  • 深入理解JAVA多线程之线程间的通信方式

    一,介绍 本总结我对于JAVA多线程中线程之间的通信方式的理解,主要以代码结合文字的方式来讨论线程间的通信,故摘抄了书中的一些示例代码. 二,线程间的通信方式 ①同步 这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信. 参考示例: public class MyObject { synchronized public void methodA() { //do something.... } synchronized public void methodB()

  • Java多线程实现异步调用的方法

    在JAVA平台,实现异步调用的角色有如下三个角色:调用者 提货单   真实数据 一个调用者在调用耗时操作,不能立即返回数据时,先返回一个提货单.然后在过一断时间后凭提货单来获取真正的数据. 去蛋糕店买蛋糕,不需要等蛋糕做出来(假设现做要很长时间),只需要领个提货单就可以了(去干别的事情),等到蛋糕做好了,再拿提货单取蛋糕就可以了. public class Main { public static void main(String[] args) { System.out.println("ma

  • Java多线程之中断线程(Interrupt)的使用详解

    interrupt方法 interrupt字面上是中断的意思,但在Java里Thread.interrupt()方法实际上通过某种方式通知线程,并不会直接中止该线程.具体做什么事情由写代码的人决定,通常我们会中止该线程. 如果线程在调用Object类的wait().wait(long)或wait(long, int)方法,或者该类的 join() .join(long) .join(long, int) .sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态

  • java中多线程的超详细介绍

    1.线程概述 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程.当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2.线程与进程 进程概述: 几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程( Process).当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位. 进程特征: 1.独立性:进程是系统中独立存在的实体,

  • Java中ArrayList的使用详细介绍

    目录 1.ArrayList类 1.1ArrayList类概述 1.2ArrayList类常用方法 1.2.1构造方法 1.2.2成员方法 1.2.3示例代码 1.3ArrayList存储字符串并遍历 1.3.1案例需求 1.3.2代码实现 1.4ArrayList存储学生对象并遍历 1.4.1案例需求 1.4.2代码实现 1.5ArrayList存储学生对象并遍历升级版 1.5.1案例需求 1.5.2代码实现 总结 1.ArrayList类 1.1ArrayList类概述 在java中,我们会

  • Java中的阻塞队列详细介绍

    Java中的阻塞队列 1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是: 在队列为空时,获取元素的线程会等待队列变为非空. 当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程.阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素. 2.Java里的阻塞队列 JDK中提供了七个阻塞队列: ArrayBlockingQueue :一个由数组结

  • java 中继承和多态详细介绍

    继承和多态 一.this super关键字 1.this: 可以在构造器中的第一代码中调用本类中的其他构造器.this(参数) 非类方法参数中隐式传入的参数,表示调用当前方法的对象. 2.super: 可以在构造器的第一句代码调用父类的构造器.super(参数). 非静态方法中表示继承的父类对象,可以调用父类方法和属性. 二.方法的覆写: 子类重新实现了和父类一样的方法.访问修饰和异常都必须至少和父类的相同或者更大的范围. 三.方法的重载: 相同的方法的名字不同的参数列表. 四.多态: java

  • java 中函数的参数传递详细介绍

    java中函数的参数传递 总结: 1.将对象(对象的引用)作为参数传递时传递的是引用(相当于指针).也就是说函数内对参数所做的修改会影响原来的对象.   2.当将基本类型或基本类型的包装集作为参数传递时,传递的是值.也就是说函数内对参数所做的修改不会影响原来的变量.   3.数组(数组引用))作为参数传递时传递的是引用(相当于指针).也就是说函数内对参数所做的修改会影响原来的数组.   4.String类型(引用)作为参数传递时传递的是引用,只是对String做出任何修改时有一个新的String

  • java中的枚举类型详细介绍

    枚举中有values方法用于按照枚举定义的顺序生成一个数组,可以用来历遍.我们自定义的枚举类都是继承自java.lang.Enum,拥有一下实例中的功能: 复制代码 代码如下: //: enumerated/EnumClass.java // Capabilities of the Enum class import static net.mindview.util.Print.*; enum Shrubbery { GROUND, CRAWLING, HANGING } public clas

  • Java中的Object类详细介绍

    理论上Object类是所有类的父类,即直接或间接的继承java.lang.Object类.由于所有的类都继承在Object类,因此省略了extends Object关键字. 该类中主要有以下方法: toString(),getClass(),equals(),clone(),finalize(), 其中toString(),getClass(),equals是其中最重要的方法. 注意: Object类中的getClass(),notify(),notifyAll(),wait()等方法被定义为f

  • Java中四种引用类型详细介绍

    Java 四种引用类型 对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于可触及(reachable)状态,程序才能使用它.从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期.这4种级别由高到低依次为:强引用.软引用.弱引用和虚引用. ⑴强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,

  • Java中的final关键字详细介绍

    •final变量如果在变量前加final关键字,则这个变量一旦被初始化,便不可再改变. 如果一个final变量是类成员变量,则必须被初始化,且只能被初始化一次. 方法中的参数也可以是final变量.这在我们需要传递引用型的变量时非常有用,因为有时候我们并不希望调用函数修改该变量而影响到原函数中对象的值.因此将引用型变量设为final类型可以有效方式变量被调用参数修改.此时在调用方法中只可以使用该变量,但不能对其做任何修改. 复制代码 代码如下: void test(final int a){ 

  • java 中多线程生产者消费者问题详细介绍

    java 中多线程生产者消费者问题 前言: 一般面试喜欢问些线程的问题,较基础的问题无非就是死锁,生产者消费者问题,线程同步等等,在前面的文章有写过死锁,这里就说下多生产多消费的问题了 import java.util.concurrent.locks.*; class BoundedBuffer { final Lock lock = new ReentrantLock();//对象锁 final Condition notFull = lock.newCondition(); //生产者监视

随机推荐