多线程入门

目录
1.线程概述
1.什么是进程
2.什么是线程
3.什么是程序
4.并行和并发
2.线程入门
1.多线程创建方式1
2.多线程创建方式2
3.多线程创建方式3(了解)
3.线程状态
4.线程API
1.线程停止
2.线程睡眠
3.线程礼让
4.插入线程
5.线程检测状态
6.线程优先级
7.守护线程
5.什么是线程安全
1.没有线程同步
2.有线程同步
同步代码块
同步方法
7.死锁
1.什么是死锁
2.解决方法
8.Lock(锁)
9.同步练习
1.线程不安全
2.线程安全的
10.线程通信
11.生产者和消费者问题
12.JDK5.0 新增线程创建方式

  1. 新增方式一:实现Callable接口
    2.新增方式二:使用线程池

1.线程概述
1.什么是进程
是程序的一次执行过程,或是正在运行的一个程序,是一个动态过程,有他自身的产生,纯在和消亡的过程

2.什么是线程
进程可进一步细化为线程,是一个程序内部的一条执行路径

3.什么是程序
是为完成特定的任务,用某种语言编写的一组指令的集合,即使一段静态的代码,静态对象

4.并行和并发
并行:多个CPU同时执行多个任务,比如多个人同时做不同的事(一遍吃饭一遍完手机)

并发:同时执行多个任务,比如秒杀平台,多个人做一件事

2.线程入门
1.多线程创建方式1
package com.xjggb.thread;

public class Test01 {
public static void main(String[] args) {

Threadl threadl1 = new Threadl(“A”);
Threadl threadl2 = new Threadl(“B”);
threadl1.start();
threadl2.start();


}
}

/*

  • 方式一
  • 步骤
  • 继承Thread类
  • 重写run方法
  • 创建对象调用start方法
  • */
    class Threadl extends Thread {

    private String name;

    public Threadl(String name) {
    this.name = name;
    }

    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    System.out.println(“运行” + i);
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    }

    }
    }
    运行结果

运行0
运行0
运行1
运行2
运行3
运行4
运行1
运行2
运行3
运行4

小结

开发步骤

继承Thread类

重写run方法

创建子类对象调用start

注意:start()方法调用后并不是立即执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行由操作系统决定

创建线程下载图片

添加依赖

<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>



代码如下

package com.xjggb.entity;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//图片下载
public 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异常”);
}

}

}

//创建线程
class TestDemo extends Thread{

private String url; //网络图片地址
private String name; // 保存的文件名

public TestDemo(String url ,String name){

this.name=name;
this.url=url;
}

@Override
public void run() {

WebDownLoader s = new WebDownLoader();
s.downLoader(url,name);
System.out.println(“已经下载的文件名称为”+name+“图片” );
}
}

class Test{
public static void main(String[] args) {
TestDemo testDemo1 = new TestDemo(“https://img-blog.csdnimg.cn/20210210194321356.png”,“我的博客图片01.png”);
TestDemo testDemo2 = new TestDemo(“https://img-blog.csdnimg.cn/20210228094648502.png”,“我的博客图片02.png”);
TestDemo testDemo3 = new TestDemo(“https://img-blog.csdnimg.cn/20210303104522231.png”,“我的博客图片03.png”);
//调用start()方法
testDemo1.start();
testDemo2.start();
testDemo3.start();

}
}



2.多线程创建方式2
开发步骤

创建类实现Runnable接口重写run方法

创建接口子类,创建线程对象子类放入线程对象的构造方法中

调用start方法

package com.xjggb.thread;


public class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(“我在秀代码” + i);
}
}

public static void main(String[] args) {

//创建线程接口实现对象
TestRunnable testRunnable = new TestRunnable();
//床架线程对象,通过线程对象开启线程

Thread thread = new Thread(testRunnable);
//掉用start方法
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(“我是主线程” + i);
}


}
}





小结

实现Runnable接口

创建子线程传入到线程对象的构造方法中

调用start()方法

推荐使用,避免单继承灵活方便同一对象被多线程调用

3.多线程创建方式3(了解)
现实步骤

实现Callable接口,需要返回值类型

重写call()方法,需要抛出异常

创建目标对象

创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1);

提交执行:Future result1 = ser.submit(t1);

获取结果:boolean r1 = result1.get();

关闭服务:ser.shutdownNow();

package com.xjggb.thread;


