多线程
ProcessThread
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,当然一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
- 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很多,就有了同时执行的错觉。
核心概念
- 线程就是独立的执行路径。
- 在程序运行时,即使没有自己创建的线程,后台也会有多个线程,如主线程,gc线程。
- main()称之为主线程,为系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器(cpu)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
//创建线程的方法一:继承Thread类,重写run方法,调度start方法开启线程;
//方法二:实现Runable接口,重写run方法,创建Thread类,引用Runable实现类,再调度start方法。
//注意:线程开启不一定立即执行,由cpu调度执行。
public class TestThread extends Thread {
//run方法线程主体
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println("我在学习多线程-----"+i);
}
}
//main线程,主线程
public static void main(String[] args) {
//创建一个线程对象
TestThread thread = new TestThread();
//调用start方法,开启子线程。
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习----"+i);
}
//主线程和子线程交替执行
}
}
使用多线程同步下载图片
//练习Thread,使用多线程同步下载图片
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() {
fileDown file = new fileDown();
file.downloader(url,name);
System.out.println("下载了文件名为"+name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("http://cdn.xiaxiang.tech/image/blogs/threeJS/case/camera/%E7%94%B5%E5%BD%B1%E7%9B%B8%E6%9C%BA%E7%84%A6%E7%82%B91.png","真难看.jpg");
TestThread2 t2 = new TestThread2("https://p0.ssl.qhimgs1.com/dmfd/296_196_/t01e84107a259d53f20.jpg", "一般.jpg");
t1.start();
t2.start();
}
class fileDown{
//下载图片的方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Lamda表达式
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么必须用代码块包裹。
- 使用Lambda的前提时接口为函数式接口。
- 多个参数也可以去掉参数类型,要么都去掉,要么都加上参数类型,必须加上括号。
为什么要使用Lambda表达式
- 避免匿名内部类定义过多
- 可以让你的代码看起来更简洁
- 去掉了一个没有意义的代码,只留下核心的逻辑
函数式接口
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
- 对于函数式接口,我们可以通过Lambda表达式创建该接口的对象。
线程状态
线程停止
- 线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
public class TestStop implements Runnable {
private boolean flag=true;
public void run() {
int i=0;
while(flag){
System.out.println("run在执行----"+i++);
}
}
//利用flag为false,停止线程
public void toFalse(){
this.flag=false;
}
public static void main(String[] args) {
TestStop stop = new TestStop();
Thread thread = new Thread(stop);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main方法-----"+i);
if (i==900){
stop.toFalse();
System.out.println("run方法停止执行");
}
}
}
}
线程休眠 sleep()
-
sleep(时间)指定当前线程阻塞的毫秒数,存在InterruptedException异常。sleep时间达到后线程就绪状态。
-
每一个对象都有一个锁,sleep不会释放锁。
-
放大问题的发生性。
public class TestSleep implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//线程延迟
/* TestSleep s = new TestSleep();
Thread thread = new Thread(s);
thread.start();*/
//获取当前时间延迟
Date date = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy-HH-mm hh:mm:ss");
while (true){
Thread.sleep(1000);
System.out.println(f.format(date));
date = new Date();
}
}
}
线程礼让 yield()
- 礼让线程,让当前正在执行的线程暂停,但不阻塞。
- 将线程从运行状态转为就绪状态。
- 让cpu重新调度,礼让不一定成功!看cpu心情。
public class TestYield implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName()+"在执行线程");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"结束线程了");
}
public static void main(String[] args) {
TestYield y = new TestYield();
new Thread(y,"小明").start();
new Thread(y,"小红").start();
}
}
线程强制执行 join()
- join合并线程,待此线程执行完成后,再执行其他线程。在此线程执行过程中,其他线程阻塞。(可以想象成插队)
public class TestJoin implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我在插队"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin join = new TestJoin();
Thread thread = new Thread(join);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main在执行"+i);
if (i==59){
thread.join();
}
}
}
}
线程状态观测 getState()
- 线程状态。线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
已退出的线程处于此状态。
public static void main(String[] args) throws InterruptedException {
TestStatus testStatus = new TestStatus();
Thread thread = new Thread(testStatus);
//new线程对象时,线程的状态
Thread.State state = thread.getState();
System.out.println(state);
//start时,线程状态
thread.start();
state=thread.getState();
System.out.println(state);
while(state!=Thread.State.TERMINATED){ //只要线程不结束,就一直输出线程状态
Thread.sleep(1000);
state =thread.getState();
System.out.println(state);
}
}
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("循环完了----");
}
线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。(优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了)
- 线程默认优先级为5,优先级的范围为0~10.
- 使用以下方式获取和改变优先级:getPriority(),setPriority()
public void run() {
System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());
TestPriority priority = new TestPriority();
Thread one = new Thread(priority, "one");
Thread two = new Thread(priority, "two");
Thread three = new Thread(priority, "three");
Thread four = new Thread(priority, "four");
//注意:一般都是先设置优先级,再启动线程
one.start();
two.setPriority(1);
two.start();
three.setPriority(10);
three.start();
four.setPriority(5);
four.start();
}
守护线程
- 线程分为用户线程和守护线程。
- 虚拟机必须确保用户线程执行完毕。
- 虚拟机不用等待守护线程执行完毕。
- 如后台记录操作日志,监控内存、垃圾回收等。
- 设置为守护线程的方法: setDaemon()(默认为false表示为用户线程,true为守护线程)
线程同步
多个线程操作同一资源(并发)
- 现实生活中,我们会遇到“同一个资源,多人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决方法就是排队,一个一个来。
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源时,其他线程必须等待,使用后释放锁即可。但同时也存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁 ,会导致优先级倒置,引起性能问题。
//线程不安全案例,会导致多人买到同一张票
public class BuyTicket {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
new Thread(myTicket,"你").start();
new Thread(myTicket,"我").start();
new Thread(myTicket,"他").start();
}
static class MyTicket implements Runnable {
private int nums = 10;
private Boolean flag = true;
public void run() {
while (flag) {
if (nums<=0){
flag=false;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
}
}
}
}
//执行结果如下:
/* 我拿到了第8票
他拿到了第9票
你拿到了第10票
我拿到了第6票
你拿到了第5票
他拿到了第7票
他拿到了第4票
我拿到了第3票
你拿到了第4票
我拿到了第2票
他拿到了第1票
你拿到了第1票
我拿到了第0票*/
同步方法和同步块 synchronized
public class BuyTicket {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
new Thread(myTicket,"你").start();
new Thread(myTicket,"我").start();
new Thread(myTicket,"他").start();
}
}
class MyTicket implements Runnable {
private int nums = 10;
private Boolean flag = true;
public void run() {
while (flag) {
buy();
}
}
public synchronized void buy(){
if (nums<=0){
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
}
}
//加入synchronized修饰方法后,执行结果为:
/*
你拿到了第10票
他拿到了第9票
我拿到了第8票
他拿到了第7票
他拿到了第6票
你拿到了第5票
你拿到了第4票
他拿到了第3票
我拿到了第2票
我拿到了第1票*/
同步块:synchronized(obj){}
- obj称之为 同步监视器
- obj可以时任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是class
public class UnsafeList{
public static void main(String[] args) {
listtwo listtwo = new listtwo();
for (int i = 0; i < 1000; i++) {
new Thread(listtwo).start();
}
}
}
class listtwo implements Runnable{
List<String> list=new ArrayList<String>();
public void run() {
synchronized (list){
list.add(Thread.currentThread().getName());
System.out.println(list.size());
}
}
}
死锁
概念:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生死锁的问题。
- 产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。
//造成死锁的案例
public class deadLock {
public static void main(String[] args) {
hua hua = new hua(0, "小红");
hua hua1 = new hua(1, "小花");
new Thread(hua).start();
new Thread(hua1).start();
}
}
//口红
class kouHong{ }
//镜子
class jiZhi{}
//化妆
class hua implements Runnable {
static kouHong kouHong = new kouHong();
static jiZhi jiZhi = new jiZhi();
private int choose;
private String name;
public hua(int choose, String name) {
this.choose = choose;
this.name = name;
}
public void run() {
if (choose == 0) {
synchronized (kouHong) {
System.out.println(this.name + "在使用口红");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (jiZhi) {
System.out.println(this.name + "在使用镜子");
}
}
} else {
synchronized (jiZhi) {
System.out.println(this.name + "在使用镜子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (kouHong) {
System.out.println(this.name + "在使用口红");
}
}
}
}
}
Lock锁
- Lock时显式锁(手动开启和关闭锁,lock()、unlock()方法)synchronized式隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchrinized有代码块和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
线程协作、通信
应用场景:生产者和消费者问题
- 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费着取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,指导仓库中再次放入产品为止。
分析
这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产产品之间,要通知消费者等待,而生产之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品,以供消费。
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步。
- synchronized不能用来实现不同线程之间的消息传递(通信)。
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,wait会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象所有调用wait方法的线程,优先级别高的线程优先调度 |
//管程法实现线程通信
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Protucer protucer = new Protucer(synContainer);
Consumer consumer = new Consumer(synContainer);
new Thread(protucer).start();
new Thread(consumer).start();
}
}
//生产者
class Protucer implements Runnable{
SynContainer synContainer;
public Protucer(SynContainer synContainer){
this.synContainer=synContainer;
}
public void run() {
for (int i = 0; i < 100; i++) {
synContainer.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer implements Runnable{
SynContainer synContainer;
public Consumer(SynContainer synContainer){
this.synContainer=synContainer;
}
public void run() {
try {
//开始等待一段时间,等待生产者产到10只鸡
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+synContainer.pop().id+"鸡");
}
}
}
//鸡
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;
count++;
//可以通知消费者消费了
this.notifyAll();
}
public synchronized Chicken pop(){
if(count==0){
//通知生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Chicken chicken=chickens[count];
this.notifyAll();
return chicken;
}
}
//信号灯法
public class TestTV {
public static void main(String[] args) {
Tv tv = new Tv();
player player = new player(tv);
watcher watcher = new watcher(tv);
new Thread(player).start();
new Thread(watcher).start();
}
}
//演员
class player implements Runnable{
Tv tv;
public player(Tv tv){
this.tv=tv;
}
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("快乐大本营");
}else {
this.tv.play("还珠格格");
}
}
}
}
//观众
class watcher implements Runnable{
Tv tv;
public watcher(Tv tv){
this.tv=tv;
}
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//电视台
class Tv{
String voice;
Boolean flag=true;
//演员表演节目
public synchronized void play(String voice){
while(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.voice=voice;
System.out.println("演员表演了"+this.voice);
this.notifyAll();
this.flag=!this.flag;
}
//观看节目
public synchronized void watch(){
while (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了"+this.voice);
this.notifyAll();
this.flag=!this.flag;
}
}
线程池
- 背景:经常创建和销毁,使用量特别大,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现了重复利用。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低了资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于管理:
- corePoolSize: 核心池的大小
- maxiumPoolSize 最大线程数
- keepAliveTime 线程没有任务时最多保持多长时间后会终止。
//线程池的使用
public class Execute {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(10);//线程池的大小
MyExecute myExecute = new MyExecute();
service.execute(new Thread(myExecute));
service.execute(new Thread(myExecute));
service.execute(new Thread(myExecute));
//线程池关闭
service.shutdown();
}
}
class MyExecute implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName());
}
}