Java多线程
进程与线程
概念
进程:进程是程序的一次执行过程,是程序执行过程中分配和管理资源的基本单位,每个进程都有自己的内存空间。
线程:线程是CPU资源调度和分派的基本单位,它可以和同一进程下的其他线程共享进程资源。
联系
线程是进程的一部分,一个进程可以有多个线程,但一个线程只能存在于一个进程中。
区别
根本区别:进程是操作系统资源调度的基本单位,线程是执行调度的基本单位。
开销方面:进程的切换开销大,线程的切换开销小。
共享方面:进程拥有自己独立的地址空间和资源,线程是共享所属进程的资源。
Java多线程的实现方式
继承Thread类
package com.Thread.create;
//继承Thread类,重写run()方法
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadDemo_one {
public static void main(String[] args) {
//创建线程,并执行
Thread my_thread = new MyThread();
my_thread.start();
//主线程的执行
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
实现Runnable接口
package com.Thread.create;
//实现Runnable接口,重写run()方法
class My_Runnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadDemo_two {
public static void main(String[] args) {
//创建线程,并执行
//1、创建Runnable对象
My_Runnable me = new My_Runnable();
//2、把Runnable对象传递到Thread类中
Thread t = new Thread(me);
//3、开启线程
t.start();
//主线程的执行
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
实现Callable接口
package com.Thread.create;
//实现Callable接口,重写call()方法
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class My_Callable implements Callable{
@Override
public Object call() throws Exception {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}
public class ThreadDemo_three {
public static void main(String[] args) {
//创建线程,并执行
//创建Callable对象
Callable my_callable = new My_Callable();
//使用FutureTask类包装Callable对象
FutureTask my_task = new FutureTask(my_callable);
//将FutureTask传递给Thread类
Thread t = new Thread(my_task);
t.start();
//主线程的执行
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
使用线程池
package com.Thread.create;
//使用线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class My_runnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadDemp_four {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建Runnable对象
Runnable me = new My_runnable();
//从线程池中获取线程对象,把Runnable对象传递过去
executorService.submit(me);
//关闭线程池。生产环境是不关的
executorService.shutdown();
//主线程的执行
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
四种创建方式的优缺点比较
实现Runnable接口比继承Thread类所具有的优势:
1、避免了Java单继承的局限性
2、适合多个相同的程序代码去共享同一个资源
3、线程池中只能放入实现Runnable或Callable类线程,不能放入继承Thread类
Callable与Runnable相比:
最主要区别是 call() 方法有返回值,而 run() 方法没有
使用线程池的好处:
1、提高响应速度,减少创建线程的时间
2、降低了资源消耗,重复利用线程池中的线程
3、便于线程的管理,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下
线程的同步(线程的安全)
线程安全
所谓的线程安全就是有多个线程同时运行,并且这些线程可能同时运行这一段代码。程序每次运行结果和单线程运行的结果是一样,并且变量的值和预期的一样。
多线程访问了共享资源才会出现线程安全问题
线程同步
使用同步机制(synchronized)可以解决多线程访问同一资源出现的线程安全问题
下面买票的类就是线程不安全的类
package com.Thread.synchronize;
public class Sell_Ticket implements Runnable{
int ticket = 100;
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
同步代码块
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
1、可以是任意对象
2、多个线程对象,需要使用同一把锁
package com.Thread.synchronize;
//使用同步代码块实现线程安全问题
/*
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
1、可以是任意对象
2、多个线程对象,需要使用同一把锁
*/
public class Sell_Ticket_Synchronized_one implements Runnable{
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
同步方法
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
package com.Thread.synchronize;
//使用同步方法实现线程安全问题
/*
同步方法的锁对象就是它自己this
*/
public class Sell_Ticket_Synchronized_two implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
sell_ticket();
}
}
private synchronized void sell_ticket(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
package com.Thread.synchronize;
//使用锁对象实现线程安全问题
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Sell_Ticket_Synchronized_three implements Runnable{
private int ticket = 100;
//创建锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
lock.lock();
try {
Thread.sleep(10);
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
}
线程的通信(生产者与消费者)
线程通信
概念
多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
为什么要处理线程通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
简单总结就是:我们希望线程有规律地执行
如何通信
使用等待唤醒机制
等待唤醒机制
什么是等待唤醒
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒的方法
1、wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2、notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3、notifyAll:则释放所通知对象的 wait set 上的全部线程。
调用wait和notify方法需要注意的细节
1、wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2、wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
生产者消费者模式
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
代码实现
包子类
package com.Thread.communication;
//包子类,也叫资源类
public class BaoZi {
//包子皮
String pi;
//包子馅
String xian;
//包子的状态,表示资源是否存在
boolean flag = false;
}
生产包子的类
package com.Thread.communication;
//包子铺,表示生产者,生产包子
public class BaoZi_Producer extends Thread{
private BaoZi baozi;
public BaoZi_Producer(BaoZi baozi){
this.baozi = baozi;
}
@Override
public void run() {
int count = 0;
//一直生产,用while(true)循环
while (true){
synchronized (baozi){
if (baozi.flag == true){
try {
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//增加趣味性
if (count%2==0){
baozi.pi = "薄皮";
baozi.xian = "牛肉馅";
System.out.println("包子铺生产包子-->"+baozi.pi+baozi.xian+"包子");
}else{
baozi.pi = "脆皮";
baozi.xian = "猪肉馅";
System.out.println("包子铺生产包子-->"+baozi.pi+baozi.xian+"包子");
}
//做个包子要5秒钟
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
baozi.flag = true;
baozi.notify();
}
}
}
}
吃包子的类
package com.Thread.communication;
//包子消费者
public class BaoZi_Comsumer extends Thread{
private BaoZi baozi;
public BaoZi_Comsumer(BaoZi baozi){
this.baozi = baozi;
}
@Override
public void run() {
while (true){
synchronized (baozi){
if (baozi.flag==false){
try {
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃包子-->"+baozi.pi+baozi.xian+"包子");
//吃包子3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
baozi.flag = false;
baozi.notify();
System.out.println("----------------------------------");
}
}
}
}
测试类
package com.Thread.communication;
public class Test {
public static void main(String[] args) {
BaoZi baozi = new BaoZi();
BaoZi_Producer bp = new BaoZi_Producer(baozi);
BaoZi_Comsumer bc = new BaoZi_Comsumer(baozi);
bp.start();
bc.start();
}
}