import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {

//创建线程
TestCallable testCallable1 = new TestCallable(“https://img-blog.csdnimg.cn/20210210194321356.png”, “我的博客图片01.png”);
TestCallable testCallable2 = new TestCallable(“https://img-blog.csdnimg.cn/20210228094648502.png”, “我的博客图片02.png”);
TestCallable testCallable3 = new TestCallable(“https://img-blog.csdnimg.cn/20210303104522231.png”, “我的博客图片03.png”);

//创建执行任务
ExecutorService ser = Executors.newFixedThreadPool(3);
Future submit1 = ser.submit(testCallable1);
Future submit2 = ser.submit(testCallable2);
Future submit3 = ser.submit(testCallable3);

//获取结果
Boolean aBoolean1 = submit1.get();
Boolean aBoolean2 = submit2.get();
Boolean aBoolean3 = submit3.get();

//关闭服务
ser.shutdown();

}

}

class TestCallable implements Callable {
private String url; //网络图片地址
private String name; // 保存的文件名

public TestCallable(String url, String name) {
this.name = name;
this.url = url;
}


@Override
public Boolean call() throws Exception {

WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url, name);
System.out.println(“下载的文件名称为” + name + “的图片”);
return true;
}
}


//下载器
//图片下载
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异常”);
}

}

}

3.线程状态
在这里插入图片描述

新建状态(New):新新建一个线程对象

就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的starta()方法,该状态的线程位于可运行线程池中,变得可运行等待获取CPU的使用权

运行状态(Running)就绪状态的线程获取了CPU,执行程序代码

阻塞状态(Blocked):阻塞状态线程因为某种原因放弃CPU使用权, 暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

小结:

线程状态

创建

就绪

运行

阻塞

死亡

4.线程API
Thread.currentThread().getName() //获取当前线程的名字

1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

1.线程停止
测试Stop

1.建议线程正常停止—>利用次数,不建议死循环.

2.建议使用标志位---->设置一个标志位

3.不要使用stop和destroy 等过时或者jdk不建议使用的方法.

package com.xjggb.thread;


//建议线程正常停止:利用此时,不建议死循环
//建议使用标记位
public class TestStop implements Runnable {

// 1.设置一个标志位
private boolean flag = true;

@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run…Thread: " + i++);
}
}

//2.设置公开的方法停止线程
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 < 30; i++) {
System.out.println("main " +i);
if( i == 20){
//调用stop方法切换线程标志位,让线程停止
testStop.stop();
System.out.println(“线程已停止”);
}
}
}
}





2.线程睡眠
每个对象都有一个锁,sleep不会释放锁

sleep(时间)指定当前线程阻塞的毫秒数

sleep可以模拟网络延迟,倒计时等

每个对象都有一个锁,seep不会释放锁

模拟倒计时

package com.xjggb.thread;

public class Test04 {
public static void main(String[] args) {
try {
show(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模拟倒计时
public static void show(int add) throws InterruptedException {

while (true) {

Thread.sleep(1000);

System.out.println("add = " + add–);
if (add < 0) {
break; //结束循环
}

}

}
}

打印当前系统时间

package com.xjggb.thread;

public class Test04 {
public static void main(String[] args) {
try {
show(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//打印当前系统时间
public static void show() throws InterruptedException {
Date date = new Date(System.currentTimeMillis()); //获取系统当前的时间

int i=10;

while (true){
Thread.sleep(1000);
String format = new SimpleDateFormat(“HH:mm:ss”).format(date);
System.out.println(“format = " + format);

//获取当前最新的时间
date = new Date(System.currentTimeMillis());
–i;
if (i<0){
System.out.println(” = " +i);
break;
}

}



}
}

3.线程礼让
礼让线程,让当前正在执行的线程暂停,单不堵塞

将线程从运行状态转为就绪状态

让CPU重新调度,礼让不一定成功,看CPU

代码如下:

package com.xjggb.thread;

public class ThreadYield {
public static void main(String[] args) {
//创建线程子对象
TestYield testYield = new TestYield();
//创建线程对象
new Thread(testYield,“线程A”).start();
new Thread(testYield,“线程B”).start();

}

}


class TestYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + “线程开启”);
//线程礼让
Thread.yield();
System.out.println(Thread.currentThread().getName() + “线程开启”);

    /* 礼让成功  礼让不成功就多执行两次
    *   线程A线程开启
        线程B线程开启
        线程A线程开启
        线程B线程开启
    * */


}
}

4.插入线程
join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执

代码如下

package com.xjggb.thread;

public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(“run+我是VIP都滚开” + i);
}

}

public static void main(String[] args) {

//启动线程
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);


//主线程

for (int i = 0; i < 500; i++) {
System.out.println(“我是Main方法” + i);

if (i == 300) {
//启动VIP线程
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

}
/*
* 礼让显示
* 我是Main方法297
我是Main方法298
我是Main方法299
我是Main方法300
run+我是VIP都滚开0
run+我是VIP都滚开1
run+我是VIP都滚开2
run+我是VIP都滚开3
run+我是VIP都滚开4
run+我是VIP都滚开5
run+我是VIP都滚开6
run+我是VIP都滚开7
* */

}


}

}

5.线程检测状态
线程状态。线程可以处于一下状态

NEW

尚未启动的线程的线程状态

代码如下

package com.xjggb.thread;

public class Thst05 {
public static void main(String[] args) {

//创建线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

//观察状态
Thread.State state1 = thread.getState();
System.out.println("state1 = " + state1);


//启动后观察状态
thread.start();

state1 = thread.getState();
System.out.println("state2 = " + state1);


//只要线程不终止,就会一直输出状态
while (state1 != Thread.State.TERMINATED) {
try {
Thread.sleep(200);
//更新线程状态
state1 = thread.getState();
System.out.println("state1 = " + state1);

} catch (InterruptedException e) {
e.printStackTrace();
}
}

/*
* state1 = NEW
state2 = RUNNABLE
state1 = RUNNABLE
state1 = RUNNABLE
state1 = TERMINATED
* */

}
}

6.线程优先级
线程优先级用数字表示,范围从1~10

Thread.MIN_PRIORITY = 1

Thread.NORM_PRIORITY = 5

Thread.MAX_PRIORITY = 10

注意先设计优先级在启动

代码如下:

package com.xjggb.thread;

public class ThreadPriority {
public static void main(String[] args) {

System.out.println(" = " + Thread.currentThread().getName());

Mypriority mypriority = new Mypriority();
Thread thread1 = new Thread(mypriority);
Thread thread2 = new Thread(mypriority);
Thread thread3 = new Thread(mypriority);
Thread thread4 = new Thread(mypriority);
Thread thread5 = new Thread(mypriority);
Thread thread6 = new Thread(mypriority);

thread1.start();

//设置优先级
thread2.setPriority(3);
thread2.start();

thread3.setPriority(5);
thread3.start();

thread4.setPriority(7);
thread4.start();

thread5.setPriority(Thread.MAX_PRIORITY);
thread5.start();

thread6.setPriority(Thread.NORM_PRIORITY);
thread6.start();


/* 运行结果
* = main
Thread-3—>7
Thread-4—>10
Thread-0—>5
Thread-2—>5
Thread-5—>5
Thread-1—>3
* */

}
}

class Mypriority implements Runnable {

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + “—>” + Thread.currentThread().getPriority());
}
}


7.守护线程
线程分为用户线程和守护线程

虚拟机必须确保线程执行完毕

虚拟机不用等待守护线程执行完毕

如后台记录操作日志,监控内存,垃圾回收等

package com.xjggb.thread;

public class Test06 {

public static void main(String[] args) {

//创建上帝
Gog gog = new Gog();
//创建你
You you = new You();
//创建线程对象
Thread thread = new Thread(gog); //使用上帝作为守护线程
//线程守护默认是false表示用户线程,正常的线程就是用户线程
thread.setDaemon(true);
thread.start();

//you启动
new Thread(you).start();

}
}
class Gog implements Runnable{
@Override
public void run() {

while (true){
System.out.println(“上帝守护你”);
}
}
}


class You implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(“我还开开心心的活着” + i+“岁”);
}
System.out.println(“任务结束了”);

}
}
/*
* 当主线程退出时,JVM会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事
*
* 上帝守护你
上帝守护你
我还开开心心的活着0岁
我还开开心心的活着1岁
我还开开心心的活着2岁
我还开开心心的活着3岁
我还开开心心的活着4岁
我还开开心心的活着5岁
我还开开心心的活着6岁
我还开开心心的活着7岁
我还开开心心的活着8岁
我还开开心心的活着9岁
任务结束了
上帝守护你
上帝守护你
* */

小结:

如果 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。换句话说,守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。

5.什么是线程安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

1.没有线程同步
package com.xjggb.security;

