Java基础--多线程的方方面面
1,什么是线程?线程和进程的区别是什么?
线程是程序执行的最小单元。
区别: 进程是操作系统进行资源处理和分配的最小单位,而一个进程可以包含多个线程,并共享进程的资源。
2,什么是多线程?为什么设计多线程?
介绍之前,我们需要理解并行和并发的定义:
并行:同一个时刻有多个线程进行。
并发:同一个时间段内有多个线程进行。
多线程指的是一个进程可以包含多个并发的线程(同一个时刻只有一个线程运行)。例如酷狗,我们可以一边听歌一边搜索自己喜欢的歌曲。多线程的存在能够让进程及时处理我们多项的请求,提高应用程序的利用率。
多线程编程需要了解到多线程运行面临的问题。
- 既然一个进程的多个线程共享进程的资源,怎样保证有多个线程访问同一资源时单个线程的访问不受其它线程的干扰。这是线程安全问题。
- 多线程怎么控制线程的执行顺序,这是线程调度问题。ps:Java对多线程调度执行抢占式,每个线程有个优先级属性(1--10,10最高),优先级高的有限执行。
3,Java种多线程的实现方式是什么?有什么区别?
Java实现多线程有两个方式。
- 继承Thread类,重写run()方法,代码如下:
线程类MyThread:
public class MyThread extends Thread{
@Override
public void run() {
for(int x=0;x<200;x++){
System.out.println(x);
}
}
}
主类Demo:
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();//新建线程类对象
MyThread mt1 = new MyThread();
mt.start();//调用start()方法
mt1.start();
}
}
ps,为什么不直接调用线程类的run()方法,而调用start()方法?
run()方法只是封装了多线程执行的操作,只是一个普通方法。
start()方法是启动线程执行的方法,由JVM自动调用run()方法。
- 实现Runnable 接口,重写run()方法,重点是主类调用的时候不同。
步骤:1,编写实现Runnable 接口的类,重写run()方法
public class ThreadRunnable implements Runnable { public void run(){}}
2,在主类中新建线程类对象,obj
ThreadRunnable tr = new ThreadRunnable();
3,新建Thread类t,将obj作为t的构造参数
Thread t = new Thread(tr);
4,调用t的start()方法。
- 继承类Thread和实现Runnable接口对比
由于Java只允许单类继承,故多选用实现Runnable接口的方法创建多线程,事实上Thread类也是接口Runnable的实现类。
public class Thread extends Object implements Runnable
4,线程的状态控制有哪些方法?
线程状态控制常用到的方法如下:
- 线程睡眠sleep(long millis)
t.sleep(1000);让线程t睡眠1000毫秒,即1秒。
- 线程加入join()
A.start();
A.join();//try catch
B.start();
C.start();
A执行完之后B和C才可以执行
- 线程礼让static void yield()
A.yield();A暂停一下,时间不确定,让同等级的线程优先运行。
- 线程中断interrupt()
A.interrupt();把线程的状态中止,并抛出 InterruptedException 。跳出阻塞的部分可以继续执行接下来的代码。
interrupt()只是改变中断状态而已. interrupt()不会中断一个正在运行的线程。这一方法实际上完成的是,给受阻塞的线程抛出一个中断信号,这样受阻线程就得以退出阻塞的状态。更确切 的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞, 它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到InterruptedException异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。
ps:对于一个正在运行的线程如果想要其结束运行,可以使用标志位,让线程跳出从而结束运行,示例如下:
public class ThreadFlag extends Thread
{
public volatile boolean exit = false; public void run()
{
while (!exit);
}
}
- 线程等待唤醒wait() notify()。这两个方法并不是线程类的方法,而是锁的方法,在接下一节介绍。
5,线程安全、死锁和生产者--消费者
我们来看下面的一段代码:
public class ThreadRunnable implements Runnable {
private static int D = 100;//D是静态变量由多个线程共享。public void run() { while(true){if(D>0){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+(D--));
} }
}//run
}
由于D是静态变量,它由ThreadRunnable的所有对象访问,每个对象对它进行输出,并减1的操作,直到D为0(可将D假想成某直达列车的票,每 个ThreadRunnable对象是一个售票窗口)假设有三个窗口如下:
public class ThreadRunnableDemo {
public static void main(String[] args) {
ThreadRunnable tr = new ThreadRunnable();//创建接口对象
Thread t1 = new Thread(tr,"窗口1");//创建Thread类,将上述对象作为构造参数
Thread t2 = new Thread(tr,"窗口2");//创建Thread类,将上述对象作为构造参数
Thread t3 = new Thread(tr,"窗口3");//创建Thread类,将上述对象作为构造参数
t1.start();//启动start方法
t2.start();
t3.start(); }
}
输出:
....
窗口1---97
窗口2---97
....
....
窗口3---2
窗口1---1
窗口2---0
窗口3----1
出同号票的原因分析:
出现0号和负号票的原因分析:
这就产生了线程不安全的问题,产生线程不安全的场景:
多个线程访问同一资源,并对资源进行多条语句操作就有可能引发线程不安全。
概括:多个线程;同一资源;不是原子操作
前两个条件我们无法改变,我们有的解决思路就是将线程对资源操作语句封装成原子操作(不会被打断)。将操作封装成原子操作。
Java使用synchronized关键字。
使用规范:
- 对共享代码块进行锁:synchronized(锁对象){共享代码块} 锁对象可以是任意的
public void run() {
while(true){
synchronized(new Object()){
if(D>0){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+(D--));
}
}
}
}//run()
- 对方法进行锁:private synchronized void function_name(){} 此时锁对象是this
- 当方法是静态时private static synchronized void function_name(){},锁对象是线程类字节码文件对象。
下面考虑更复杂的情况------死锁
死锁顾名思义就是加的锁打不开,一般发生在当两个线程互相拿着对方的锁即锁嵌套,造成两个线程一直处于等待中。死锁示例:
经典的生产者和消费者问题
问题描述:生产者producer生产资源,消费者customer消耗资源呢,我们设计的程序最低保证消费者在消耗资源时必须保证有资源。
设计思想:消费者和生产者共用一个锁,消费者一直消费资源,直到剩余资源数小于规定(0或者业务目标),消费者线程进入等待,直到生产者生产资源后将自己唤醒。
实现:
Producer类
package producer; import java.util.ArrayList; public class Producer implements Runnable { private ArrayList<String> al;
public Producer(ArrayList<String> al){
this.al = al;
} @Override
public void run() {
while(true){
synchronized (al) {
while(al.size()>0){
try {
al.wait();//只要有资源,生产线程就wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int i=0;i<10;i++){
al.add(""+i);
}
al.notify();//生产完成,唤醒等待线程即消费者线程
}
}
}
}
Customer类
package producer; import java.util.ArrayList; public class Customer implements Runnable {
private ArrayList<String> al;
public Customer(ArrayList<String> al){
this.al = al;
}
@Override
public void run() {
while(true){
synchronized (al) {
while(al.size()<1){
try {
al.wait();//如果没资源,消费者线程就wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while(al.size()>0){
System.out.println("消费者:"+al.size());
al.remove(0); }
al.notify();//消费没了,唤醒等待线程即生产者线程
}
}
}
}
调用类Demo
package producer; import java.util.ArrayList; public class Demo {
private static ArrayList<String> al= new ArrayList<String>();
public static void main(String[] args) { Producer p = new Producer(al);
Customer c = new Customer(al); Thread producer = new Thread(p);
Thread customer = new Thread(c); producer.start();
customer.start(); }
}
6,线程的优化有哪些方法?
- 线程池
实际业务场景中线程的寿命都很短暂,例如对于网站访问,每个用户请求是一个线程,如果来一个用户,进行一套线程的创建、就绪等动作会严重影响
服务器的响应效率,鉴于此,Java中有了线程池的解决办法,它的思想是程序初始运行时在一个容器内新建固定数量的线程,当用到时从容器内取出一个线程,
线程执行完之后再放回到容器内,实质是以空间换时间,这个容器在Java中就被称为线程池。
Java实现线程池
Executors类工厂类
方法:
public static ExecutorService new FixedThreadPool(int nThreads);//该方法返回一个含有n个线程的线程池接口
线程池接口:ExecutorService
方法:
submit(Runnable task);//将一个线程类加入到线程池
结束:shutdown()
ps:福利:
Java基础--多线程的方方面面的更多相关文章
- Java基础-多线程-③线程同步之synchronized
使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...
- Java基础-多线程-②多线程安全问题
什么是线程的安全问题? 上一篇 Java基础-多线程-①线程的创建和启动 我们说使用实现Runnable接口的方式来创建线程,可以实现多个线程共享资源: class Dog implements Ru ...
- java基础-多线程应用案例展示
java基础-多线程应用案例展示 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候 ...
- java基础-多线程二
java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...
- 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!
前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...
- java基础多线程之共享数据
java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保 ...
- Java基础——多线程
Java中多线程的应用是非常多的,我们在Java中又该如何去创建线程呢? http://www.jianshu.com/p/40d4c7aebd66 一.常用的有三种方法来创建多线程 新建一个类继承自 ...
- AJPFX: Java基础多线程(一)
多线程是Java学习的非常重要的方面,是每个Java程序员必须掌握的基本技能.本文只是多线程细节.本质的总结,并无代码例子入门,不适合初学者理解.初学者学习多线程,建议一边看书.看博文,以便写代码尝试 ...
- 十三、Java基础---------多线程总结
多线程概述 理解多线程首先应明确线程,要了解线程就必须了解什么是进程. 1.进程 是一个正在执行的程序. 每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 2.线程 就是进程 ...
随机推荐
- npm check failed 解决办法
npm ERR! shasum check failed for C:\Users\MM\AppData\Local\Temp\npm-10900-415697c8\registry.npmjs.or ...
- 分页SQL模板
select * from ( select rownum as rn ,a.* from ( select * from page a where object_id >1000 and ow ...
- index unique scan
INDEX UNIQUE SCAN 索引唯一扫描.单块读 只可能发生在unique index/primary key 等值查找 等待事件:db file s ...
- 「Poetize9」升降梯口
3056: 升降梯口 Time Limit: 2 Sec Memory Limit: 128 MBSubmit: 43 Solved: 42[Submit][Status] Description ...
- 实战weblogic集群之应用部署
一.创建应用发布目录,上传应用包. 1.在10.70.52.11-14的/app/sinova目录下建立applications目录(名称可以自定义),作为我们应用的发布目录. $ mkdir /ap ...
- ToArray()和IEnumerable<T>,List<T>
一:ToArray() 在程序中,我们往往习惯使用List<>这种集合类,但是程序中却要求需要传递一个数组,List<>已经为我们提供了toArray()方法 二:IEnume ...
- kafka-简单事例
开始创建项目,这里所用的工程结构是maven. 在pox.xml中添加kafka的依赖包,如下所示: <dependency> <groupId>org.apache.kafk ...
- DNA Sequence - POJ 2778(AC自动机+矩阵乘法)
题目大意:DNA序列是有 ATGC 组成的,现在知道一些动物的遗传片段有害的,那么如果给出这些有害的片段,能否求出来所有长度为 N 的基因中有多少是不包含这些有害片段的. 分析:也是断断续续做了一 ...
- poj1038
题目大意:网络导航? 标准的web浏览器包含向前和向后浏览最近的页面的特性,有一个方法来实现这些用两个栈来跟踪页面达到向前和向后的移动,在这个问题里面,你被要求实现这些. 以下命令需要支持: BACK ...
- select.poll,epoll的区别与应用
先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...