Java多线程
概念:
1.程序:
静止的,是计算机指令的集合,它以文件的形式存储在磁盘上,只有当程序获得cpu资源运行起来,才称为进程,就好比我们下载一个app然后把它放在了本地的磁盘
2.进程:
由多个线程组成,相互协作完成指定的作业.运行起来的程序就是进程,cpu分配资源的单位.下载后我们使用这款app,点开它,获得了cpu资源,运行起来就是一个进程
3.线程:
线程又称为轻量级的进程,cpu调度资源的最小单位,就好比一个公司是一个程序,下边的每个部门就是一个进程,公司按部门分配资源,分配资源后,怎么用,谁用,所以部门中的每一个人就是一个线程,他们是调度使用资源的最小单位.
4.单线程:
如果一个进程只有一个线程,这样的程序称为单线程程序,一个部门就你一个人,没人和你抢资源,但是这样太浪费资源
5.多线程:
如果一个进程拥有不止一个线程,那么这个进程就是多线程的.优势:可以同时执行多个任务。提高运行的效率。
线程的基本组成部分:
1.cpu时间片,操作系统会为每个线程分配执行时间
2.运行数据:
堆空间:存储线程需要的对象,(多个线程可以共享堆中的对对对象)
栈空间:存储线程需要的局部变量(每个线程都有独立的栈,程序计数器)
3.线程的逻辑代码
线程的使用场景:
1.比如app开发中耗时的操作都不在UI主线程中做
2.实现响应更快的应用程序, 即主线程专门监听用户请求,子线程用来处理用户请求。以获得大的吞吐量。
感觉这种情况下,多线程的效率未必高。 这种情况下的多线程是为了不必等待, 可以并行处理多条数据。
比如JavaWeb的就是主线程专门监听用户的HTTP请求,然后启动子线程去处理用户的HTTP请求。
3.某种任务,虽然耗时,但是不耗CPU的操作时,开启多个线程,效率会有显著提高。
比如读取文件,然后处理。 磁盘IO是个很耗费时间,但是不耗CPU计算的工作。 所以可以一个线程读取数据,一个线程处理数据。肯定比一个线程读取数据,然后处理效率高。 因为两个线程的时候充分利用了CPU等待磁盘IO的空闲时间。
创建线程:
1.创建一个类继承Thread然后重写run()方法
线程A:
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 500; i++) {
System.out.println("线程A: "+ i);
}
}
}
线程B:
public class MyThread2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 500; i++) {
System.out.println("线程B: " + i);
}
}
}
测试:
public class Test {
@Test
public void testExetendThread() {
MyThread myThread = new MyThread();
myThread.start();
MyThread2 myThread2 = new MyThread2();
myThread2.start();
}
}
结果:
2.事项Runable接口,然后重写run方法
创建线程:
线程A
public class MyThread3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程A: " + i);
}
}
}
线程B
public class MyThread4 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程B: " + i);
}
}
}
测试
public class Test {
@Test
public void testImplRnable() {
Runnable runnable3 = new MyThread3();
Thread myThread3 = new Thread(runnable3);
myThread3.start();
Runnable runnable4 = new MyThread4();
Thread myThread4 = new Thread(runnable4);
myThread4.start();
}
}
结果
3.匿名内部类
两种写法:第一种
public class Test {
@Test
public void testRunableImpl() {
Runnable mr = ()-> {
for (int i = 0; i < 500; i++) {
System.out.println("线程A: " + i);
}
};
Thread t = new Thread(mr);
t.start();
}
}
或者这样写:
public class Test {
@Test
public void testAisThread() {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName()+": " + i);
}
}
},"线程A").start();
}
}
第二种:
public class TestMultiplyThread2 {
@Test
public void TestThread() {
Thread t1 = new Thread("线程A") {
@Override
public void run() {
System.out.println("我是一个子线程");
for(int i = 0; i < 500; i++) {
System.out.println(i);
}
}
};
t1.start();
System.out.println("hello world");
}
}
结果
4.Lamda表达式:
public class Test {
@Test
public void testAisThread() {
new Thread(() -> {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}, "线程A").start();
}
}
需要注意的是使用lamda表达式必须是函数式接口(即接口中只有一个方法).
5.Calllable接口和Future
这个需要配合线程池来使用,这里举一个简单的例子吧
public class Test {
public void testFutureThread(String[] args) {
//这里面的核心线程数和最大线程数是一样的10
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "学会了吗?";
}
});
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
newFixedThreadPool底层:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
结果:
多线程的常用方法
currentThread() 获取当前线程对象。 类方法
setName(String name) 设置线程的名字。
getName() 获取线程的名字。
setPriority(int priority) 设置线程的优先级。 优先级的取值范围[1,10],默认是5
getPriority() 获取线程的优先级。
getState() 获取线程的状态
join() 执行该线程,会阻塞当前线程。
sleep(long millis) 休眠指定时间(单位毫秒),会阻塞当前线程。类方法
start() 启动线程
yield() 暂定该线程的执行,交出CPU的使用权。
线程的同步
线程的同步和线程的并发
1:线程的同步不是客观上的线程从宏观上的同步运行,微观上的串行的过程这一现象,而恰恰相反,线程的同步是指的线程的不同时执行,而是串行,以保证线程对临界资源的访问.
2:线程的并发,指的是线程从宏观上一起运行,微观上还是串行的,因为那个线程获得cpu的时间片,谁就运行.
卖票的实例:
不同步发生的数据混乱现像,即多个线程对同一临界资源的访问打破了原理操作的情况下:
简单的java代码:
public class Tickets {
private int tickets = 100;
public int getTickets() {
return tickets;
}
public void sellTickets() {
tickets--;
System.out.println(Thread.currentThread().getName() + " : 买了一张票,当前剩余: " + tickets+ "张票");
}
}
测试代码
public class TMyTest {
@Test
public void testStickets() {
Tickets t1 = new Tickets();
new Thread(() -> {
while (true) {
if (t1.getTickets() > 1) {
t1.sellTickets();
}else {
return;
}
}
}, "线程A").start();
new Thread(() -> {
while (true) {
if (t1.getTickets() > 1) {
t1.sellTickets();
}else {
return;
}
}
}, "线程B").start();
}
}
结果出现不符合现实的现像:即500张票卖出了1000张,出现数据的混乱,如下:
线程的同步操作
同步方法:
//在返回值前面加synchronized,语法是
public 方法修饰符 synchronized 返回值类型 方法名(参数){}
public class Tickets {
private int tickets = 100;
public int getTickets() {
return tickets;
}
public synchronized void sellTickets() {
tickets--;
System.out.println(Thread.currentThread().getName() + " : 买了一张票,当前剩余: " + tickets+ "张票");
}
}
测试代码
public class TMyTest {
@Test
public void testStickets() {
Tickets t1 = new Tickets();
new Thread(() -> {
while (true) {
if (t1.getTickets() > 1) {
t1.sellTickets();
}else {
return;
}
}
}, "线程A").start();
new Thread(() -> {
while (true) {
if (t1.getTickets() > 1) {
t1.sellTickets();
}else {
return;
}
}
}, "线程B").start();
}
}
结果:
同步代码块
public class Tickets {
private int tickets = 100;
public int getTickets() {
return tickets;
}
public void sellTickets() {
//同步代码块:
synchronized(this) {
tickets--;
System.out.println(Thread.currentThread().getName() + " : 买了一张票,当前剩 余: " + tickets+ "张票");
}
}
}
重入锁(ReentrantLook):
public class Tickets {
private int tickets = 100;
public int getTickets() {
return tickets;
}
public void sellTickets() {
//在juc包下,重入锁
Lock lock = new ReentrantLock();
lock.lock();
tickets--;
System.out.println(Thread.currentThread().getName() + " : 买了一张票,当前剩余: " + tickets + "张票");
lock.unlock();
}
}
线程间的通信
什么是线程通信?
不同线程之间可以相互的发信号。这就是线程通信。之所以需要进行线程通信,是因为有些时候,一个线程的执行需要依赖另外一个线程的执行结果。在结果到来之前,让线程等待(wait),有了结果只之后再进行后续的操作。对于另外一个线程而言,计算完结果,通知(notify)一下处于等待状态的线程.
线程通信借助的是Object类的wait,notify,nitifyall方法。
wait作用是让当前线程阻塞,阻塞多久,取决于有没有其他线程唤醒它。
notify作用是唤醒处于wait状态的线程。必须是同一个监视器下的线程。
notifyall作用是唤醒所有处于wait状态的线程。必须是同一个监视器下的线程。
一般情况下,多线程里会出现线程同步的问题,我们不但要进行线程通信,还要解决线程同步的问题。
生产者-消费者模式
这是一个比较经典的多线程场景。有商品的时候,消费者才可以消费,没有商品的时候,消费者等待。商品库存充足的时候,生产者等待,库存不满的时候,生产者生产商品。
public class Saler {//售货员类
private int productCount = 10; //商品数量
public synchronized void stockGoods() {
if(productCount < 2000) {
productCount++;
System.out.println(Thread.currentThread().getName() + "生产了1件商品,库存是:" + productCount);
this.notifyAll();
}else {
System.out.println("库存满了");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sellGoods() {
if(productCount > 0) {
productCount--;
System.out.println(Thread.currentThread().getName() + "购买了1件商品,库存剩余:" + productCount);
this.notifyAll();
}else {
System.out.println("库存不足");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Productor implements Runnable{//生产者类
private Saler s;
public Productor(Saler s) {
super();
this.s = s;
}
@Override
public void run() {
while(true) {
s.stockGoods();
}
}
}
public class Customer implements Runnable{//消费者类
private Saler s;
public Customer(Saler s) {
super();
this.s = s;
}
@Override
public void run() {
while(true) {
s.sellGoods();
}
}
}
public class TestTread {
public static void main(String[] args) {
//生产者-消费者模式。模拟生产和消费过程
Saler s = new Saler();
Customer c = new Customer(s);
Productor p = new Productor(s);
Thread t1 = new Thread(c, "客户1");
t1.start();
Thread t2 = new Thread(p,"厂家");
t2.start();
Customer c2 = new Customer(s);
Thread t3 = new Thread(c2, "客户2");
t3.start();
}
}
注意:调用notify()方法必须在对该对象加锁的同步代码块中
线程的生命周期
API中的java.lang包下的Thread.State枚举类下定义的线程状态如下:
NEW:新建状态,指的是线程已经创建,但是尚未start()。
RUNNABLE:可运行状态(已经调用了start方法),已经准备就绪,一旦抢到CPU就立即执行。
BLOCKED:阻塞状态,处于阻塞状态的线程正在等待进入Synchronized块(或方法)。
WAITING:等待状态,等待其他线程执行任务。直到其他线程任务结束或者收到notify信号。
TIMED-WAITING:等待状态,限定时间的等待状态。
TERMINATED:终止状态。线程要运行的任务已经结束。
线程池
线程池的相关类树
ThreadPoolExecutor底层构造方法:
public ThreadPoolExecutor(
//核心线程数
int corePoolSize,
//最大线程数
int maximumPoolSize,
//除了核心线程外的线程的最大存活时间
long keepAliveTime,
//时间的单位是TimeUnit是一个enmu(enmunation:枚举)类,
TimeUnit unit,
//任务队列
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
手动创建一个线性池
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 5, TimeUnit.MINUTES, new ArrayBlockingQueue(3));
pool.execute(new Runnable() {//实现Executor接口中得execute方法
@Override
public void run() {
System.out.println("你好世界!");
}
});
pool.submit(new Runnable() {//实现接口Executor子接口ExecutorService中的submit方法
@Override
public void run() {
System.out.println("你是小猪!");
}
});
}
}
对ExecutorService,Executor和Executors工具类创建线性池与Spring使用一下
applicationContext.xml中的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<context:component-scan base-package="com.lanou.dao" />
<context:property-placeholder location="jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.user}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" c:ds-ref="dataSource" />
</beans>
jdbc.properties文件的配置:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/day05?characterEncoding=utf-8&useSSL=false
jdbc.user=root
jdbc.password=123456
bean中的实体类:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Girl {
private int gId;
private String gName;
private int kId;
}
测试方法:与Callable和Future接口的使用.
@Test
public void testExecutors(){
ExecutorService es = Executors.newFixedThreadPool(10);
Future<List<Girl>> future = es.submit(new Callable<List<Girl>>() {
@Override
public List<Girl> call() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
QueryRunner queryRunner = applicationContext.getBean(QueryRunner.class);
String sql = "select g_id gId,g_name gName,k_id kId from girl";
List<Girl> girlList = queryRunner.query(sql, new BeanListHandler<Girl>(Girl.class));
return girlList;
}
});
try {
for(Girl girl : future.get()){
System.out.println(girl.toString());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
测试结果: