什么是java高并发_Java高并发编程一--什么是线程

1.什么是线程

在讲解java高并发时必须要先聊聊线程,那么什么是线程呢?下面是网上的答案:1.线程:进程中负责程序执行的执行单元

线程本身依靠程序进行运行

线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2.进程:执行中的程序

一个进程至少包含一个线程

3.单线程:程序中只存在一个线程,实际上主方法就是一个主线程

4.多线程:在一个程序中运行多个任务

目的是更好地使用CPU资源

我这里通俗一点讲其实就是:一个程序里头不同的执行路径,可以放在不同的cpu里面同步运行

2.如何启动一个线程

2.1继承Thread

//在java.lang包中定义, 继承Thread类必须重写run()方法class MyThread extends Thread{    private static int num = 0;

public MyThread(){

num++;

}

@Override

public void run(){

System.out.println("主动创建的第"+num+"个线程");

}

}/*创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。

注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,

即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,

此时并不会创建一个新的线程来执行定义的任务。*/public class Test{    public static void main(String[] args){

System.out.println("主线程ID:"+Thread.currentThread().getId());

MyThread thread1 = new MyThread("thread1");

thread1.start();

MyThread thread2 = new MyThread("thread2");

thread2.run();

}

}

class MyThread extends Thread{    private String name;

public MyThread(String name){        this.name = name;

}

@Override

public void run(){

System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());

}

2.2实现Runnable接口

//在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。//实现Runnable接口必须重写其run方法。//下面是一个例子:public class Test{    public static void main(String[] args){

System.out.println("主线程ID:"+Thread.currentThread().getId());

MyRunnable runnable = new MyRunnable();

Thread thread = new Thread(runnable);

thread.start();

}

}

class MyRunnable implements Runnable{    public MyRunnable(){

}

@Override

public void run(){

System.out.println("子线程ID:"+Thread.currentThread().getId());

}

}//Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,//然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,//然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,//是不会创建新线程的,这根普通的方法调用没有任何区别。//事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。//在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。//直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,//所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

2.3使用ExecutorService、Callable、Future实现有返回结果的多线程

多线程后续会讲到,这里暂时先知道一下有这种方法即可。

/**

* 有返回值的线程

*/ @SuppressWarnings("unchecked")  public class Test{  public static void main(String[] args) throws ExecutionException,

InterruptedException{

System.out.println("----程序开始运行----");

Date date1 = new Date();

int taskSize = 5;

// 创建一个线程池

ExecutorService pool = Executors.newFixedThreadPool(taskSize);

// 创建多个有返回值的任务

List list = new ArrayList();

for (int i = 0; i < taskSize; i++) {

Callable c = new MyCallable(i + " ");

// 执行任务并获取Future对象

Future f = pool.submit(c);

// System.out.println(">>>" + f.get().toString());

list.add(f);

}

// 关闭线程池

pool.shutdown();

// 获取所有并发任务的运行结果

for (Future f : list) {

// 从Future对象上获取任务的返回值,并输出到控制台

System.out.println(">>>" + f.get().toString());

}

Date date2 = new Date();

System.out.println("----程序结束运行----,程序运行时间【"

+ (date2.getTime() - date1.getTime()) + "毫秒】");

}

}

class MyCallable implements Callable{  private String taskNum;

MyCallable(String taskNum) {

this.taskNum = taskNum;

}

public Object call() throws Exception{

System.out.println(">>>" + taskNum + "任务启动");

Date dateTmp1 = new Date();

Thread.sleep(1000);

Date dateTmp2 = new Date();

long time = dateTmp2.getTime() - dateTmp1.getTime();

System.out.println(">>>" + taskNum + "任务终止");

return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";

}

}/*代码说明:

上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。

public static ExecutorService newFixedThreadPool(int nThreads)

创建固定数目线程的线程池。

public static ExecutorService newCachedThreadPool()

创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。

如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

public static ExecutorService newSingleThreadExecutor()

创建一个单线程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,

返回Future。如果Executor后台线程池还没有完成Callable的计算,

这调用返回Future对象的get()方法,会阻塞直到计算完成。*/

3.线程的状态创建(new)状态: 准备好了一个多线程的对象

就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度

运行(running)状态: 执行run()方法

阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用

终止(dead)状态: 线程销毁

当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。  当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。  线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。  当由于突然中断或者子任务执行完毕,线程就会被消亡。7ea409ff08d9e5eceed4f5fd19ca4868.png

在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。

注:sleep和wait的区别:sleep是Thread类的方法,wait是Object类中定义的方法.

Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.

Thread.sleep和Object.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.

4.上下文切换

对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

5.线程的常用方法

编号方法说明1public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。2public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。3public final void setName(String name)改变线程名称,使之与参数 name 相同。4public final void setPriority(int priority)更改线程的优先级。5public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。6public final void join(long millisec)等待该线程终止的时间最长为 millis 毫秒。7public void interrupt()中断线程。8public final boolean isAlive()测试线程是否处于活动状态。9public static void yield()暂停当前正在执行的线程对象,并执行其他线程。10public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。11public static Thread currentThread()返回对当前正在执行的线程对象的引用。

6.停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。

停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。

在Java中有以下3种方法可以终止正在运行的线程:使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。

使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

暂停线程

interrupt()方法

7.线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。

设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。

设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:

public final void setPriority(int newPriority){

ThreadGroup g;

checkAccess();        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {            throw new IllegalArgumentException();

}        if((g = getThreadGroup()) != null) {            if (newPriority > g.getMaxPriority()) {

newPriority = g.getMaxPriority();

}

setPriority0(priority = newPriority);

}

}//在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,//则JDK抛出异常throw new IllegalArgumentException()。//JDK中使用3个常量来预置定义优先级的值,代码如下:public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;/*线程优先级特性:

继承性

比如A线程启动B线程,则B线程的优先级与A是一样的。

规则性

高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。

随机性

优先级较高的线程不一定每一次都先执行完。

*/

8.守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。

Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)

在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)

不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值