public class Test02 {

public static void main(String[] args) {

//创建子线程实现列
WindowRunnable windowRunnable = new WindowRunnable(100);

//创建线程对象
Thread thread1 = new Thread(windowRunnable);
Thread thread2 = new Thread(windowRunnable);
Thread thread3 = new Thread(windowRunnable);

thread1.start();
thread2.start();
thread3.start();

        /*
    * Thread-0售出票号100
      Thread-2售出票号100
      Thread-2售出票号99
      Thread-1售出票号100
    * */


}

}

class WindowRunnable implements Runnable{

//定义票数
private int ticket;

public WindowRunnable(int ticket) {
this.ticket = ticket;
}

@Override
public void run() {

while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName() +“售出票号”+ ticket);
ticket–;
}else {
break;
}
}

}

}
由于线程被CPU调度的随机性,多次运行结果不一致,但基本一致可以看出,多个窗口在迸发进行销售,而且有

错票,重要 ,漏票问题

2.有线程同步
同步代码块
synchronized(同步监视器){

需要被同步的代码

}

操作共享数据的代码,需要被同步的代码

共享数据,多个线程共同操作的变量,比如:ticket九四共享数据

同步监视器,俗称锁,任何一个类对象,都可以充当锁

package com.xjggb.security;

public class Test02 {

public static void main(String[] args) {

//创建子线程实现列
WindowRunnable windowRunnable = new WindowRunnable(100);

//创建线程对象
Thread thread1 = new Thread(windowRunnable);
Thread thread2 = new Thread(windowRunnable);
Thread thread3 = new Thread(windowRunnable);

thread1.start();
thread2.start();
thread3.start();

/*
* Thread-0售出票号100
Thread-2售出票号100
Thread-2售出票号99
Thread-1售出票号100
* */

}

}

class WindowRunnable implements Runnable{

//定义票数
private int ticket;

public WindowRunnable(int ticket) {
this.ticket = ticket;
}

@Override
public void run() {

synchronized (WindowRunnable.class){ //同步代码块
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName() +“售出票号”+ ticket);
ticket–;
}else {
break;
}
}
}


}
}
同步方法
package com.xjggb.security;

public class Test04 {
public static void main(String[] args) {
//创建线程
Window2 window2 = new Window2();

//创建线程
Thread thread1 = new Thread(window2,“线程1”);
Thread thread2 = new Thread(window2,“线程2”);
Thread thread3 = new Thread(window2,“线程3”);

//启动线程
thread1.start();
thread2.start();
thread3.start();

}
}

class Window2 implements Runnable{
//创建票数
private int ticket=100;
@Override
public void run() {
while (true){

show();
}
}


//同步方法
private synchronized void show(){
if (ticket>0){

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket–;

}

}


}

7.死锁
1.什么是死锁
不同的线程分别抢占用对方需要的同步资源不放弃,都在等待对方放弃,自己需要的同步志愿,就形成了线程死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

代码如下:

package com.xjggb.security;

public class Test05 {

public static void main(String[] args) {

StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();

//创建线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s1) {
s1.append(“a”);
s2.append(“1”);

//睡眠两秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在嵌套一个线程安全
synchronized (s2) {


s1.append(“b”);
s2.append(“2”);

System.out.println("s2 = " + s2);
System.out.println("s1 = " + s1);
}
}

}
}).start();

//在创建一个线程

new Thread(() -> {
synchronized (s2) {
s1.append(“o”);
s2.append(“7”);
//睡眠两秒
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在嵌套一个线程安全
synchronized (s1) {


s1.append(“p”);
s2.append(“8”);


System.out.println("s2 = " + s2);
System.out.println("s1 = " + s1);
}
}

}).start();


}

}

小结:

程序不会结束,也无法执行下去

2.解决方法
专门的算法,原则

尽量减少同步资源的定义

尽量避免嵌套同步

8.Lock(锁)
java提供了更强大的线程同步机制–通过显示定义同步代锁对象来实现同步,同步锁使用Lock锁对象充当

Lock接口是控制多个线程对共享资源进行访问的工具,

代码如下:

package com.xjggb.security;


import java.util.concurrent.locks.ReentrantLock;

public class Test06 {
public static void main(String[] args) {

/*
* 面试题 synchronized与Lock的异同
* 相同: 二者都可以解决线程安全问题
* 不同: synchronized 执行完同步代码会自动释放锁
* lock需要手动的启动同步(lock()) 同时结束同步也需要手动的实现
* */
//创建子线程
TestLock testLock = new TestLock();

Thread thread1 = new Thread(testLock);
Thread thread2 = new Thread(testLock);
Thread thread3 = new Thread(testLock);
thread1.start();
thread2.start();
thread3.start();


}


}

