多线程
线程简述
什么是进程?什么是线程?
进程是一个应用程序。(一个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程
进程和线程是什么关系
进程A和进程B的内存独立不共享
线程A和线程B(在java语言中):
线程A和线程B,堆内存和方法区内存共享
但是栈内存独立,一个线程一个栈
假设启动10个线程,会有10个栈空间,每个栈之间互不干扰,各自执行各自的,这就是多线程并发
java中之所以有多线程机制,目的就是为了提高程序的处理效率
问题思考:
使用多线程机制之后,main方法结束,是不是有可能程序也不会结束?
答:main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈
对于单核的CPU来讲,真的可以做到真正的多线程并发吗?
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核CPU来说,它们处理是极其快速的,多个线程之间频繁的切换,给人一种并发的感觉。但其实不是并发的
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
四核CPU表示同一个时间点上,可以真正的有4个进程并发执行
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1.这才叫真正的多线程并发。
线程的三种实现方式
java语言中,实现线程有三种方式,先来看一下前两种,最后一种放在后面了
java支持多线程机制。并且java将多线程实现了,我们只需要继承就行了。
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法
package com.bjpowernode.thread;
/*
实现线程的第一种方式:
怎么创建线程对象? MyThread myThread = new MyThread();
怎么启动线程?调用线程对象的start方法
注意:方法体当中的代码永远都是自上而下的顺序一次逐行执行的
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread t = new MyThread();
//启动线程
//t.run();
// 直接调用run方法和调用start方法的区别是什么?
//直接调用run方法不会启动线程,不会分配新的分支栈(这种方式就是单线程)
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈开出来,start()方法就结束了。线程就启动成功了。
//启动成功的线程会自动调用run()方法,并且run方法在分支栈的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
t.start();
//这里的代码还是运行在主线程中
for (int i = 0; i < 1000; i++) {
System.out.println("主线程--->"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程--->" + i);
}
}
}
线程的start:
第二种方式
package com.bjpowernode.thread;
public class ThreadTest03 {
public static void main(String[] args) {
//创建一个可运行的对象
//MyRunnable r = new MyRunnable();
//Thread t = new Thread(r);
//以上两行可合并
Thread t = new Thread(new MyRunnable());
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程--->"+i);
}
}
}
//这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程--->"+i);
}
}
}
还可以用匿名内部类的形式
package com.bjpowernode.thread;
/*
采用匿名内部类可以吗?
*/
public class ThreadTest04 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类方式。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t线程--->"+i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程--->"+i);
}
}
}
线程的生命周期
线程对象
1.获取,修改线程对象的名字:
获取线程名字:
String name = 线程对象.getName();
修改线程对象的名字:
线程对象.setName(“线程名字”)
当线程没有设置名字的时候,默认的名字有什么规律?
Thread-0
Thread-1
Thread-2
Thread-3
…
2.获取当前线程对象
Thread currentThread = Thread.currentThread();
package com.bjpowernode.thread;
/*
1.怎么获取当前线程对象
2.获取线程对象的名字
3.修改线程对象的名字
*/
public class ThreadTest05 {
public static void main(String[] args) {
//currentThread就是当前线程对象
//这个代码出现在main方法当中,所以当前线程就是主线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());//main
//创建线程对象
MyThread2 t = new MyThread2();
//设置线程的名字
//t.setName("tttt");
//获取线程的名字
String tName = t.getName();
System.out.println(tName);//Thread-0
t.start();
MyThread2 t2 = new MyThread2();
System.out.println(t2.getName());//Thread-1
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//currentThread就是当前线程对象,当前线程是谁呢?
//当t1线程执行run方法,那么这个当前线程就是t1
//当t2线程执行run方法,那么这个当前线程就是t2
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+"-->" +i);
}
}
}
线程的sleep方法
sleep方法实例
package com.bjpowernode.thread;
/*
线程的sleep方法:
static void sleep(long millis)
1.静态方法Thread.sleep(1000)
2.参数是毫秒
3.作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用。
4.Thread.sleep();方法,可以实现:
间隔特定的时间去执行一段特定的代码
*/
public class ThreadTest06 {
public static void main(String[] args) {
// //当前线程进入休眠,睡眠5s
// //当前线程就是主线程!!!
// try {
// Thread.sleep(1000 * 5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("5s后输出Hello world");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
//睡眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
看一道sleep的面试题
package com.bjpowernode.thread;
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
MyThread3 t = new MyThread3();
t.setName("t");
t.start();
//调用sleep方法
try {
//这行代码会让线程t进入休眠状态吗?
t.sleep(1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5)
//这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
//这行代码出现在main方法中,main线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World!");
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
唤醒正在休眠的线程
案例
package com.bjpowernode.thread;
/*
怎么唤醒一个正在睡眠的线程?
注意:这个不是终断线程的执行,是终止线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5s后线程醒来
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制)
t.interrupt();
}
}
class MyRunnable2 implements Runnable{
//重点:run()当中的异常不能throw,只能try catch
//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->begin");
//睡眠一年
try {
Thread.sleep(1000*60*60*24*365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->end");
}
}
终止一个线程的执行的方法
stop方法(已过时,不推荐使用)
package com.bjpowernode.thread;
/*
在java中怎么强行终止一个线程的执行。
stop方法存在很大的缺点,容易丢失数据。因为这种方法是直接将线程杀死。
线程没有保存到数据将会丢失,不建议使用
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟5s
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5s后强行终止t线程
t.stop();//已过时,不建议使用
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
修改标记
package com.bjpowernode.thread;
/*
怎么合理的终止一个线程的执行。这种方式是很常用的
*/
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r = new MyRunnable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
//模拟5s
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想要什么时候终止t的执行,就什么时候把标记修改为false
r.run = false;
}
}
class MyRunnable4 implements Runnable{
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (run){
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//终止当前线程
return;
}
}
}
}
线程调度(了解)
常见的线程调度模型
抢占式调度模型
哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些
java采用的就是抢占式调度模型
均分式调度模型
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
平均分配,一切平等
有一些编程语言,线程调度模型采用的是这种方式
线程调度的方法
实例方法:
void setPriority(int newPriority)设置线程的优先级
int getPriority() 获取线程的优先级
最低优先级1
默认优先级是5
最高优先级是10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)
静态方法:
static void yeild() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。
yeild()方法的执行会让当前线程从“运行状态”回到“就绪状态”
注意:回到就绪状态之后,可能还会再次抢到。
线程安全
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。
什么时候数据在多线程并发的环境下会存在安全问题
1.多线程并发
2.有共享数据
3.共享数据有修改的行为
怎么解决线程安全问题(前导)
在多线程并发的环境下,有共享数据,而且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题。
这种机制被称为:线程同步机制
线程同步
两种模型:
1.异步编程模型
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型
其实就是:多线程并发(效率较高)
2.同步编程模型
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程直接发生了等待关系,这就是同步编程模型
同步就是排队
线程同步的语法
线程同步机制的语法是:
synchronized(){
//线程同步代码块
}
synchronized后面小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队。
()中写什么?
那要看你想要哪些线程同步。
假设t1,t2,t3,t4,t5,有5个线程
你只希望前三个排队,t4,t5不需要排队,怎么办?
你一定要在()中写一个t1,t2,t3共享的对象,
而这个对象对于t4,t5来说是不共享的
synchronized的三种写法
第一种
/*以下代码的执行原理:
1.假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2.假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放
3.假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象
的这把锁,结果这把锁已经被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序.*/
synchronized (this){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
第二种
/*在实例方法上也可以使用synchronized。
synchronized出现在实例方法上,
缺点:一定锁的是this,不能是其他对象了,所以这种方式不灵活。
另外当它出现在实例方法上时,表示整个方法体都需要同步,可能会无故扩大同
步的范围,导致程序的执行效率降低。所以这种方式不常用。
优点:代码简洁,如果共享的对象就是this,并且需要同步的代码块是整个方法
体,建议使用这种方式*/
public synchronized void withdraw(double money){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
第三种
在静态方法上使用synchronized
表示找类锁,类锁永远只有一把。
可以用来保护静态变量的安全
对象锁:1个对象1把锁,100个对象100把锁
类锁:100个对象,也可能只是1把类锁
Java中三大变量哪个不存在线程安全问题?
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享
(实例变量和静态变量都属于成员变量)
思考题:
局部变量中用StringBuffer还是StringBuilder
答:用StringBuilder。因为局部变量中不存在线程安全问题。StringBuffer是线程安全的,降低了效率(java.lang.StringBuffer中有的方法中使用了synchronized)。StringBuffer不是线程安全的,但效率高
另外ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的
看4道synchronized的面试题
package com.bjpowernode.exam1;
//面试题1:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为doOther方法没有synchronized
//面试题2:在doOther方法上加入synchronized后,doOther方法
// 执行的时候需要等待doSome方法的结束吗?
//需要
public class Exam01 {
public static void main(String[] args) throws
InterruptedException {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc=mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
package com.bjpowernode.exam3;
//面试题3:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为MyClass对象是两个,两把锁
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc=mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
package com.bjpowernode.exam4;
//面试题4:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc=mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出现在静态方法上是找类锁
public synchronized static void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
死锁
package com.bjpowernode.deadlock;
/*
写一个死锁。
synchronized最好不要嵌套使用,一不小心可能造成死锁
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//t1和t2两个线程共享o1,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
开发中怎么解决线程安全问题(总结)
并不是一上来就选择线程同步(synchronized)的
synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量(并发量)降低,用户体验差。在不得已的情况下再选择线程同步机制。
- 方案1:尽量使用局部变量代替实例变量和静态变量
- 方案2:如果必须是实例变量,那么可以考虑创建多个对象,这样实例对象的内存就不共享了(1个线程对应1个对象,100个线程对应100个对象,对象不共享就没有数据安全问题了。)
- 方案3: 如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择线程同步机制(synchronized)了
守护线程
java语言中线程分为两大类:
一类是:用户线程
另一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程
package com.bjpowernode.thread;
/*
守护线程
*/
public class ThreadTest14 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("备份数据的线程");
//启动线程之前,将线程设置为守护线程
t.setDaemon(true);
t.start();
//主线程:主线程是用户线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
@Override
public void run() {
int i = 0;
//即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止
while (true){
System.out.println(Thread.currentThread().getName()+"--->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器
定时器的作用:间隔特定的时间,执行特定的程序
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
- 可以使用sleep方法,睡眠,设置睡眠时间,这是最原始的定时器。
- 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
- 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.bjpowernode.thread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.SimpleFormatter;
public class TimerTest {
public static void main(String[] args) throws Exception {
//创建定时器对象
Timer timer = new Timer();
//守护线程的方式
//Timer timer = new Timer(true);
//指定定时任务
SimpleDateFormat sdf = new SimpleDateFormat
("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2022-01-06 15:55:15");
//由于TimerTask是个抽象类,不能直接new对象所以可以用匿名
//内部类方式
// timer.schedule(new TimerTask() {
// @Override
// public void run() {
// SimpleDateFormat sdf = new //SimpleDateFormat
// ("yyyy-MM-dd HH:mm:ss");
// String strTime = sdf.format(new Date());
// System.out.println(strTime+":成功完成了一次
// 数据备份!");
// }
// },firstTime,1000*10);
timer.schedule(new LogTimerTask(),firstTime,1000*10);
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写你需要执行的任务就行了。
SimpleDateFormat sdf = new SimpleDateFormat
("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime+":成功完成了一次数据备份!");
}
}
实现线程的第三种方式
实现callable接口。(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式
package com.bjpowernode.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/*
实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程
受堵塞,效率较低
*/
public class ThreadTest15 {
public static void main(String[] args) throws Exception {
//第一步:创建一个“未来任务类”对象
//参数非常重要,需要给一个Callable接口实现类对象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
//call方法相当于run方法,不过call有返回值
//线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(1000*10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a+b; //自动装箱(300结果变成Integer)
}
});
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
//这里是main方法,是在主线程中
//在主线程中,怎么获取t线程的返回结果?
//get()方法的执行会导致“当前线程堵塞”
Object obj = task.get();
System.out.println("线程执行结果:"+obj);
//main方法这里的程序要想执行必须等待get()方法的结束
//而get()方法可能需要很久。因为get()方法是拿另一个线程的
//执行结果,另一个线程执行是需要时间的。
System.out.println("Hello World!");
}
}
Object类中的wait和notify方法(生产者和消费者模式)
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。
wait方法的作用
Object o = new Object();
o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
notify方法作用
Object o = new Object();
o.notify();
表示:唤醒正在o对象上等待的线程
还有一个notifyAll方法:唤醒o对象上处于等待的所有线程
package com.bjpowernode.thread;
import java.util.ArrayList;
import java.util.List;
/*
1.使用wait方法和notify方法实现”生产者和消费者模式“
2.什么是生产者和消费者模式?
生产线程负责生产,消费线程负责消费
生产线程和消费线程要达到均衡
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法
3.wait和notify方法不是线程对象的方法,是普通java对象都有的方法
4.wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库,有线程安全问题
5.wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
6.notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
7.模拟这样一个需求:
仓库我们采用List集合。
List集合中假设只能存储1个元素
1个元素就表示仓库满了
*/
public class ThreadTest16 {
public static void main(String[] args) {
//创建一个仓库对象,共享的
List list = new ArrayList();
//创建两个线程对象
//生产者线程
Thread t1 = new Thread(new Prodecer(list));
//消费者线程
Thread t2 = new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Prodecer implements Runnable{
//仓库
private List list;
public Prodecer(List list){
this.list=list;
}
@Override
public void run() {
//一直生产
while (true){
//给仓库对象List加锁
synchronized (list){
if (list.size()>0){//大于0.说明仓库中已经有1个元素了
//当前线程进入等待状态,并且释放Producer之前占有的List集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能够执行到这里说明仓库是空的,可以生产
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName()+"--->"+obj);
//唤醒消费者消费
list.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable{
//仓库
private List list;
public Consumer(List list){
this.list=list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
if (list.size()==0){
//仓库已经空了
//消费者线程等待,释放掉List集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能够执行到此处说明仓库中有数据,进行消费。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName()+"--->"+obj);
//唤醒生产者生产
list.notify();
}
}
}
}