多线程
idea的一些设置
bin 目录下 idea64.ex3.vmoptions
-Xms128m //初始的内存值,增加该值可以提高Java程序的启动速度 16G内存的机器可尝试设置为Xms512m
-Xmx750m //设置最大内存数,提高改值,可以减少内存Garage收集的频率,提高程序的性能
-XX:ReservedCodeCacheSize=512m //保存的代码缓存512m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-ea
-Dsun.io.useCanonCaches=false
-Djdk.http.auth.tunneling.disabledSchemes=""
-Djdk.attach.allowAttachSelf=true
-Djdk.module.illegalAccess.silent=true
-Dkotlinx.coroutines.debug=off
基本概念:程序、进程、线程
**程序:**是完成特定任务、用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象.
**进程:**是程序的一次执行过程,或是正在运行的一个程序.是一个动态的过程:有它自身的产生、存在和消亡的过程.—生命周期
>如:运行中的QQ:运行中的MP3播放器
>程序是静态的,进程是动态的.
>进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存去月
**线程:**进程可进一步细化为线程,是一个程序内部的一条执行路径.
>若一个进程同一时间并行执行多个线程,就是支持多线程的.
>线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
>一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象.这就使得线程间通信更简便、高效.但多个线程操作共享的系统资源可能就会带来安全的隐患
- 细节:
- 类先加载进内存,如果是有两个线程,那么每个线程有自己独立的虚拟机栈和程序计数器
- 方法区和堆是一个进程共享的,里面每个线程都共用一个
单核和多核CPU的理解
1.单核CPU,其实是一个假的多线程,因为在一个时间单元内,也只能执行一个线程的任务.例如:虽然有多条车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员.如果有某个人不想交钱,那么收费人员可以把它"挂起",(晾着他,等他想通了,准备好了前,再去收费).但是因为CPU时间单元特别短,因此感觉不出来.
2.如果是多核的话,才能更好的发挥多线程的效率.(现在的服务器都是多核的)
3.一个Java引用程序java.exe,其实至少有三个线程n:main()主线程,gc垃圾回收线程,异常处理线程.当然如果发生异常,会影响主线程.
并行与并发
并行:多个CPU同时执行多个任务.比如多个人同时做不同的事
并发:一个CPU(采用时间片)同时(看似是同时)执行多个任务.比如:秒杀、多个人做同一件事
多线程的优点:
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比使用多个线程来完成用的时间更短,为何还需多线程.
多线程的优点:
- 提高应用程序的响应.对图形化界面更有意义,可增强用户体验.
- 提高计算机系统CPU的使用率
- 改善程序的结构,将即长又复杂的进程分为多个线程,独立运行,利于理解和修改.
何时需要多个线程
- 程序需要执行两个或多个任务.
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等
- 需要一些后台运行的程序时
线程的创建和使用
- Java语言的JVM允许运行多个线程,它通过java.lang.Thread类来体现.
- Thread类的特性
1.每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体.
2.通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
Thread类有关方法
-
void start():启动线程、并执行对象的run()方法
-
run():线程在被调度时执行的操作
-
String getName(): 返回当前线程的名称
-
void setName(String name):设置当前线程名称
-
static Thread currentThread():返回当前线程.在Thread子类中就是this,通常用于主线程和 Runnable实现类
-
static void yield():线程让步 释放当前cpu的执行权,有可能再下一刻又分给他
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 但是不意味这低优先级或自己抢不到,只是概率大小问题而已
若队列中没有同优先级的线程,忽略此方法
-
join():当某个程序在执行流中调用其他线程的join()方法时,调用线程被阻塞,直到join()方法加入的线程执行完为止.举例:在线程a中调用b中的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a结束阻塞状态
低优先级的线程也可以获得执行
-
static void sleep(long millis):(指定时间:毫秒数) 让当前线程"睡眠"指定的millitime毫秒数,在指定是的毫秒时间内,当前线程是阻塞状态
令当前活动线程在指定时间段内放弃对CPU的控制,使其他线程有机会被执行,时间到后重排队
抛出InterruptedException异常
-
stop():强制线程生命周期结束,不推荐使用 (API中已经过时了,不建议使用) 强制结束当前线程
-
boolean isAlive():返回boolean,判断线程是否还活着
重点:看一些Thread源码,源码我认为比话语解释更好
- 空参构造器
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
- 有参构造(调用构造的时候可以传名字)
public Thread(String name) {
init(null, null, name, 0);
}
- setName
private volatile String name;
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
- getName()
public final String getName() {
return name;
}
- 测试
public class ThreadMethodTest{
public static void main(String[] args) {
HelloThread h1 = new HelloThread("Thread一");
// h1.setName("线程一");
h1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if(i==20){
try {
h1.join(); //当前主线程停止,等到h1线程执行完后主线程才接着执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(h1.isAlive());
}
}
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
try { //不能throws 异常,因为他的父类没有抛异常,所以重写的方法不能抛超过父类的异常,
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
if(i%20==0){
this.yield();
}
}
}
public HelloThread(String name) {
super(name);
}
}
线程通信:wait()/notify/notifyAll()此三个方法定义在Object类中,虽然不是线程中的,但是常用于线程的使用
线程的调度
- 调整策略
时间片:
抢占式:高优先线程抢占CPU
- 线程的优先级等级
MAX_PRIORITY:10 最大的优先级是10
MIN_PRIORITY:1 最小的优先级是1
NORM_PRIORITY:5 默认的优先级是5
- 涉及方法
getPriority():返回线程优先级
setPriority(int newPriority) :改变线程的优先级
- 说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
Thred变量的定义:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
- 查看优先级
public class ThreadTest {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//这个可以省略Thread.currentThread()
System.out.println(Thread.currentThread().getName()+":优先级"+getPriority()+" "+i);
}
}
}.start();
System.out.println(555555);
for (int i = 0; i < 50; i++) {
//这个不可以省略Thread.currentThread()
System.out.println(Thread.currentThread().getName()+":优先级"+Thread.currentThread().getPriority()+" "+i);
}
}
}
- 设置优先级的时候要在start()之前设置,否则没啥作用 (在匿名对象中没法用)
多线程的创建 方式一:继承与Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类中的run方法 ----->将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调start()方法
- test :遍历100以内的所有偶数
/*
* 多线程创建,方式一:继承于Thread类*/
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread myThread = new MyThread();
//4.通过此对象调用start ①启动当前线程②调用点前线程的run()
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello");
}
}
}
结果:
mainhello和Thread-0:偶数无规律出现,主要还是看你电脑的CPU怎么切换和切换的频率了
- 能否直接调run去执行多线程? 当然不可以,直接调用run没有创建新的线程,只是普通方法调用
/*
* 多线程创建,方式一:继承于Thread类*/
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread myThread = new MyThread();
//4.通过此对象直接调用run()
myThread.run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello");
}
}
}
结果:
按顺序 main 0 2 4... main hello...
- 问题二:能否用同一个对象再启动一个线程 (爆错:Exception in thread “main” java.lang.IllegalThreadStateException)
myThread.start();
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread myThread = new MyThread();
//4.通过此对象调用start
myThread.start();
//问题二:能否用同一个对象再启动一个线程 不可以还让已经start()的线程执行,会报异常
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello");
}
}
}
解决办法,再new个对象就行. 重新创建一个线程对象
MyThread myThread1 = new MyThread();
myThread1.start();
- 假如子类只需要一遍那么可以用匿名的写法,
public class ThreadTest {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello");
}
}
}.start();
System.out.println(555555);
}
}
- 练习:例子:创建三个窗口卖票,总票数为100张
class Window extends Thread{
static int ticket=10;
@Override
public void run() {
while (true){
if (ticket>0) {
System.out.println(getName()+":卖票"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
Window t4 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println();
}
}
结果:
窗口3:卖票10
窗口3:卖票9
窗口3:卖票8
窗口3:卖票7
窗口3:卖票6
窗口1:卖票10
窗口1:卖票4
窗口2:卖票10
窗口4:卖票10
窗口2:卖票2
窗口1:卖票3
窗口3:卖票5
窗口4:卖票1
Process finished with exit code 0
总结:
存在线程安全问题,待解决
创建多线程的方式二:实现Runnable的接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法 run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()方法
一些方法源码
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public void run() {
if (target != null) {
target.run();
}
}
- 方式二
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5.通过Thread类的对象调用start():①启动线程,②调用当前线程的run()-->调用Runnable类型的target的run
t1.start();
//在启动一个线程,遍历100以内的偶数
// t1.start(); 报错
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
比较两种方式
开发中:优先选择:实现Runnable接口的方式
原因:1.实现的方式没有类的单继承的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系: Thread本身也是实现了Runnable接口
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
目前这两种方式,要想启动线程,都是调用Thread类中的start()方法
public
class Thread implements Runnable {
补充:线程的分类
java中线程分为两类:一种是守护线程,一种是用户线程
- 他们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开.
- 守护线程是用来服务用户线程的,通过start()方法前调用thread.setDatemon(true)可以把一个用户线程变成一个守护线程
- Java垃圾回收就是一个典型的守护线程
- 若JVM中都是守护线程,当前JVM将推出
- 形象理解:兔死狗烹,鸟叫弓藏
线程的生命周期
- JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象.Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态
**新建:**当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
**就绪:**处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配CPU资源
**运行:**当就绪的线程被调用并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
**阻塞:**在某种特殊情况下,被认为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
**死亡:**线程完成了它的全部工作或线程被提前强制性中止或出现异常导致结束
线程的同步
例子:创建三个窗口卖票,总票数为100,使用实现Runnable接口的方式
-
问题:卖票过程中,出现了重票、错票 -->出现了线程安全问题吧
-
问题出错的原因: 当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
-
如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以开始操作ticket. 这种情况即使线程a出现了阻塞,也不能改变.
-
在Java中,我们通过同步机制,来解决线程的安全问题
解决线程安全问题方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码-->不能包含代码多了,也不能少了
2.共享数据:多个线程共同操作的变量. 比如:ticket就是共享数据
3.同步监视器: 俗称:锁 任何一个类的对象,都可以充当锁.
要求:多个线程必须要共用同一把锁
继承的方式实现锁
class Window extends Thread{
static int ticket=10;
static Object o = new Object(); //必须静态的
@Override
public void run() {
while (true) {
synchronized (o) { //这个不可以用this,他代表了t1,t2,t3三个对象
if (ticket > 0) {
System.out.println(getName() + ":卖票" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
System.out.println();
}
}
实现Runnable的方式实现锁
class MThread implements Runnable{
private int ticket=100;
Object o = new Object();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (o) { //这个可以写this,this是MThread的对象,这个对象只有一个,后面没有重复造 这个可以,要考虑this是不是唯一
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " " + ticket);
ticket--;
} else
break;
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
- 所里面都可以用类来充当对象 xxx.class (类也是对象 反射) 类只会加载一次
- 注意: Object o = new Object();放到run方法中就不安全了,因为锁不唯一了
解决线程安全问题方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的.
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
- 继承方法的:
/*
* 使用同步方法处理继承Thread类的方式中的线程安全问题 */
class Window4 extends Thread{
static int ticket=200;
@Override
public void run() {
while (true) {
show();
if (ticket==0) {
break;
}
}
}
//public synchronized void show(){ //同步监视器 t1,t2,t3
public static synchronized void show(){ //同步监视器 Windows4.class
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票" + ticket);
ticket--;
}
}
}
public class Windows4Test {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
System.out.println();
}
}
- 实现方法的
/*
* 使用同步方法解决实现Runnable接口的线程安全问题*/
class MThread3 implements Runnable{
private int ticket=100;
Object o = new Object();
@Override
public void run() {
while (true){
show();
if ((ticket==0)){
break;
}
}
}
private synchronized void show(){//同步监视器 this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " " + ticket);
ticket--;
}
}
}
public class Windows3Test {
public static void main(String[] args) {
MThread3 mThread = new MThread3();
Thread t1 = new Thread(mThread);
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
同步的方式:
解决了线程的安全问题 --好处
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低.—局限性
线程安全的单例模式懒汉式
- 改进1
/*
* 使用同步机制将单例模式中懒汉式改写为线程安全的*/
class Bank{
private Bank(){} 私有构造器
private static Bank instance=null;
public static Bank getInstance(){
//方式一:效率稍差
synchronized (Bank.class) {
if(instance==null){
instance=new Bank();
}
return instance;
}
}
}
public class BankTest {
}
改进2
/*
* 使用同步机制将单例模式中懒汉式改写为线程安全的*/
class Bank{
private Bank(){}
private static Bank instance=null;
public static Bank getInstance(){
//方式二:效率更高
if(instance==null){
synchronized (Bank.class) {
if(instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
public class BankTest {
}
死锁问题:
不同的线程分别占用对方需要的同步资源不放弃,都在的等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁或,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
- 解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
我们使用同步时,要避免出现死锁.
package test;
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
synchronized(s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
解决线程安全问题方式三
解决线程安全问题方式三:Lock锁 —5.0新增
/*
* 解决线程安全问题的方式三:*/
class Window implements Runnable{
private int ticket=100;
//实例化 ReentrantLock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + ticket);
ticket--;
}else break;
}finally {
//3.调用解锁的方法:unlock
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
面试题:synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步( lock() ),同时结束的时候也需要手动的实现( unlock() )
优先使用顺序:
Lock—>同步代码块—>(已经进入了方法体,分配了相应资源)–>同步方法(在方法体之外)
面试题:如何解决线程安全问题? 有几种方式
线程的通信
涉及到的三种方法:
- wait(): 一旦指向此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify(): 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
说明:
-
wait() ,notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中
-
wait() ,notify(), notifyAll()三个方法调用者必须是同步代码块或是同步方法中的同步监视器,否则会出现.IllegalMonitorStateException
-
wait() ,notify(), notifyAll()三个方法定义在java.lang.Object类中
package test;
/*
* 线程通信的例子:使用两个线程打印1-100,线程1,线程2,交替打印*/
class Number implements Runnable{
private int number =1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (number<=100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使的调用如下wait()方法的线程进入阻塞状态
wait(); //会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}else
break;
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t1.setName("线程2");
t1.start();
t2.start();
}
}
面试题: sleep()和wait()方法异同
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
不同点:
* 两个方法声明的位置不同: Thread类中声明sleep(),Object类中声明wait()
* 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
* 关于是否释放资源同步监视器的问题,如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁.
经典例题 生产者/消费者问题
分析:
1.是否是多线程问题? 是,生产者线程,消费者线程
2.是否有共享数据的问题?是,店员(或产品)
3.如何解决线程安全问题?同步机制,有三种方法
4.是否涉及线程的通信? 是
class Clerk{
private int productCount;
//生产产品
public synchronized void produceProduct() {
if (productCount<20) {
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第"+productCount);
notify(); //生产有产品后就可以唤醒消费者
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount>0) {
System.out.println(Thread.currentThread().getName() + ":开始消费第"+productCount);
productCount--;
notify(); //消费有产品后就可以唤醒生产者
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{ //生产者
private Clerk clerk;
@Override
public void run() {
System.out.println(getName() + ":开始生产产品");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer extends Thread{ //消费者
private Clerk clerk;
@Override
public void run() {
System.out.println(getName() + ":开始消费产品");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
p1.start();
c1.start();
}
}
JUD5.0新增线程创建方式
创建线程方式三: 实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
if (i%2==0) {
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口的实现类对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,去创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread); //FutureTask间接实现了Runnable接口
//5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象
Thread t1 = new Thread(futureTask);
t1.start();
try {
//6.获取Callable中call方法的返回值
//get返回值即为FutureTask构造器参数Callable实现类重写的call()的方法的返回值
Object sum = futureTask.get();
System.out.println("总和为"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
-
如何理解实现Callable接口的方式比实现Runnable接口创建多线程方式要强大?
-
call方法可以有返回值
-
可以抛出异常,被外面的操作捕获,获取异常的信息
-
创建线程方式四 使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用.类似生活中的公共交通工具.
好处:
提高响应速度(减少了创建线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
…
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
//设置线程池的属性
//System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.allowCoreThreadTimeOut(true);
// ((ThreadPoolExecutor) service).setKeepAliveTime();
//2.执行指定线程的操作,需要提供实现Runnable (Thread implements Runnable)接口或Callable接口实现类的对象
service.execute(new NumberThread());
service.execute(new NumberThread1()); //适合适用于Runnable
//service.submit(Callable callable);//适合使用与Callable
//关闭连接池
service.shutdown();
}
}
面试题 创建多线程有几种方式
四种
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池(响应速度提高了,提高了资源的重用率,便于管理)
总结
-
声明周期关注两个概念:状态、相应的方法
-
关注 :
-
状态a---->状态b:哪些方法执行了(回调方法)
-
某个方法主动调用 状态a---->状态b
-
-
阻塞:临时状态,不可以作为最终状态
-
死亡:最终状态
- 不会释放锁的操作
-
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前程序的执行
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁.(同步监视器)
应尽量避免使用suspend()和resume()来控制线程,已经过时的方法了