class TestLock implements Runnable {

private int ticket = 100; //定义票数
private ReentrantLock lock = new ReentrantLock(); //实例化ReentrantLock


@Override
public void run() {

try {
lock.lock(); //开启锁
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + “:” + ticket);
ticket–;

} else {
break;
}
}
} finally {
lock.unlock(); //释放锁
}

}
}

9.同步练习
1.线程不安全
package com.xjggb.account;

public class Test01 {
public static void main(String[] args) {

Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);

customer1.setName(“甲”);
customer2.setName(“乙”);
customer3.setName(“丙”);

//启动线程
customer1.start();
customer2.start();
customer3.start();

         /*
    *  代码运行结果直接出现线程安全问题
    * 存钱成功,余额为2000.0
      存钱成功,余额为2000.0
      存钱成功,余额为2000.0
      存钱成功,余额为4000.0
      存钱成功,余额为5000.0
      存钱成功,余额为4000.0
      存钱成功,余额为8000.0
      存钱成功,余额为8000.0
      存钱成功,余额为8000.0
    * */



}
}

//多线程用户存钱
class Customer extends Thread {

private Account account;

//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}

@Override
public void run() {

for (int i = 0; i < 3; i++) {

account.setMoney(1000);
}

}
}


//创建账户类
class Account {

private double money;

public Account() {
}

public Account(double money) {
this.money = money;
}

/**
* 获取
* @return money
/
public double getMoney() {
return money;
}

/
*
* 设置
* @param
*/
public void setMoney(double mone) {
if (mone>0){

//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题

System.out.println(“存钱成功,余额为” + money);
}
}

}

2.线程安全的
三种方式

synchronized方法

synchronized代码块

lock锁

三种方式都行

使用synchronized方法

package com.xjggb.account;

public class Test01 {
public static void main(String[] args) {

Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);

customer1.setName(“甲”);
customer2.setName(“乙”);
customer3.setName(“丙”);

//启动线程
customer1.start();
customer2.start();
customer3.start();


/*
保证线程安全
* 存钱成功,余额为1000.0
存钱成功,余额为2000.0
存钱成功,余额为3000.0
存钱成功,余额为4000.0
存钱成功,余额为5000.0
存钱成功,余额为6000.0
存钱成功,余额为7000.0
存钱成功,余额为8000.0
存钱成功,余额为9000.0
* /


}
}

//多线程用户存钱
class Customer extends Thread {

private Account account;

//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}

@Override
public void run() {

for (int i = 0; i < 3; i++) {

account.setMoney(1000);
}

}
}


//创建账户类
class Account {

private double money;

public Account() {
}

public Account(double money) {
this.money = money;
}

/

* 获取
* @return money
/
public double getMoney() {
return money;
}

/
*
* 设置
* @param
*/
public synchronized void setMoney(double mone) {
if (mone>0){

//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,添加synchronized 就像队列一样排队一个一个来

System.out.println(“存钱成功,余额为” + money);
}
}

}

10.线程通信
涉及到三个方法:

wait():一旦此执行此方法,当前线程就进入阻塞状态,并释放同步监视器

notify():一旦执行

代码如下:

package com.xjggb.account;

public class Test01 {
public static void main(String[] args) {

Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);

customer1.setName(“甲”);
customer2.setName(“乙”);
customer3.setName(“丙”);

//启动线程
customer1.start();
customer2.start();
customer3.start();


/*
*
* 存钱成功,余额为1000.0
存钱成功,余额为2000.0
存钱成功,余额为3000.0
存钱成功,余额为4000.0
存钱成功,余额为5000.0
存钱成功,余额为6000.0
存钱成功,余额为7000.0
存钱成功,余额为8000.0
存钱成功,余额为9000.0
* /


}
}

//多线程用户存钱
class Customer extends Thread {

private Account account;

//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}

@Override
public void run() {

for (int i = 0; i < 3; i++) {

account.setMoney(1000);
}

}
}


//创建账户类
class Account {

private double money;

public Account() {
}

public Account(double money) {
this.money = money;
}

/
*
* 获取
* @return money
/
public double getMoney() {
return money;
}

/
*
* 设置
* @param
*/
public synchronized void setMoney(double mone) {
if (mone>0){

//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题

System.out.println(“存钱成功,余额为” + money);
}
}

}

小结:

wait(),notify(),notifyAll() 三个方法必须使用在同步方法或者代码块中

wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器

否则就会出现异常

