多线程小结
线程与进程
**进程:**
是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
**线程:**
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
用户线程: 当一个进程不包含任何一个存活的用户线程时,进程结束。
守护线程: 守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
同步与异步
**同步:**
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
**异步:**
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以
继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。
对于异步调用,调用的返回并不受调用者控制。
并发与并行
**并发:**
指两个或多个事件在**同一个时间段内**发生。
**并行:**
指两个或多个事件在**同一时刻**发生(同时发生)。
线程的三种实现
继承Thread类创建线程
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
public class MyThread extends Thread{
@Override
//run方法就是线程要执行的任务方法
public void run() {
//这里的代码 就是一条新的执行路径
//这个执行路径的处罚方式,不是调用run方法,而是通过thread对象的start()来启动任务
for(int i =0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
在main中
public class Demo1{
public static void main(String[] args){
MyThread m = new MyThread();
m.start(); //利用start开始
for(int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
运行结果
实现Runnable接口创建线程
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法: run( )
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("床前明月光"+i);
}
}
在main中
public class demo2 {
public static void main(String[] args){
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
for(int i=0;i<10;i++){
System.out.println("疑是地上霜"+i);
}
}
}
运行结果
也有一个很简便的实现方式,利用匿名内部类的方法
public class demo2 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("一二三四五"+i);
}
}
}.start();
for(int i=0;i<10;i++) {
System.out.println("上山打老虎" + i);
}
}
}
运行结果
PS : 也可以采用Lambda表达式实现。
Runnable的好处
/**
* 多线程技术
* 实现Runnable 与继承Thread相比有如下优势:
* 1. 通过创建任务,给线程分派的方式来实现多线程,更适合多个线程同时执行相同任务的情况。
* 2。 可以避免单继承带来的局限性
* 3. 任务与线程本身是分离的,提高程序的健壮性
* 4. 后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
*
* @param args
*/
使用Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
static public class CallableThreadTest implements Callable<Integer>{
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
main
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Call {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 10;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==5)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
}
运行结果
设置和获得线程名称
Thread.currentThread().getName()来获取线程名称,可以用setName()设置线程名称。
线程的运行状态
线程状态。 线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
线程安全与不安全
线程不安全的原因在于多个线程同时执行争抢对一个数据进行操作。例如:
public class demo3 {
public static void main(String[] args) {
//线程安全问题 线程不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
public void run(){
while(count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}
}
}
}
运行结果
线程不安全的解决办法
同步代码块
Java中synchronized用于解决同步问题,当有多条线程同时访问共享数据时,如果不进行同步,就会发生错误,Java提供的解决方案是:只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行可以。解决这个问题。这里在用synchronized时会有两种方式,一种是同步方法,即用synchronized来修饰方法,另一种是提供的同步代码块。
格式: synchronized(锁对象){}
public class demo3yi {
/**
* 线程同步: synchronized
* */
public static void main(String[] args) {
/**
* 线程不安全
* 解决方案1. 同步代码块
* 格式: synchronized(锁对象){
*
* }
*/
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
private Object o = new Object();
public void run(){ //A B C
//Object o = new Object();
while (true) {
synchronized (o){
if (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票还有:" + count);
}else{
break;
}
}
}
}
}
}
运行结果
同步方法
用synchronized修饰方法
public synchronized boolean sale(){
//this 非静态
//Ticket.class 静态
if (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票还有:" + count);
return true;
}else{
return false;
}
}
public class demo3er {
/**
* 线程同步:synchronized
* */
public static void main(String[] args) {
//线程不安全
//解决方案2 .同步方法
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
public void run(){
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
//this 非静态
//Ticket.class 静态
if (count > 0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票还有:" + count);
return true;
}else{
return false;
}
}
}
}
运行结果
显式锁Lock
显式锁更能体现锁的概念,参数为true表示公平锁。
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁 :参数为true为公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while(true){
l.lock();
if(count>0){
System.out.println("正在准备开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票还有"+count);
}else{
break;
}
l.unlock();
}
}
}
完整卖票代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class demo3san {
public static void main(String[] args) {
/**
* 线程不安全
* 解决方案3. 显式锁 Lock 子类 ReentrantLock
* 公平锁 非公平锁
* */
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁 :参数为true为公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while(true){
l.lock();
if(count>0){
System.out.println("正在准备开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票还有"+count);
}else{
break;
}
l.unlock();
}
}
}
}
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
1) 降低资源消耗。
2) 提高响应速度。
3) 提高线程的可管理性。
缓存线程池(CachedThreadPool)
/**
* 缓存线程池
*(长度无限制)
*任务加入后的执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池,然后使用
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo7 {
//线程池
//创建线程
//创建任务
//执行任务
//关闭线程
/**
* 缓存线程池
*(长度无限制)
*任务加入后的执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池,然后使用
*/
public static void main(String[] args) {
//指挥线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午1");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午2");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午3");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
}); //线程池的确实现了重复使用
}
}
前三个线程各自创建线程并放进线程池,执行完后,第四个线程进来发现有空闲线程,就不再创建新线程。
运行结果
定长线程池(FixedThreadPool)
程序中设定线程池的固定长度(newFixedThreadPool)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo8 {
/**
* 定长线程池。
* (长度是指定的数值)
* 任务加入后的执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
* */
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
运行结果
单线程线程池(SingleThreadExecutor)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo9 {
/**
* 单线程线程池。
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 3. 不空闲,则等待 池中的单个线程空闲后 使用
* */
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午1");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午2");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午3");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午4");
}
});
}
}
只有一个线程,运行结果
定时线程池(ScheduledThreadPool )
执行定时/周期性任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class demo10 {
/**
* 周期任务 定长线程池。
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程 且线程池未满的情况下,则创建线程 并放入线程池,然后使用
* 4. 不存在空闲线程 且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行,当某个实际触发时,自动执行某任务
* */
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
*1. 定时执行1次
* 参数1. 定时执行的任务
* 参数2. 时常数字
* 参数3. 市场数字的时间单位,TimeUnit的常量指定
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
},5, TimeUnit.SECONDS);*/
/**
* 周期执行任务
* 参数1. 任务
* 参数2. 延迟时常数字(第一次执行在什么时间以后)
* 参数3. 周期时常数字(每隔多久执行一次)
* 参数4. 市场数字的单位
* */
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("汗滴禾下土");
}
},5,1,TimeUnit.SECONDS);
}
}
第一个“汗滴禾下土”在程序运行5秒后出现,
之后每隔1秒出现一次
运行结果
由于线程池接触的少,只会创建和使用,了解各个参数的作用,所以对于其深层知识了解的不多。
找到一篇有关线程池的文章,原作者同意转载学习Java多线程:彻底搞懂线程池