Java多线程
1、简介
- 多任务:在只有单线程的情况下,看似同时执行的任务,其实因为处理时间的缘故,实际在同一时间只执行一个任务。
- 多线程:一个进程中可以包括若干个线程,一个进程至少包括一个线程。线程是CPU调度和执行的最小单位。如果实现一个线程执行一个任务,就可以实现同时实现任务。
注意:真正的多线程是指多个CPU,也就是多核。在只有单核的情况下,同一时间,cpu只能执行一条指令,因为cpu切换指令的速度很快,就造成了同一时间进行的错觉。
核心概念:
- 每一个线程都在自己的工作内存交互,内存控制不当就会造成数据错误
- 在一个进程中开辟多个线程,线程的调度由调度器执行,调度顺序与操作系统密切相关,不能人为改变
- 线程会带来额外的开销,例如CPU调度时间,并发控制开销
2、创建方式
基本流程
2.1 继承Thread类
声明一个类是Thread
的子类。这个子类应重写类Thread
的run
方法。子类的一个实例可以被分配和启动。例如,一个线程计算素数大于规定值可以写成如下:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
下面的代码将创建一个线程并开始运行:
PrimeThread p = new PrimeThread(143);
p.start();
Demo01
package com.xiaojing;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//实现方式一:继承Thread
public class ThreadDemo01 extends Thread{
private String url;
private String name;
public ThreadDemo01(String url, String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
new FileDownloader().download(url,name);
System.out.println("下载了文件"+name);
}
public static void main(String[] args) {
ThreadDemo01 thread1 = new ThreadDemo01("https://i0.hdslb.com/bfs/archive/e62b6b095ef38dfb742687f11e4b570dde420b5d.png","bilibili.png");
ThreadDemo01 thread2 = new ThreadDemo01("https://i0.hdslb.com/bfs/archive/a33a3d01bc6a742e2ec115a126aa4aa90425cf42.jpg@880w_388h_1c_95q","诸葛大力.png");
thread1.start();
thread2.start();
}
}
class FileDownloader{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 实现Runnable接口
创建一个线程是声明一个类实现Runnable
接口的其他方式。该类实现run
方法。那类的一个实例可以分配,作为一个参数传递Thread
时创建并开始。同样的例子在这个其他风格看起来像以下:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
下面的代码将创建一个线程并开始运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
推荐使用Runnable接口:避免单继承的局限性,因为Thread类也实现了Runnable接口
Demo02
package com.xiaojing;
//龟兔赛跑,并模拟兔子睡觉
public class Race implements Runnable {
private int steps;
private String winner;
@Override
public void run() {
while(true){
Boolean bool = isOver(steps);
if(Thread.currentThread().getName().equals("兔子") && steps%20 == 0){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(bool){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+steps+++"步");
}
}
public Boolean isOver(int steps){
if(winner != null){
return true;
}
if(steps >= 100){
winner = Thread.currentThread().getName();
System.out.println(winner +"is winner");
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
2.3 实现Callable接口(了解)
Demo03
package com.xiaojing;
import java.util.concurrent.*;
//实现方式三:Callable
public class ThreadDemo04 implements Callable<Boolean> {
private String url;
private String name;
public ThreadDemo04(String url, String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
new FileDownloader().download(url,name);
System.out.println("下载了文件"+name);
return true;
}
public static void main(String[] args) {
ThreadDemo01 thread1 = new ThreadDemo01("https://i0.hdslb.com/bfs/archive/e62b6b095ef38dfb742687f11e4b570dde420b5d.png","bilibili.png");
ThreadDemo01 thread2 = new ThreadDemo01("https://i0.hdslb.com/bfs/archive/a33a3d01bc6a742e2ec115a126aa4aa90425cf42.jpg@880w_388h_1c_95q","诸葛大力.png");
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交
Future<?> s1 = executorService.submit(thread1);
Future<?> s2 = executorService.submit(thread2);
//关闭服务
executorService.shutdown();
}
}
3、Lambda表达式
Lambda表达式实质是函数式编程。使用lambda表达式需要一个函数式接口:即只包含一个方法的接口。
Lambda表达式的产生过程:
外部类 ——>静态内部类——>局部内部类——>匿名内部类——>lambda表达式
基本结构:
(params)->experssion[表达式]
(params)->statement[语句]
(params)->{statements}
package com.xiaojing.lambda;
public class LambdaDemo {
public static void main(String[] args) {
//Lambda表达式
Hello h = ()->{
System.out.println("Hello world");
};
}
}
//函数式接口
interface Hello{
void Hello();
}
4、线程停止
- 不推荐使用JDK提供的stop()、destroy()方法
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量当flag=false的时候,终止线程
package com.xiaojing.stopThread;
public class StopThreadDemo implements Runnable{
boolean flag = true;
@Override
public void run() {
int i = 0;
while(flag){
System.out.println("thread" + i++ +"run...");
}
}
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
StopThreadDemo thread = new StopThreadDemo();
new Thread(thread).start();
for (int i = 0; i < 100; i++) {
System.out.println("mainthread" + i);
if(i > 90){
thread.stop();
System.out.println("子线程停止了");
}
}
}
}
5、线程休眠
- sleep()指定当前线程阻塞的毫秒数
- 时间过后,线程进入就绪状态
- 每一个对象都有一个锁,sleep不会释放锁
Demo
package com.xiaojing.sleepThread;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟延时
public class SleepThreadDemo {
public static void main(String[] args) {
Date date = new Date(System.currentTimeMillis());
while(true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6、线程礼让
- 礼让线程,让正在执行的线程重新转为就绪状态
- 让cpu重新调度,即礼让不一定成功!
7、Join
- Join合并线程,待该线程执行完后,才能执行其他线程
- 可类似插队
8、线程状态
- 线程状态。一个线程可以有以下规定:
NEW
线程尚未开始RUNNABLE
处于这种状态中的java虚拟机执行的线程。BLOCKED
线程阻塞等待监控锁WAITING
处于这种状态的线程被无限期地等待另一个线程来执行特定的动作。TIMED_WAITING
处于这种状态的线程正在等待另一个线程上执行一个动作指定的等待时间。TERMINATED
处于这种状态的线程退出
package com.xiaojing.threadState;
public class StateDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
while(state != Thread.State.TERMINATED){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();
System.out.println(state);
}
}
}
9、线程优先级
- 线程的优先级用数字表示,范围是(1~10)
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;(默认优先级)
- 优先级高的线程,只是意味着被cpu调度的概率大,并不意味着一定会被调度
10、守护线程
- 线程分为用户线程和守护线程
- JVM必须确保用户线程执行完毕
- JVM不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等
package com.xiaojing.state;
public class DaemoDemo {
public static void main(String[] args) {
God god = new God();
Person person = new Person();
Thread godThread = new Thread(god);
godThread.setDaemon(true);//默认为false,即用户线程
Thread personThread = new Thread(person);
godThread.start();
personThread.start();
}
}
class God implements Runnable {
@Override
public void run() {
while(true)
{
System.out.println("上帝永生");
}
}
}
class Person implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("人活了"+i+"天");
}
System.out.println("=====Goodbye world======");
}
}
//personThread执行完后,JVM就停止了,而没有管godThread是否执行完毕
11、线程同步(并发)
**并发:**同一个对象被多个线程同时操作
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程才使用。而在这个过程中,仅仅只有队列还不够,还需要加入锁机制synchronized。若是线程在访问某个资源的时候,没有上锁,那么其他线程也能访问到这个资源,会导致不安全。前面在线程休眠的时候,提到:**每一个对象都有一把锁!**当一个线程获得一个对象的排他锁,便可以独占资源,其他需要这个对象的线程就暂时挂起,直到这个锁被释放。虽然锁机制使安全问题得到解决,但也存在一些问题:
- 在多线程竞争下,加锁,释放锁,会导致上下文切换频繁以及调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,就会产生优先级倒置,引起性能问题
synchronized关键字
synchronized关键字包括两种用法:synchronized方法和synchronized块
同步方法:
同步方法:public synchronized void method(){}
- synchronized方法控制对对象的访问,每一个对象都有一个锁,而每一个synchronized方法都必须获得调用这个方法的对象的锁才能执行,否则这个线程就会被阻塞。
- 如果一个大的方法被申明为synchronized,会影响性能
同步块:
同步块:synchronized(obj){}
- obj是同步监视器,可以是任何对象,但推荐使用公共资源作为同步监视器
- 在同步方法中,不用设置同步监视器,其默认的同步监视器就是this,这个对象本身
- 在同步块中,代码块执行完毕后,自动释放监视对象obj的锁
一般来说,同步块中锁的对象是产生变化的对象。
Demo:
package com.xiaojing.syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(1000, "Marry");
Get you = new Get(account, 300);
Get girl = new Get(account, 500);
new Thread(you,"you").start();
new Thread(girl,"girl").start();
}
}
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Get implements Runnable{
//账户
Account account;
//取钱
int getMoney;
//手中的钱
int haveMoney;
public Get(Account account, int getMoney) {
this.account = account;
this.getMoney = getMoney;
}
//线程不安全
// @Override
// public void run() {
// if(account.money <= 0){
// System.out.println(Thread.currentThread().getName()+"钱不够!");
// return;
// }
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// account.money = account.money - getMoney;
// haveMoney = haveMoney + getMoney;
// System.out.println("余额为"+account.money);
// System.out.println(Thread.currentThread().getName()+"手中有"+ haveMoney);
// }
//线程安全
@Override
public void run() {
synchronized (account){
if(account.money <= 0){
System.out.println(Thread.currentThread().getName()+"钱不够!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - getMoney;
haveMoney = haveMoney + getMoney;
System.out.println(Thread.currentThread().getName()+"手中有"+ haveMoney);
System.out.println("余额为"+account.money);
}
}
}
12、死锁
死锁:多个线程各自占有一些共享资源,并且互相等待对方的资源才能运行,导致两个或多个线程一直处于等待对方资源的状态,而停止执行的情形。例如某个同步块,同时拥有两个对象,就会发生死锁的问题。(同步块内嵌问题)
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程访问
- 请求与保持条件:一个线程因为请求资源而阻塞,对以获得的资源保持不放
- 不剥夺条件:线程已获得的资源在使用完前,不能强行剥夺
- 循环等待条件:多个线程循环等待对方的资源
一个发生死锁的写法:
package com.xiaojing.syn;
public class Deadlock {
public static void main(String[] args) {
eatFruit b1 = new eatFruit(0);
eatFruit b2 = new eatFruit(1);
new Thread(b1,"我").start();
new Thread(b2,"你").start();
}
}
class Apple{
}
class Pear{
}
class eatFruit implements Runnable{
static Apple apple = new Apple();
static Pear pear = new Pear();
private int code;
public eatFruit(int code) {
this.code = code;
}
@Override
public void run() {
eat();
}
private void eat(){
if(code == 0){
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"得到苹果的锁");
synchronized (pear){
System.out.println(Thread.currentThread().getName()+"得到梨子的锁");
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
synchronized (pear){
System.out.println(Thread.currentThread().getName()+"得到梨子的锁");
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"得到苹果的锁");
}
}
}
}
}
安全的写法:
package com.xiaojing.syn;
public class Deadlock {
public static void main(String[] args) {
eatFruit b1 = new eatFruit(0);
eatFruit b2 = new eatFruit(1);
new Thread(b1,"我").start();
new Thread(b2,"你").start();
}
}
class Apple{
}
class Pear{
}
class eatFruit implements Runnable{
static Apple apple = new Apple();
static Pear pear = new Pear();
private int code;
public eatFruit(int code) {
this.code = code;
}
@Override
public void run() {
eat();
}
private void eat(){
if(code == 0){
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"得到苹果的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (pear){
System.out.println(Thread.currentThread().getName()+"得到梨子的锁");
}
}else{
synchronized (pear){
System.out.println(Thread.currentThread().getName()+"得到梨子的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"得到苹果的锁");
}
}
}
}
13、Lock
- 从JDK5.0开始,Java提供了一个更强大的线程同步机制—lock对象来实现同步
- java.util.concureent.locks.Lock接口是控制多个线程对某个资源进行独占访问。线程在执行前,需要获得lock对象,并对lock对象进行加锁,执行完毕后,进行释放锁。
- ReentrantLock类可以显性的加锁和释放,比较常用。
- 使用lock加锁时,要紧跟try…finally语句
Demo:
package com.xiaojing.gaoji;
import java.util.concurrent.locks.ReentrantLock;
public class Testlock {
public static void main(String[] args) {
Num num = new Num();
new Thread(num).start();
new Thread(num).start();
new Thread(num).start();
}
}
class Num implements Runnable{
int ticketnum = 10;
private final ReentrantLock lock = new ReentrantLock();//ReentrantLock 可重入锁
@Override
public void run() {
lock.lock();
try{
while (true){
if(ticketnum > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketnum--);
}else{
break;
}
}
}finally {
lock.unlock();
}
}
}
14、线程通信
Java提供了几个方法实现线程间的通信
Demo
package com.xiaojing.gaoji;
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
Actor actor = new Actor(tv);
Lisener lisener = new Lisener(tv);
new Thread(actor).start();
new Thread(lisener).start();
}
}
class Actor implements Runnable{
TV tv;
public Actor(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2 == 0){
tv.play("喜羊羊");
}else {
tv.play("雾山五行");
}
}
}
}
class Lisener implements Runnable{
TV tv;
public Lisener(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
class TV{
private boolean flag = true;//true——观众等待,false——演员等待
private String show;
//演员拍戏
public synchronized void play(String show){
if(!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
this.show = show;
System.out.println("演员表演了"+this.show);
this.flag = !this.flag;
}
//观众观看
public synchronized void watch(){
if(this.flag){
try {
//观众先等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
System.out.println("观众观看了"+this.show);
this.flag = !this.flag;
}
}
15、线程池
在阿里代码标准中,已经不用Executors.newFixedThreadPool()创建线程池了,而使用ThreadPoolExecutor类创建线程池。实际上在newFixedThreadPool方法中,就返回了一个ThreadPoolExecutor对象。
package com.xiaojing.gaoji;
import java.util.concurrent.*;
public class TestPool {
public static void main(String[] args) {
//创建容量为10的线程池
ThreadPoolExecutor service = new ThreadPoolExecutor(10,10,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1),new ThreadPoolExecutor.DiscardOldestPolicy());
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public ThreadPoolExecutor(
int corePoolSize, - 线程池核心池的大小。
int maximumPoolSize, - 线程池的最大线程数。
long keepAliveTime, - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
TimeUnit unit, - keepAliveTime 的时间单位。
BlockingQueue<Runnable> workQueue, - 用来储存等待执行任务的队列。
ThreadFactory threadFactory, - 线程工厂。
RejectedExecutionHandler handler) - 拒绝策略。