Java之多线程(一)
一,前言
今天总结一些关于线程方面的知识,说到线程可谓是无人不知,毕竟这东西不管是在工作开发中,还是实际生活中都时时存在着。关于线程方面的内容非常多,从简单的单线程,多线程,线程安全以及到高并发等等,当然也包括信息通信。
当然这次从线程的基本开始,后面也会慢慢的补充线程的高级使用,这也算是让自己再复习一次了(哈哈)。
以下内容包括:
- 二,线程介绍
- 三,线程的创建
- 四,线程安全
- 五,线程池
二,线程介绍
先来介绍几个关于线程方面的概念。
2.1,并行与并发
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
在我们的实际操作系统中,会存在多个应用程序,那么在单CPU的情况下如何同时执行多个程序。从宏观上观察多个程序同时运行,但是从微观角度看其实这是CPU在多个程序之间进行高速切换,只是切换的速度是非常的快,因此看上去像是在一起执行。
反过来说,在多CPU的情况同样是在多程序之间来回切换,但是这种情况下不再是一个CPU在处理。就像工人一样,人越多干活就越快,也因此大大提高了计算机的运行速度。
2.2 ,线程与进程
进程:当应用程序进入到内存中时,每个应用程序都有其独立的内存空间。进程就是程序在内存中的执行过程,是程序运行的基本单位。
线程:而线程是进程的执行单元,进程的执行是需要线程依靠的,可以是单线程,也可以是多线程。
一个程序运行后至少有一个进程,一个进程中可以包含多个线程 。
我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:
线程调度:
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
三,线程的创建
创建线程有两种方式:
- 继承
java.lang.Thread
类,重写run方法实现线程创建。 - 实现
java.lang.Runnable
接口,实例化其实现类对象创建线程。
3.1,Thread
先来看看API文档的说明:
Thread是一个类,但同时也实现了Runnable接口。
接着使用Thread创建线程。
public class ThreadMain {
public static void main(String[] args) {
// 1,实例化ThreadMode对象
ThreadMode thread = new ThreadMode();
// 2,调用start()方法启动线程
thread.start();
}
}
/**
* 继承Thread类
*/
class ThreadMode extends Thread {
// 1,重写父类的run方法。
@Override
public void run() {
System.out.println("使用Thread创建线程!");
}
}
3.2,构造方法
public Thread():
分配新的 Thread 对象。
public Thread(Runnable target):
分配一个带有指定目标的新线程。
public Thread(String name):
分配一个带有名字的新线程。
public Thread(Runnable target, String name):
分配一个带有指定目标的新线程,并指定线程的名字。
3.3,Runnable
java.lang.Runnable:
Runnable 接口该由那些打算通过某一线程其实例的类来实现。类必须定义一个称为run的无参方法。
实现步骤:
1,创建一个Runnable接口的实现类。
2,在实现类中重写Runnable接口中的run方法,设置线程的任务。
3,创建一个Runnable实现类的对象。
4,创建Thread类对象,构造方法中传递Runnable接口的实现类对象。
5,调用Thread类中start方法,开启线程执行run方法。
public class ThreadInterface implements Runnable{
@Override
public void run() {
System.out.println("使用Runnable创建线程!");
}
}
// 在main方法中,创建实现类对象并开启线程
ThreadInterface thread = new ThreadInterface();
new Thread(thread,"runnable").start();
说到这里Thread类和Runnable接口都可以创建新的线程,那么它们之间又有什么区别呢?
使用Runnable接口的好处:
- 避免了单继承的局限性一个类只能继承一个父类,如果创建线程选择继承Thread类,那么就不能再继承别的父类。而采用实现Runnable接口,则还可以再继承,且再实现别的接口。
- 增强了程序的扩展性,降低了程序的耦合性(解耦)实现Runnable接口的方式,把设置线程任务和开启线程的任务进行了分离。
3.4,匿名内部类方式创建线程
作用:
1,简化代码
2,把子类继承父类,重写父类的方法,创建子类对象一步合成。
3,把实现类接口,重写接口中的方法,创建实现类对象一步合成。
格式:
new 父类/接口(){}
Thread
new Thread(){
@Override
public void run() {
super.run();
}
}.start();
使用lambda表达式写法(JDK8特性,后面会分享该特性):
new Thread(() -> System.out.println("Thread匿名内部类")).start();
Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名内部类");
}
};
// 开启线程
new Thread(runnable).start();
使用lambda表达式:
Runnable runnable = () -> System.out.println("Runnable匿名内部类");
// 开启线程的第一种方式
new Thread(runnable).start();
四,线程安全
线程安全通常有3种解决方式:
1,同步代码块
2,同步方法
3,锁机制(lock)
我们以卖车票为案例,用三种方式去解决车票的重复售卖,超卖情况。
4.1,同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象。
2.但是必须保证多个线程使用的锁对象是同一个。
3.锁对象作用: 把同步代码块锁住,只让一个线程在同步代码块中执行。
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//同步代码块
synchronized (obj){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
在main方法中调用线程,并模拟三个窗口同时售票。
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
4.2,同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
System.out.println("this:"+this);
//使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
}
/*
静态的同步方法
不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也是就是this
*/
public /*synchronized*/ void payTicket(){
synchronized (this){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
在main函数中调用该线程,其代码与上述一样。
4.3,同步锁(Lock)
java.util.concurrent.locks.Lock
接口
Lock 实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
使用步骤:
1.在成员位置创建一个ReentrantLock对象。
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁。
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 。
请看如下API说明:
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//1.在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
//先判断票是否存在
if(ticket>0){
try {
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}
五,线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程是需要时间的。
在JDK5之前,对于线程池的使用是需要程序员用集合来自己进行创建。在JDK5之后就不再需要手动去创建,JDK已经帮我们封装好了。
5.1,线程池概念
- 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
用一张简单的图来理解下线程池工作的原理。
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,可能最后死机)。
5.2,使用方式
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
- 创建线程池对象。
- 创建Runnable接口子类对象。
- 提交Runnable接口子类对象。
- 关闭线程池(一般不做,因为再次使用的时候线程池中就没有线程了)。
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程: " + Thread.currentThread().getName());
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
// 从线程池中获取线程对象
service.submit(r);
// 再获取多个线程
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。而且将使用完的线程又归还到了线程池中
}
}
六,sleep()与wait()
1,所属分类不同,sleep属于Thread类中,而wait属于Object类中。
2,锁控制不同,sleep不会释放锁,而wait会释放锁且不会影响其他线程进入同步代码块或同步方法中。也就是说sleep会占用资源,wait不会占用资源。
3,sleep可以在任意地方使用,而wait需在同步代码块或者同步方法中使用。
七,volatile与synchronized
1,volatile性能比synchronized要好,因为volatile是线程同步的轻量级实现,但是在jdk1.5之后性能有所提高了。
2,volatile只能修饰变量,synchronized可以修饰方法以及代码块。
3,多线程访问volatile不会发生阻塞,synchronized会出现阻塞。
4,volatile能保证数据的可见性,但不能保证原子性。synchronized可以保证原子性。
5,volatile解决变量在多线程之间的可见性。synchronized解决的是多线程之间访问资源的同步性。
6,volatile防止指令重排。
八,总结
似乎觉得本人的每一篇博客的篇幅都好长,可能是因为都是一些很基础的知识点吧,所以就会涉及到很多方方面面,写着写着就很多了。不过这样记下来时间久了还可以再回来看看,多少也算有点印象(哈哈)。
如果你阅读到此,很感谢您的耐心。以上总结的内容,如有不适之处,欢迎留言指正。
感谢阅读!
Java之多线程(一)的更多相关文章
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- java之多线程 二
线程的生命周期: 当线程被创建并被启动时,它既不是一启动就进入了执行状态,在线程的生命周期中,它要经过new(新建),就绪(Runnable),运行(Running),阻塞(Blocked),dead ...
- Java的多线程机制系列:(一)总述及基础概念
前言 这一系列多线程的文章,一方面是个人对Java现有的多线程机制的学习和记录,另一方面是希望能给不熟悉Java多线程机制.或有一定基础但理解还不够深的读者一个比较全面的介绍,旨在使读者对Java的多 ...
- Java Thread 多线程 介绍
1.线程概述 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程. 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2.线程 ...
- Java:多线程<一>
程序运行时,其实是CPU在执行程序的进程,为了提高工作效率一个进程可以有多个线程. Java的多线程: 其实我们之前就见过Java的线程,main就是Java的一个线程,还有另一个条线程总是和main ...
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java的多线程机制系列:(三)synchronized的同步原理
synchronized关键字是JDK5之实现锁(包括互斥性和可见性)的唯一途径(volatile关键字能保证可见性,但不能保证互斥性,详细参见后文关于vloatile的详述章节),其在字节码上编译为 ...
- Java基础——多线程
Java中多线程的应用是非常多的,我们在Java中又该如何去创建线程呢? http://www.jianshu.com/p/40d4c7aebd66 一.常用的有三种方法来创建多线程 新建一个类继承自 ...
- JAVA之多线程的创建
转载请注明源出处:http://www.cnblogs.com/lighten/p/5967853.html 1.概念 老调重弹,学习线程的时候总会牵扯到进程的概念,会对二者做一个区分.网上有较多的解 ...
- Java基础--多线程的方方面面
1,什么是线程?线程和进程的区别是什么? 2,什么是多线程?为什么设计多线程? 3,Java种多线程的实现方式是什么?有什么区别? 4,线程的状态控制有哪些方法? 5,线程安全.死锁和生产者--消费者 ...
随机推荐
- 一次简单的SQL手工注入
1. 首先要了解SQL注入的原理: SQL Injection:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令. 具体来说,它是利 ...
- Unity经典游戏编程之:球球大作战
版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...
- php sql 类似 mybatis 传参
PHP sql 处理上,没有类似于 java mybatis 的工具,导致进行一些sql 处理时,会有诸多不便, 楼主抽时间写了一个 php 类似 mybatis 的sql 工具,省去了拼装sql 的 ...
- kafka消息的处理机制(五)
这一篇我们不在是探讨kafka的使用,前面几篇基本讲解了工作中的使用方式,基本api的使用还需要更深入的去钻研,多使用才会有提高.今天主要是探讨一下kafka的消息复制以及消息处理机制. 1. bro ...
- 反向传播 Backpropagation
前向计算:没啥好说的,一层一层套着算就完事了 y = f( ... f( Wlayer2T f( Wlayer1Tx ) ) ) 反向求导:链式法则 单独看一个神经元的计算,z (就是logit)对 ...
- Spring项目集成ShiroFilter简单实现权限管理
Shiros是我们开发中常用的用来实现权限控制的一种工具包,它主要有认证.授权.加密.会话管理.与Web集成.缓存等功能.我是从事javaweb工作的,我就经常遇到需要实现权限控制的项目,之前我们都是 ...
- wwww
public class MainActivity extends AppCompatActivity implements XListView.IXListViewListener{ ; priva ...
- MySQL-EXPLAIN执行计划字段解释
做 MySQL 查询优化遇到明明建了索引查询仍然很慢,看这个 SQL 的执行计划,看它到底有没有用到索引,执行的具体情况.我们可以用 EXPLAIN 命令查看 SQL 的执行计划,SQL 优化的重要性 ...
- 多线程 共享资源 同步锁 java
Java多线程编程:Lock synchronized是java中的一个关键字,也就是说是Java语言内置的特性.那么为什么会出现Lock呢? 如果一个代码块被synchronized修饰了,当一 ...
- Postman系列二:Postman中get接口实战讲解(接口测试介绍,接口测试流程,头域操作)
一:接口测试介绍 接口测试:就是针对软件对外提供服务的接口输入输出进行测试,以及接口间相互逻辑的测试,验证接口功能和接口描述文档的一致性. 接口测试好处:接口测试通常能对系统测试的更为彻底,更高的保障 ...