所有的资料来源都是以【狂神说Java】多线程详解为基础
【狂神说Java】多线程详解
建议看视频,这篇博客是笔记
同时要结合目录看
类比于现实中的的多任务,在同一个时间段,同时做几件事情
在程序中就是主线程与子线程同时进行任务
在操作系统中的运行的程序就是进程,而进程通常采用多进程执行
程序
指令和数据的有序集合,本身没有任何运行的含义,是静态概念
进程
是执行程序的一次执行过程,是一个动态概念,是系统资源分配的单位
线程
一个进程通常包括多个线程,线程是CPU调度和执行的单位
run()方法是需要执行的方法体
start()方法是开启多线程的方法体
不建议使用:避免OOP单继承局限性
package cn.livorth;
public class TestThread01 extends Thread{
@Override
public void run() {
//在run()方法中编写执行体
for (int i = 0; i < 100; i++) {
System.out.println("在子线程中执行--" + i);
}
}
public static void main(String[] args) {
//在主线程中创建线程对象,使用start方法启动线程
TestThread01 testThread = new TestThread01();
testThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("在主线程中执行---" + i);
}
}
}
效果如下:
并行交替执行,轮流占用cpu
样例来自《狂神说java-多线程详解》
代码(详解在注释):
package cn.livorth;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread02 extends Thread{
private String url;
private String filename;
//构造函数传入所需的url和filename
public TestThread02(String url, String filename) {
this.url = url;
this.filename = filename;
}
@Override
public void run() {
//调用downLoader来获取文件
webDownLoader webDownLoader = new webDownLoader();
webDownLoader.downLoader(url, filename);
System.out.println(filename + "下载完成");
}
public static void main(String[] args) {
//传入三张图图片的url
TestThread02 t1 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
"481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");
TestThread02 t2 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
"98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");
TestThread02 t3 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
"70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");
//开始多线程下载
t1.start();
t2.start();
t3.start();
}
}
class webDownLoader
{
/**
* 通过url获取文件内容,并保存
* @param url
* @param name
*/
public void downLoader(String url, String name)
{
try {
//调用Commons_io包里面的方法copyURLToFile
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoader出现问题");
}
}
}
结果:
这只是一个简单的对多线程的小实践,事实上通过这样获取图片还是有诸多问题
推荐使用:避免单继承的局限性,方便同一个对象被多个进程调用
package cn.livorth;
public class TestThread03 implements Runnable{
@Override
public void run() {
//在run()方法中编写执行体
for (int i = 0; i < 100; i++) {
System.out.println("在子线程中执行--" + i);
}
}
public static void main(String[] args) {
//创建Runable接口的实现类对象
TestThread03 testThread = new TestThread03();
//创建线程对象,通过线程对象来开启线程,也就是代理
Thread thread = new Thread(testThread);
thread.start();
//也可以缩写为:
//new Thread(testThread).start();
for (int i = 0; i < 100; i++) {
System.out.println("在主线程中执行---" + i);
}
}
}
效果如下:
按照狂神说的那个样例来的话,会出现问题
对个线程操作同一个资源的时候,线程不安全,数据会紊乱
所以不写了
仅作了解即可
通过服务提交线程
改自Thread中的应用
《狂神说java-多线程详解》-P8
package cn.livorth;
import java.util.concurrent.*;
public class TestCallable01 implements Callable<Boolean> {
private String url;
private String filename;
//构造函数传入所需的url和filename
public TestCallable01(String url, String filename) {
this.url = url;
this.filename = filename;
}
@Override
public Boolean call() {
//调用downLoader来获取文件
webDownLoader webDownLoader = new webDownLoader();
webDownLoader.downLoader(url, filename);
System.out.println(filename + "下载完成");
return true;
}
public static void main(String[] args) {
//传入三张图图片的url
TestCallable01 t1 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
"481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");
TestCallable01 t2 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
"98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");
TestCallable01 t3 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
"70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");
//创建执行服务(参数为线程池的大小)
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = service.submit(t1);
Future<Boolean> r2 = service.submit(t2);
Future<Boolean> r3 = service.submit(t3);
//获取结果(不写应该也是可以的,这里应该是作为一种检验参数)
try {
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
//关闭服务
service.shutdown();
}
}
结果:
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
不推荐使用JDK提供的stop()、destroy()。
推荐让线程自己停下来,但也不建议使用死循环
建议使用一个标志位作为终止变量,当flag==false时,线程终止运行
package cn.livorth.state;
public class TestStop implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i = 0;
while(flag)
{
System.out.println("子进程执行中---" + i++);
}
}
//设置个公开方法,利用标志位停止线程
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 100; i++) {
System.out.println("主进程执行中---" + i);
if(i == 90){
testStop.stop();
System.out.println("子进程结束运行");
}
}
}
}
线程休眠
sleep()方法的单位是毫秒
sleep()存在异常InterruptedException
sleep完了线程进入就绪状态,不会直接进入执行态
可以用于模拟网络延时,倒计时等
每个对象都有一个锁,sleep不释放锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
模拟网络延时可以放大问题的发生性
线程礼让
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
Join
join合并线程,只能是当前线程执行完之后才能执行其他线程,对其他线程造成阻塞
package cn.livorth.state;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程执行---" + i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 200)
{
try {
//阻塞主线程,使子线程优先执行完
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程执行---" + i);
}
}
}
代码样例:
package cn.livorth.state;
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("******");
}
});
//启动前
Thread.State state = thread.getState();
System.out.println(state);//NEW
//启动后
thread.start();
state = thread.getState();
System.out.println(state);//RUNABLE
//阻塞时与结束时
while(state != Thread.State.TERMINATED){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();
//TIMED_WAITING或者TERMINATED
System.out.println(state);
}
}
}
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.
使用以下方式获取或改变优先级
注意:
先设置优先级再启动
main方法的默认优先级为5
理论上来说优先级越高的越先执行,哪怕他start更晚
代码示例:
package cn.livorth.state;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
//将上帝设置为守护线程,在false的时候为用户线程
thread.setDaemon(true);
thread.start();
//开启一般的用户线程
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
//上帝永生,无限循环
while (true)
System.out.println("上帝在你身旁");
}
}
class You implements Runnable{
@Override
public void run() {
//人生有限,100年后撒手人寰
for (int i = 0; i < 100; i++) {
System.out.println("happy everyday");
}
System.out.println("goodbye world");
}
}
结果:
上帝明明是个死循环,但是进程还是结束了。
因为这里将上帝线程设置为了守护线程,虚拟机不加以考虑
并发:同一个对象被多个线程同时操作,也就是不同线程同时操作同一个资源地址,造成数据紊乱
同步:多个需要同时访问资源的线程进入对象的等待池,等待前面线程使用完毕
锁:每个对象都有把锁,当获取对象时,独占资源,其他线程必须等待,使用结束后才释放
synchronized关键字
synchronized默认锁的是他自身的对象,要是跨对象,通常使用同步块,即锁共享资源所在的对象
同步方法
public synchronized void method(int args){}
因为每个对象对应一把锁,使用加了关键字之后,方法一旦执行就独占该锁
缺陷:若将一个大的方法申明为synchronized将会影响效率
同步块
synchronized(Obj){}
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形. 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
我的理解:
张三占用着A资源,等着B资源,李四占用着B资源,等着A资源
张三同时有AB资源才能完成任务,李四也一样,但是两个人谁都不会先放手
然后就进入了死锁
产生死锁的四个必要条件:
只要破除任意一个就能避免死锁
通过显示定义同步锁对象(Lock)来实现同步
ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
演示代码:
package cn.livorth.gaoji;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock = new TestLock2();
new Thread(testLock, "A").start();
new Thread(testLock, "B").start();
new Thread(testLock, "C").start();
}
}
class TestLock2 implements Runnable{
int count = 1000;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true)
{
try {
//进入加锁状态
lock.lock();
if(count > 0)
System.out.println(Thread.currentThread().getName() + "---" +count--);
else
break;
}
finally {
//解锁
lock.unlock();
}
}
}
}
说明:
在不加锁的情况下,ABC可能会同时操作到count,导致数据紊乱
在加了锁之后,ABC排队操作
有一说一这个我们上个学期的操作系统讲的很详细了
生产者与消费者共享一个资源,同时生产者与消费者相互依赖互为条件
主要是用以下方法达到通信的效果:
解决方式:
管程法
设置一个缓冲区,用于暂存数据
代码示例:
package cn.livorth.gaoji;
import java.util.LinkedList;
import java.util.Queue;
//管程法解决
public class TestPC {
public static void main(String[] args) {
//创建缓冲区
SynContainer container = new SynContainer();
//双线程
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了第" + i + "件产品");
container.push(new Products(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第" + container.pop().id + "件产品");
}
}
}
//产品
class Products{
int id;
public Products(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
Queue<Products> queue = new LinkedList<Products>();
int count = 0;
int size = 10;
//生产者放入产品
public synchronized void push(Products product)
{
//容器满,等待消费者消费
if(count == size)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没满,我们则需要存入成品,并唤醒消费者
count++;
queue.offer(product);
this.notifyAll();
}
//消费者消费产品
public synchronized Products pop()
{
//容器为空,等待生产者生产
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器有剩,取出物品,并告诉生产者可以生产了
count--;
Products productPoll = queue.poll();
this.notifyAll();
return productPoll;
}
}
信号灯法
设置一个标记位(类似于容量为1的管程法)
package cn.livorth.gaoji;
//信号灯法解决
public class TestPC2 {
public static void main(String[] args) {
//创建缓冲区
TheProduct theProduct = new TheProduct();
//双线程
new Producers(theProduct).start();
new Consumers(theProduct).start();
}
}
//生产者
class Producers extends Thread{
TheProduct theProduct;
public Producers(TheProduct theProduct) {
this.theProduct = theProduct;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
theProduct.push("产品" + i);
}
}
}
//消费者
class Consumers extends Thread{
TheProduct theProduct;
public Consumers(TheProduct container) {
this.theProduct = container;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
theProduct.pop();
}
}
}
//产品
class TheProduct{
boolean flag = false;
String product;
//生产者生产,消费者等待
public synchronized void push(String product)
{
if(flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者生产了" + product);
this.product = product;
this.flag = !this.flag;
this.notifyAll();
}
//消费者消费,生产者等待
public synchronized void pop()
{
if(!flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了" + product);
this.flag = !this.flag;
this.notifyAll();
}
}
简单的使用参照Callable的使用
并发进阶以后再学