Java 多线程详解
基本概念:程序 - 进程 - 线程
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的
何时需要多线程:
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时。
线程的创建和启动
一.线程的创建(此程序非多线程)
public class Sample {
public void method1(String str){
System.out.println(str);
}
public void method2(String str){
method1(str);
}
public static void main(String[] args) {
Sample s = new Sample();
s.method2("hello!");
}
}
二.单线程制造商品
1.定义商品(Goods)类
package com.gec.singleThread;
public class Goods {
private int id;
private String name;
private int num;
private double price;
public Goods() {
}
public Goods(String name, int num, double price) {
this.name = name;
this.num = num;
this.price = price;
}
public Goods(int id, String name, int num, double price) {
this.id = id;
this.name = name;
this.num = num;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
2.测试类
import java.util.ArrayList;
import java.util.List;
public class Factory {
//主线程
public static void main(String[] args) throws InterruptedException {
try {
singleThreadCreateGoods();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void singleThreadCreateGoods() throws InterruptedException {
List<Goods> goodses = new ArrayList <>();
//currentThread 当前线程 getName 获取线程名称
String name = Thread.currentThread().getName();
//开始时间,获取制作商品的时间消耗
long start = System.currentTimeMillis();
//生产商品
int i = 1;
while (i<=100){
//采用线程中的sleep方法,让主线程睡眠1秒,相当于造商品
Thread.sleep(1000);
goodses.add(new Goods(i,"辣条",10,5));
System.out.println(name+"生产第"+i+"个商品");
i++;
}
long end = System.currentTimeMillis();
System.out.println(name+"生产100个商品需要消耗"+(end-start)+"毫秒");
}
}
3.运行结果
main生产第90个商品
main生产第91个商品
main生产第92个商品
main生产第93个商品
main生产第94个商品
main生产第95个商品
main生产第96个商品
main生产第97个商品
main生产第98个商品
main生产第99个商品
main生产第100个商品
main生产100个商品需要消耗100054毫秒
三.多线程的创建和启动
1.通过继承Thread类,并重写run方法来实现多线程(采用上述Gooods类)
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。
Thread类的特性
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
- 通过该Thread对象的start()方法来调用这个线程
继承Thread的步骤:
1.创建一个类继承Thread
2.重写run方法,是线程体
3.创建测试类
4.创建线程对象
5.调用start方法,启动线程
1.生产线
import java.util.List;
/**
* 生产线
* 实现线程方式1:继承Thread类
* 实现Thread类中的run方法(线程需要执行的内容)
*/
public class CreateLine extends Thread{
//商品仓库
public List<Goods> goodses;
//带参构造方法
public CreateLine(List <Goods> goodses) {
this.goodses = goodses;
}
@Override
public void run() {
Goods goods = null;
try {
String name = Thread.currentThread().getName();
//开始时间,获取制作商品的时间消耗
long start = System.currentTimeMillis();
//生产商品
int i = 1;
while (goodses.size()<=100){
//采用线程中的sleep方法,让主线程睡眠1秒,相当于造商品
Thread.sleep(1000);
goods = new Goods(i,"辣条"+i,10,5);
goodses.add(goods);
System.out.println(name+"生产第"+i+"个商品");
i++;
}
long end = System.currentTimeMillis();
System.out.println(name+"生产"+i+"商品需要消耗"+(end-start)+"毫秒");
System.out.println(name + "生产"+ goods.getName()+"商品总数量:"+goodses.size());
}catch (Exception e){
e.printStackTrace();
}
}
}
2.测试类
import java.util.ArrayList;
import java.util.List;
public class Factory {
//主线程
public static void main(String[] args) throws InterruptedException {
List<Goods> list = new ArrayList <>();
//创建线程
for (int i = 0; i < 4; i++) {
//创建每一个线程
CreateLine cl = new CreateLine(list);
//启动线程
cl.start();
//使用join方法让主线程等待子线程执行完,然后再执行主线程
//cl.join();
}
System.out.println("商品建造完成...");
}
}
3.运行结果
Thread-3生产第48个商品
Thread-1生产第48个商品
Thread-3生产第49个商品
Thread-1生产第49个商品
Thread-3生产50商品需要消耗49020毫秒
Thread-1生产50商品需要消耗49020毫秒
Thread-1生产辣条49商品总数量:102
Thread-3生产辣条49商品总数量:102
2.实现Runnable接口的形式来实现多线程
实现Runnable接口步骤:
1.创建一个类实现Runnable接口
2.重写run方法
3.创建测试类
4.创建实现类对象
5.创建线程对象,将实现类对象放入线程中
6.调用start方法启动线程
MyRunnable.java
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"==========>"+i);
}
}
}
TestRunnable.java
public class TestRunnable {
public static void main(String[] args) {
//1.实例化MyRunnable对象
MyRunnable mr = new MyRunnable();
//2.创建线程,并将Runnable的实例作为运行体运行
Thread t = new Thread(mr,"线程1");
Thread t1 = new Thread(mr,"线程2");
Thread t2 = new Thread(mr,"线程3");
t.start();
t1.start();
t2.start();
}
}
运行结果
线程1==========>96
线程2==========>97
线程3==========>97
线程1==========>97
线程2==========>98
线程3==========>98
线程1==========>98
线程2==========>99
线程3==========>99
线程1==========>99
2.1 匿名内部类实现多线程
NoNameRunnable.java
public class NoNameRunnable {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "==========>" + i);
}
}
}, "线程1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "==========>" + i);
}
}
}, "线程2");
//线程的优先级控制
t.setPriority(10);//设置线程优先级,但是并不代表线程一定先执行,只是提高该线程抢占的概率
t1.setPriority(1);
t.start();
t1.start();
}
}
运行结果
线程2==========>93
线程2==========>94
线程1==========>94
线程2==========>95
线程1==========>95
线程1==========>96
线程2==========>96
线程2==========>97
线程1==========>97
线程2==========>98
线程1==========>98
线程1==========>99
线程2==========>99
3.实现Callable接口的形式来实现多线程
实现Callable接口步骤:
- 创建Callable接口实现类,并重写call方法,线程体执行后会返回一个结果
- 创建测试类
- 创建Callable接口子类实例,使用FutureTask类来包装Callable使用对象,自动封装Callable对象的返回值
- 将FutureTask对象放入Thread中执行,并启动线程
- 调用FutureTask中的get方法获取到封装的Callable返回值
CallableDemo.java
- Callable 该接口的方法是对某一类型进行多线程执行,并最后返回结果
public class CallableDemo implements java.util.concurrent.Callable<Integer> {
public int sum;
@Override
public Integer call() throws Exception {
for (int i = 0; i < 20; i++) {
sum += i;
}
return sum;
}
}
TestCallable.java
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws Exception {
CallableDemo cd = new CallableDemo();
FutureTask <Integer> ft = new FutureTask <>(cd);
Thread t = new Thread(ft);
t.start();
Thread t1 = new Thread(ft);
t1.start();
//结果是从FutureTask中获取
System.out.println("线程执行后的结果为:" + ft.get());
}
}
运行结果
线程执行后的结果为:190
四.同步代码块及同步方法解决线程安全问题
1.同步代码块
利用同步代码块解决同一张票在多个窗口售出的问题
- 利用Synchronized关键字实现头部处理
- 当线程进入后,获得对象锁.修改其标志,进行占用,别的线程就无法进入
- 当该线程执行完成后,释放同步锁,标志再次被修改,放行一个线程
Ticket1.java
public class Ticket1 implements Runnable{
private int ticket = 100; //总票数
@Override
public void run() {
while (true){
/**
* 设置同步代码块,使用任意对象(obj)去检测同步代码块的闭合,如果代码块中有线程,阻止其他线程进入
* 该对象可以是任意对象
*/
synchronized (this){
if (ticket>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票,票号为:"+ticket--);
}else {
break;
}
}
}
}
}
2.同步方法
- 利用Synchronized关键字修饰的方法叫做同步方法,整个方法都被同步化
Ticket2.java
public class Ticket2 implements Runnable{
private int ticket = 100; //总票数
@Override
public void run() {
boolean flag = true;
while (true){
flag = saleTicket();
}
}
//每次该方法只能进入一个线程
private synchronized boolean saleTicket() {
if (ticket>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票,票号为:"+ticket--);
return true;
}
return false;
}
}
TestTicket.java
public class TestTicket {
public static void main(String[] args) {
//Ticket1 tic = new Ticket1();
Ticket2 tic = new Ticket2();
for (int i = 1; i < 4; i++) {
Thread t = new Thread(tic,"窗口"+i);
t.start();
}
}
}
运行结果
窗口1售出车票,票号为:7
窗口1售出车票,票号为:6
窗口1售出车票,票号为:5
窗口1售出车票,票号为:4
窗口1售出车票,票号为:3
窗口1售出车票,票号为:2
窗口1售出车票,票号为:1
五.单例设计模式之懒汉式
Singleton.java
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
return instance;
}
}
SingleThread.java
import java.util.List;
public class SingleThread implements Runnable {
List<Singleton> list = null;
public SingleThread(List<Singleton> list) {
this.list = list;
}
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Singleton si = Singleton.getInstance();
System.out.println(si);
list.add(si);
}
}
TestSingle.java
import java.util.ArrayList;
import java.util.List;
public class TestSingle {
public static void main(String[] args) throws InterruptedException {
List<Singleton> list = new ArrayList<>();
SingleThread st = new SingleThread(list);
Thread t = new Thread(st);
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t.start();
t1.start();
t2.start();
}
}
运行结果
com.gec.single.Singleton@543fe730
com.gec.single.Singleton@543fe730
com.gec.single.Singleton@543fe730
六.线程计数器(记录线程执行情况)
设计目标:
- 使用多线程模拟团队集体活动
- 有一个队长 20个队员
- 所有队员在广场集合,开始自由活动
- 10秒后所有队员返回全部集结
- 统计活动时间最长的队员并输出
使用的方法:
- CountDownLatch类表示线程执行计数器,用于监听线程的执行情况.
- await() 检查线程是否执行完成
- countDown() 该方法操作线程执行完成减少1个数量,直到清零
Captain.java
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 队长
*/
public class Captain extends Thread {
private String captainName; //队长名称
private List <Meber> mebers;
public Captain(String captainName, List <Meber> mebers) {
this.captainName = captainName;
this.mebers = mebers;
}
public String getCaptainName(String captainName) {
return captainName;
}
public void setCaptainName(String captainName) {
this.captainName = captainName;
}
@Override
public void run() {
//创建计数器.数值为队员数量
CountDownLatch count = new CountDownLatch(mebers.size());
System.out.println("出去活动的队员数量为:" + count.getCount());
System.out.println(captainName+": 所有队员在广场集合,开始自由活动,10秒后所有队员必须回来集合.");
//1.开始时间
long start = System.currentTimeMillis();
System.out.println("开始时间为:"+start);
for (Meber meber : mebers) {
meber.setCount(count); //队员总数
meber.start();
}
try {
//使用await方法让主线程等待,等待count归0,然后再继续执行后续内容
count.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.结束时间
long end = System.currentTimeMillis();
System.out.println("结束时间为:"+end);
System.out.println("出去活动的队员数量:"+count.getCount());
String message = count.getCount() == 0? "所有队员集结完毕" : "有队员掉队了,开小差去了";
System.out.println(captainName+": "+message+",活动时间为:"+(end-start));
}
}
Meber.java
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* 队员
*/
public class Meber extends Thread{
private String meberName; //队员名称
private CountDownLatch count; //线程计数器
public Meber(String meberName) {
this.meberName = meberName;
}
public String getMeberName() {
return meberName;
}
public void setMeberName(String meberName) {
this.meberName = meberName;
}
public void setCount(CountDownLatch count) {
this.count = count;
}
/**
* 队员活动
*/
@Override
public void run() {
//创建随机对象,随机活动时间
Random ran = new Random();
int millis = 10000+ran.nextInt(10000);
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("队员"+meberName+", 开始自由活动,时间为:"+millis);
//将计数器减1 (模拟队员活动结束回到广场集合)
count.countDown(); //每调用一次数量减1
}
}
CaptainTest.java
package com.gec.count;
import java.util.ArrayList;
import java.util.List;
public class CaptainTest {
public static void main(String[] args) {
//队员列表
List<Meber> list = new ArrayList <>();
for (int i = 0; i < 20; i++) {
list.add(new Meber((i+1)+"号队员"));
}
Captain captain = new Captain("中国好队长",list);
captain.start();
}
}
七.线程通信
1.wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
2.wait()方法
- 在当前线程中调用方法: 对象名.wait()
- 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
- 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
- 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
- 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
3.notify()方法 / notifyAll()方法
- 在当前线程中调用方法: 对象名.notify()
- 功能:唤醒等待该对象监控权的一个线程。
- 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
4.例题
4.1 生产包子,卖包子
包子工厂
WanZaiFactory.java
import java.util.List;
public class WanZaiFactory extends Thread {
List <String> list = null;
public WanZaiFactory(List <String> list) {
this.list = list;
}
@Override
public void run() {
long start = System.currentTimeMillis();
//定义包子数量
int num = 0;
boolean flag = true;
while (flag) {
if (System.currentTimeMillis() - start <= 5000) {
synchronized (list) {
//判断包子的数量
if (list.size() >= 10) {
//暂停生产
try {
list.wait(); //沉睡当前线程
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("湾仔流沙包" + (++num));
System.out.println(Thread.currentThread().getName() + "生产湾仔流沙包" + num);
}
}
}else {
flag = false;
}
}
}
}
包子店铺
WanZaiShop.java
import java.util.List;
public class WanZaiShop extends Thread {
List <String> list = null;
public WanZaiShop(List <String> list) {
this.list = list;
}
@Override
public void run() {
//包子的数量
int num = 0;
boolean flag = true;
long start = System.currentTimeMillis();
//卖包子
while (flag) {
if (System.currentTimeMillis() - start <= 2000) {
synchronized (list) {
//判断包子的数量
if (list.size() <= 0) {
//暂停生产
try {
list.notify(); //唤醒包子工厂,生产包子
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.remove("湾仔流沙包" + (++num));
System.out.println(Thread.currentThread().getName() + "卖出湾仔流沙包" + num);
}
}
} else {
flag = false;
}
}
}
}
测试类
TestWait.java
import java.util.ArrayList;
import java.util.List;
public class TestWait {
public static void main(String[] args) {
List<String> list = new ArrayList <>();
WanZaiFactory wf = new WanZaiFactory(list);
wf.setName("湾仔码头工厂");
WanZaiShop ws = new WanZaiShop(list);
ws.setName("湾仔店铺1号");
wf.start();
ws.start();
}
}
4.2 交替打印数字、字母
Print.java
public class Print {
int index = 1;
//输出数字
public synchronized void printNum(int a){
while (index%2==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++; //数量加1后会进入到另外一个方法
System.out.println(a);
notify();
}
public synchronized void printChar(char c){
while (index%2!=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++; //数量加1后会进入到另外一个方法
System.out.println(c);
notify();
}
}
PrintNum.java
public class PrintNum implements Runnable{
Print p = null;
public PrintNum(Print p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <= 26; i++) {
p.printNum(i);
}
}
}
PrintCharacter.java
public class PrintCharacter implements Runnable{
Print p = null;
public PrintCharacter(Print p) {
this.p = p;
}
@Override
public void run() {
for (char i = 'a';i<='z';i++){
p.printChar(i);
}
}
}
TestPrint.java
public class TestPrint {
public static void main(String[] args) {
Print p = new Print();
Thread t = new Thread(new PrintNum(p));
Thread t1 = new Thread(new PrintCharacter(p));
t.start();
t1.start();
}
}
总结
本文主要讲解了Java 多线程,希望浏览文章的读者们能够通过该文章有所提升。