教程
狂神说java官网
B站视频链接
线程简介
程序Precess是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程Thread则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。
main()
称之为主线程,为系统的入口,用于执行整个程序。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程实现
线程创建
继承Thread类
步骤:继承Thread类;重写run()方法;调用start开启线程。
public class TestThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("测试线程---------" + i); } }
public static void main(String[] args) { TestThread testThread1 = new TestThread();
testThread1.start();
for (int i = 0; i < 100; i++) { System.out.println("主线程-----------" + i); } } }
|
可见两个线程交替运行。注:线程开启并不一定立即执行,由CPU调度执行。
import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.IOException; import java.net.URL;
public class TestThread2 extends Thread {
private String url; private String name;
public TestThread2(String url, String name) { this.url = url; this.name = name; }
@Override public void run() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载文件:" + name); }
public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://img.lfalive.top/PT/HUDPT.jpg", "hudpt.jpg"); TestThread2 t2 = new TestThread2("https://img.lfalive.top/PT/hitpt.JPG", "hitpt.jpg"); TestThread2 t3 = new TestThread2("https://img.lfalive.top/PT/byrpt.JPG", "byrpt.jpg");
t1.start(); t2.start(); t3.start(); } }
class WebDownloader { public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常"); } } }
|
实现Runnable接口
步骤:定义MyRunnable类实现Runnable接口;实现run()方法,编写线程执行体;创建线程对象,调用start()方法启动线程。
public class TestThread3 implements Runnable { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("测试线程---------" + i); } }
public static void main(String[] args) {
TestThread3 t1 = new TestThread3();
new Thread(t1).start();
for (int i = 0; i < 20; i++) { System.out.println("主线程-----------" + i); } } }
|
多线程下载图片的样例也可用该方法实现。
不建议继承Thread类,因为需要避免OOP单继承局限性;建议使用Runnable接口,可以避免单继承局限性,灵活方便,方便同一个对象被多个线程使用,即”将任务和线程完全分离“。
并发问题
以售卖火车票为例,多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
public class Race implements Runnable { private static String winner;
@Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步"); if (gameOver(i)) { break; } } }
private boolean gameOver(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(); } }
|
winner变量即为公共资源。
实现Callable接口及线程池
Callable好处:可以定义返回值,可以抛出异常。线程池参考:
import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> { private String url; private String name;
public TestCallable(String url, String name) { this.url = url; this.name = name; }
@Override public Boolean call() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载文件:" + name); return true; }
public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("https://img.lfalive.top/PT/HUDPT.jpg", "hudpt.jpg"); TestCallable t2 = new TestCallable("https://img.lfalive.top/PT/hitpt.JPG", "hitpt.jpg"); TestCallable t3 = new TestCallable("https://img.lfalive.top/PT/byrpt.JPG", "byrpt.jpg");
ExecutorService ser = Executors.newFixedThreadPool(3);
Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3);
boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get();
System.out.println(rs1); System.out.println(rs2); System.out.println(rs3);
ser.shutdown(); } }
class WebDownloader { public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常"); } } }
|
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:核心池的大小
corePoolSize
,最大线程数maximumPoolSize
,线程没有任务时最多保持多长时间后会终止keepAliveTime
相关API:Executors
和ExecutorService
。
newFixedThreadPool()
方法创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。ExecutorService
也可以和Runnable
配合使用,如execute(Runnable)
,submit(Runnable)
,submit(Callable)
等。
FutureTask
实现了Future
接口,可以把FutureTask
交给Executor
执行;也可以通ExecutorService.submit()
方法返回一个FutureTask
,然后执行FutureTask.get()
方法。通过返回的Future
对象,我们可以检查提交的任务是否执行完毕。如果任务执行完成,future.get()
方法会返回Callable任务的执行结果。注意,future.get()
方法会产生阻塞。
调用shutdown()
方法之后,ExecutorService
不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()
执行之前提交的任务都会被执行。如果我们想立即关闭,可以调用ExecutorService.shutdownNow()
方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
静态代理
真实对象和代理对象都要实现同一个接口,代理对象要代理真实角色。
好处:代理对象可以做很多真实对象做不了的事情,真实对象可以专注做自己的事情。
public class StaticProxy { public static void main(String[] args) { You you = new You("张三"); new WeddingCompany(you).HappyMarry(); } }
interface Marry { void HappyMarry(); }
class You implements Marry { private String name;
public You(String name) { this.name = name; }
@Override public void HappyMarry() { System.out.println(this.name + "结婚了,恭喜!"); } }
class WeddingCompany implements Marry { private Marry target;
public WeddingCompany(Marry target) { this.target = target; }
@Override public void HappyMarry() { before(); this.target.HappyMarry(); after(); }
private void after() { System.out.println("事后,结尾款。"); }
private void before() { System.out.println("事前,布置会场。"); } }
|
Lambda表达式
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,可以通过lambda表达式来创建该接口的对象。
public class TestLambda1 { static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2"); } }
public static void main(String[] args) { ILike like = new Like(); like.lambda();
like = new Like2(); like.lambda();
class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3"); } }
like = new Like3(); like.lambda();
like = new ILike() { @Override public void lambda() { System.out.println("I like lambda4"); } }; like.lambda();
like = () -> System.out.println("I like lambda5"); like.lambda(); } }
interface ILike { void lambda(); }
class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda"); } }
|
public class TestLambda2 { public static void main(String[] args) { ILove love = (a, b) -> System.out.println(a + " love " + b); love.love(2, 5); } }
interface ILove { void love(int a, int b); }
|
线程状态
线程方法
方法 |
说明 |
setPriority(int newPriority) |
更改线程的优先级 |
static void sleep(long millis) |
在指定的毫秒数内让当前正在执行的线程休眠 |
void join() |
等待该线程终止 |
static void yield() |
暂停当前正在执行的线程对象,并执行其他线程 |
void interpret() |
中断线程,别用这个方式 |
boolean isAlive() |
测试线程是否处于活动状态 |
停止线程
不推荐使用JDK提供的stop(),destroy()方法,已废弃。推荐让线程自己停下来。简直使用一个标志位进行终止变量,当flag=false,则终止线程运行。
public class TestStop implements Runnable { private boolean flag = true;
@Override public void run() { int i = 0; while (flag) { System.out.println("running" + 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 < 1000; i++) { System.out.println("main" + i); if (i == 900) { testStop.stop(); System.out.println("time to stop"); } } } }
|
线程休眠
sleep存在异常InterruptException,sleep时间达到后线程进入就绪状态,可以模拟网络延时、倒计时等。
每个对象都有一个锁,sleep不会释放锁。
public class TestSleep { public static void main(String[] args) { try { countDown(10); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void countDown(int num) throws InterruptedException { while (num > 0) { System.out.println(num--); Thread.sleep(1000); } } }
|
线程礼让yield
让当前正在执行的线程暂停,但不阻塞。将线程从运行状态转为就绪状态,让CPU重新调度。礼让不一定成功,取决于CPU。
合并线程
Join()
合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
public class TestJoin implements Runnable { @Override public void run() { for (int i = 0; i < 500; i++) { System.out.println("vip-->" + i); } }
public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start();
for (int i = 0; i < 300; i++) { if (i == 200) { thread.join(); } System.out.println("main-->" + i); } } }
|
线程状态观测
线程状态处于以下几个状态之一:NEW、RUNNABLE、BLOCKED、WATTING、TIMED_WAITTING、TERMINATED。
public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 2; 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(); System.out.println(state);
while (state != Thread.State.TERMINATED) { Thread.sleep(100); state = thread.getState(); System.out.println(state); } } }
|
线程优先级
线程的优先级用数字表示,通过getPriority()
获取优先级,通过setPriority(int)
改变优先级。默认最大优先级MAX_PRIORITY
为10,最小MIN_PRIORITY
为1,可修改。
public class TestPriority { public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority);
t1.setPriority(1); t2.setPriority(4); t3.setPriority(Thread.MAX_PRIORITY);
t1.start(); t2.start(); t3.start(); } }
class MyPriority implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); } }
|
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,都是看CPU的调度。
守护线程
线程分为守护线程和用户线程。虚拟机必须确保用户线程执行完毕,但不必等待守护线程执行完毕。守护线程,和后台记录操作日志、监控内存、垃圾回收等。
线程同步
并发:同一个对象被多个线程同时操作。
线程同步其实就是一种等待机制,多个线程进入这个对象的等待池形成队列。
==>加入锁机制synchronized,一个线程获得对象的排它锁,独占资源,其他线程必须等待其使用完后释放锁。锁机制会引起一定程度的性能问题,以换取安全性。例如,如果一个优先级高的线程等一个优先级低的线程释放锁,会导致优先级倒置。
不安全的例子
public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(() -> list.add(Thread.currentThread().getName())).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
|
最后集合大小小于10000,因为同时可能会有多个线程操作一个对象。
同步方法和同步块
synchronized
关键字有两种用法,synchronized
方法和synchronized
块。
同步方法
public synchronized void method(int args) {}
|
每个对象都有一把锁,synchronized
方法都必须获得调用该方法的对象的锁才能运行,线程一旦执行,就独占该锁,直到方法返回释放。缺点:将一个大的方法声明为synchronized
会影响效率,锁得太多,浪费资源。
同步块
Obj称之为同步监视器,Obj可以是任何对象,推荐使用共享资源作为同步监视器。同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这对象本身。
public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(() -> { synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
|
不安全的例子,加上同步块后就安全了。
CopyOnWriteArrayList
CopyOnWriteArrayList
,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁。所有可变操作都是通过对底层数组进行一次复制来实现。底层实现添加的原理是先copy出一个容器(可以简称副本),再在副本里进行修改,最后把副本的引用地址赋值给之前旧容器的地址。在修改数据期间,其他线程如果读取数据,是读取到旧容器里的数据。
参考:高并发编程之CopyOnWriteArrayList介绍
CopyOnWriteArrayList
也是属于java.util.concurrent
工具包的,Java的concurrent用法详解。
public class TestJUC { public static void main(String[] args){ CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(() -> list.add((Thread.currentThread().getName()))).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
|
死锁
多个线程互相抱着对方需要的资源,形成僵持。
public class DeadLock { public static void main(String[] args) { new Makeup(0, "Nina").start(); new Makeup(1, "Amelie").start(); } }
class Lipstick { }
class Mirror { }
class Makeup extends Thread { static final Lipstick lipstick = new Lipstick(); static final Mirror mirror = new Mirror(); int choice; String grilName;
Makeup(int choice, String grilName) { this.choice = choice; this.grilName = grilName; }
@Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } }
private void makeup() throws InterruptedException { if (choice == 0) { synchronized (lipstick) { System.out.println(this.grilName + " gets lipstick"); Thread.sleep(1000); synchronized (mirror) { System.out.println(this.grilName + " gets mirror"); } } } else { synchronized (mirror) { System.out.println(this.grilName + " gets mirror"); Thread.sleep(2000); synchronized (lipstick) { System.out.println(this.grilName + " gets lipstick"); } } } } }
|
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
想办法打破其中的一个或多个条件就可以避免。
Lock(锁)
通过显式定义同步锁对象(Lock对象)来实现同步。java.util.concurrent.locks.Lock
接口可以控制多个线程对共享资源进行访问。ReentrantLock
是可重入锁,实现了显式加锁、释放锁。
private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); try{ } finally { lock.unlock(); } }
|
对比:Lock
是显式,synchronized
是隐式。Lock
只有代码块锁,synchronized
有代码块锁和方法锁。使用Lock
锁,JVM将话费较少时间调度线程,性能更好,且具有更好的扩展性。一般优先使用Lock>同步代码块>同步方法。
线程通信
java提供了几个方法解决线程之间的通信问题。均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
。
方法名 |
作用 |
wait() |
表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) |
指定等待的毫秒数 |
notify() |
唤醒一个处于等待状态的线程 |
notifyAll() |
唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
在生产者消费者问题中,仅有synchronized
是不够的,synchronized
可阻止并发更新同一个共享资源,但不能用来实现不同线程之间的消息传递。
管程法
生产者将生产号的数据放入缓冲区,消费者从缓冲区拿出数据。
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++) { container.push(new Chicken(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++) { container.pop(); } } }
class Chicken { int id;
public Chicken(int id) { this.id = id; } }
class SynContainer { Chicken[] chickens = new Chicken[10]; int count = 0;
public synchronized void push(Chicken chicken) { if (count == chickens.length) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } chickens[count++] = chicken; System.out.println("produced id = " + chicken.id + ", container " + count); this.notifyAll(); }
public synchronized void pop() { if (count == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("consumed id = " + chickens[--count].id + ", container " + count); this.notifyAll(); } }
|
信号灯法
public class TestPc2 { public static void main(String[] args) { TV tv = new TV(); new Actor(tv).start(); new Watcher(tv).start(); } }
class Actor extends Thread { TV tv;
public Actor(TV tv) { this.tv = tv; }
@Override public void run() { for (int i = 0; i < 10; i++) { if (i % 2 == 0) { this.tv.act("dance"); } else { this.tv.act("sing"); } } } }
class Watcher extends Thread { TV tv;
public Watcher(TV tv) { this.tv = tv; }
@Override public void run() { for (int i = 0; i < 10; i++) { this.tv.watch(); } } }
class TV { String show; boolean flag = true;
public synchronized void act(String show) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Act " + show); this.notifyAll(); this.show = show; this.flag = !this.flag; }
public synchronized void watch() { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Watch " + show); this.notifyAll(); this.flag = !this.flag; } }
|