面试题:

sleep()和wait()异同?

相同点: 一旦执行方法就会使得当前的线程进入阻塞状态

不同点:

两个方法的声明位置不同:Thread类中声明sleep(),Object类中声明wait()

调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码中

关于是否释放同步监视器(锁)如果两个方法都使用在同步代码块或者同步方法中,sleep()方法不会释放锁wait()会自动释放锁

11.生产者和消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。

代码如下

package com.xjggb.security;

public class Test07 {
public static void main(String[] args) {
Clerk clerk = new Clerk(); //创建店员
Productor productor = new Productor(clerk); //创建生产者
productor.setName(“我是生产者”);
Customer customer = new Customer(clerk); //创建生产者
customer.setName(“我是消费者”);

//启动线程
productor.start();
customer.start();


}
}

//生产者
class Productor extends Thread {
private Clerk clerk;

//创建构造函数
public Productor(Clerk clerk) {
this.clerk = clerk;
}


@Override
public void run() {
System.out.println(“生产者开始生产产品”);
while (true) {
//睡一秒中
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开始生产产品
clerk.addProduct();

}

}
}

//消费者
class Customer extends Thread {

private Clerk clerk;

//创建构造函数
public Customer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
while (true) {
//睡一秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

//开始消费产品
clerk.getProduct();
}

}
}

//店员
class Clerk {

private int product = 0; //定义产品数量

public synchronized void addProduct() { //生产产品
if (product < 20) {
product++;
System.out.println(Thread.currentThread().getName() + “生产了第” + product + “个”);
notifyAll(); //唤醒
} else {
try {
wait(); // 阻塞线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

public synchronized void getProduct() { //消费产品
if (product > 0) {
System.out.println(Thread.currentThread().getName() + “取走了第” + product + “个”);
product–;
notifyAll();
} else {
try {
wait(); // 阻塞线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}


}



小结:

分析是否是多线程问题?是,生产者和消费者

分析是否有共享数据,是,店员 (或者产品)

如何结局线程的安全问题?同步代码块,同步方法,和lock锁等

是否涉及线程通信

12.JDK5.0 新增线程创建方式

  1. 新增方式一:实现Callable接口
    与Runnable相比Callable功能更加强大

相比于run()方法,call()可以有返回值

方法可以抛出异常,被外面的操作捕获

支持范型的返回值

需要借助FutureTask类,比如获取返回结果

 Future接口

 可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。

 FutrueTask是Futrue接口的唯一的实现类

 FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

代码如下:

package com.xjggb.account;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test03 {
public static void main(String[] args) {

//创建子类线程
Thread1 thread1 = new Thread1();

//将实现类对象作为参数传递待FutureTask的构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(thread1);

//将FutureTask的对象传到Thread的构造器中,创建Thread类的对象并start()
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//获取返回值
try {
System.out.println(“偶数总和” + futureTask.get());

} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}



class Thread1 implements Callable {
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {

if (i%2==0){ //获取模于2等于零的累加
sum+=i;
}
}

return sum;
}
}

2.新增方式二:使用线程池
 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

 好处:

 提高响应速度(减少了创建新线程的时间)

 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

 便于线程管理  corePoolSize:核心池的大小  maximumPoolSize:最大线程数

 keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程API

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

 ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

     void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable 

      Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable

 void shutdown() :关闭连 接池  Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池  Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池  Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池  Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池  Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。

创建线程池

  //Executors工具类、线程池的工厂类,用法语创建并返回不同类型的线程池
    //1、创建一个可根据需要创建性线程的线程池
    Executors.newCachedThreadPool();


//2、创建一个可重用固定线程书的线程池
Executors.newFixedThreadPool(n);


//3、创建一个只有一个线程的线程池
Executors.newSingleThreadExecutor();

//4、创建一个线程池,它可安排再给定延迟后运行命令或者定期地执行
Executors.newScheduledThreadPool(n);

package com.xjggb.account;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test04 {

public static void main(String[] args) {
//创建指定的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//创建线程的实现对象
PoolTest poolTest = new PoolTest();
executorService.execute(poolTest);



}
}

class PoolTest implements Runnable{
@Override
public void run() {

int sum=0;
for (int i = 0; i < 100; i++) {
if (i%2==0){
sum+=i;
}
}
System.out.println("sum = " + sum);

}
}

Callable

service.submit(new FutureTask(new PoolThread1()));
关闭连接池

executorService.shutdown();
设置连接池属性

//类型强转
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
service1.setKeepAliveTime();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值