方式一: 继承Thread类
步骤
-
创建一个继承于Thread类的对象
-
重写Thread类的run()方法 --> 此线程将要执行的操作声明在run()中
-
创建Thread类子类对象
-
通过实例对象调用start()方法,(1)启动当前线程 (2)调用当前线程的run()方法
注意
- run()方法只是一个普通方法,只有调用start()方法才能创建新的线程
- 一个Thread子类对象实例只能start一次,只能创建启动一次线程
Thread类中常用方法
void | start() | 使该线程开始执行;Java 虚拟机调用该线程的 run() 方法。 |
void | run() | 通常要重写此方法,将创建的线程要执行的操作写在此方法中 |
static Thread | currentThread() | 返回对当前正在执行的线程对象的引用 |
void | setName() | 设置当前线程名称 |
String | getName() | 获取当前线程名称 |
static void | yield() | 暂停当前正在执行的线程对象进入就绪状态,并执行其他线程,静态方法,不会释放锁对象 |
void | join() | 等待该线程终止,在线程a中调用线程b的join()方法,线程a进入阻塞状态, 直到b指向完毕或等待规定的时间之后,join()方法有三种重载形式,其余两种可设置时间, 注意不要调用自己的join()方法,否则自己的线程就会进入等待 |
void | sleep() | 在指定的毫秒(或毫秒加纳秒)数内休眠线程,不会释放锁对象 |
从以下示例去理解创建和启动线程
- 创建一个线程变量100以内的偶数
public class demo01 {
public static void main(String[] args) {
//3. 创建Thread类子类对象
TestThread testThread = new TestThread();
//4. 通过实例对象调用start()方法,启动线程
testThread.start();
System.out.println("hello_1");
System.out.println("hello_2");
System.out.println("hello_3");
}
}
//1. 创建一个继承于Thread类的对象
class TestThread extends Thread {
//2. 重写Thread类的run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(i);
}
}
}
}
- 创建三条线程同时下载三张图片
public class demo02 {
public static void main(String[] args) {
//启动三个线程下载三张图片
new DownThread("Thread/src/file/海绵宝宝.jpg",
"http://www.qkmango.top/file/img/hmbb.jpg").start();
new DownThread("Thread/src/file/派大星.jpg",
"http://www.qkmango.top/file/img/pdx.jpg").start();
new DownThread("Thread/src/file/章鱼哥.jpg",
"http://www.qkmango.top/file/img/zyg.jpg").start();
}
}
/**
* <p>下载图片线程类</p>
* <p>此类启动线程后调用进行下载图片</p>
*/
class DownThread extends Thread {
private String name;
private String path;
@Override
public void run() {
new DownImg().download(name, path);
}
public DownThread(String name, String url) {
this.name = name;
this.path = url;
}
}
/**
* 下载图片类
*/
class DownImg {
public void download(String path, String url) {
URL webUrl = null;
try {
webUrl = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try(BufferedInputStream is = new BufferedInputStream(webUrl.openStream());
BufferedOutputStream os =
new BufferedOutputStream(new FileOutputStream(path))) {
byte[] flush = new byte[1024*8];
int len = -1;
while ((len = is.read(flush)) !=-1) {
os.write(flush,0,len);
}
os.flush();
System.out.println(path+" 图片下载完成!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 部分常用方法
public class demo01 {
public static void main(String[] args) {
//5. setName()
Thread.currentThread().setName("main线程");
TestThreadMethod t = new TestThreadMethod("遍历线程");
t.start();
for (int i = 0; i < 100; i++) {
if(i % 2 ==0) {
//3. currentThread()
//4. getName()
System.out.println(Thread.currentThread().getName()+": "+i);
}
if(i == 20) {
try {
//join(),当主线程1==20时调用t.join(),主线程进入阻塞状态,等待t线程执行完毕
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class TestThreadMethod extends Thread {
//调用父类构造器传入线程名字
TestThreadMethod(String name) {
super(name);
}
//2. run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 ==0) {
//3. currentThread()
//4. getName()
System.out.println(Thread.currentThread().getName()+": "+i);
}
if(i % 20 ==0) {
//6. yield()
Thread.yield();
}
}
}
}
方式二:实现Runnable接口
步骤
-
创建一个实现了Runnable接口的类
-
实现类去实现Runnable接口中的抽象方法run()方法
-
创建实现类对象
-
创建Thread类的对象,将实现类对象作为参数传入Thread的构造方法
-
通过Thread类对象调用start()方法
注意
这种方式创建多个Thread类对象,可将同一个实现类对象作为一个参数传递进去,那么此时多个线程都是公用的同一个对象同一份数据
从以下实例去理解创建多线程的第二种方式
- 三个线程共同使用同一个对象的数据,共同使用一份数据,共同售卖100张票(此时会有线程安全问题)
public class demo01 {
public static void main(String[] args) {
//创建三个线程,共用同一个对象
Window window = new Window();
new Thread(window,"窗口1").start();
new Thread(window,"窗口2").start();
new Thread(window,"窗口3").start();
}
}
/**
* 售票窗口
*/
class Window implements Runnable {
//票数
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"售卖第" + ticket + "张票");
ticket--;
} else {
System.out.println("售卖完毕");
break;
}
}
}
}
线程优先级
线程优先级有10挡
Thread类中提供了3种内置的常量
MAX_PRIORITY: 10 线程可以具有的最高优先级
MIN_PRIORITY: 1 线程可以具有的最低优先级
NORM_PRIORITY:5 分配给线程的默认优先级
设置和获取线程的优先级
setPriority(int newPriority) 更改线程的优先级
getPriority() 获取线程的优先级
注意
高优先级的线程抢占低优先级线程的CPU的执行权,但只是从概率上讲高优先级的的线程高概率被执行,
并不意味着高优先级的线程执行完低优先级的线程才执行,只是概率问题
从以下实例掌握线程优先级的设置的获取
public class demo01 {
public static void main(String[] args) {
TestThreadPriority t = new TestThreadPriority();
//设置t线程为最大优先级
t.setPriority(Thread.MAX_PRIORITY);
t.start();
//设置当前主线程为最低优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+
": "+Thread.currentThread().getPriority()+": "+i);
}
}
}
}
/**
* 测试线程优先级类
*/
class TestThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(getName()+": "+getPriority()+": "+i);
}
}
}
}
线程的生命周期
线程的生命周期一共有五种状态
- 新建
- 就绪
- 运行
- 阻塞
- 死亡
线程安全
使用同步机制解决线程安全问题
引例
一下代码,三个线程共同使用同一份对象的数据,但是在运行时会出现线程安全问题
问题:会出现重票和错票 --> 出现了线程安全问题
* 原因:在某个线程操作车票时,尚未操作完成时,其他线程参与进来,也操作车票
* 如何解决:当一个线程操作ticket时,其他线程不能参与进来,直到此线程操作完ticket时其他线程才能参与进来,
* 即使此线程发生了阻塞,也不能被改变
public class demo01 {
public static void main(String[] args) {
TestThreadPriority t = new TestThreadPriority();
//设置t线程为最大优先级
t.setPriority(Thread.MAX_PRIORITY);
t.start();
//设置当前主线程为最低优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+
": "+Thread.currentThread().getPriority()+": "+i);
}
}
}
}
/**
* 测试线程优先级类
*/
class TestThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(getName()+": "+getPriority()+": "+i);
}
}
}
}
方式一:synchronized同步代码块
* synchronized(同步监视器) {
* //需要被同步的代码
* }
* 说明:1. 操作共享数据的代码,即为需要被同步的代码
* 2. 共享数据:多个线程共同操作的数据。比如本问题当中的ticket就是共享数据
* 3. 同步监视器,俗称“锁”,任何一个类的对象都可以充当锁;
* 要求:多个线程都要使用同一把锁
* 注意:将操作共享数据的代码包含起来,但是不能包含多了,也不能包含少了
提示:
“锁”也可以是当前对象this
,只要在合适的情况下满足多个线程共用一把锁就可以了
"锁"也可以是当前这个类,类也是对象,例如Window.class
就是Window类这个对象,需要反射的知识
示例 解决实现Runnable接口的线程安全问题
public class demo01 {
public static void main(String[] args) {
//创建三个线程,共用同一个对象
Window window = new Window();
new Thread(window,"窗口1").start();
new Thread(window,"窗口2").start();
new Thread(window,"窗口3").start();
}
}
/**
* 售票窗口
*/
class Window implements Runnable {
//票数
private int ticket = 100;
@Override
public void run() {
while (true) {
//synchronized包裹住操作共享数据的代码
//此时多个线程使用同一个对象,所以this是唯一的,满足要求
synchronized (this) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"售卖第" + ticket + "张票");
ticket--;
} else {
System.out.println("售卖完毕");
break;
}
}
}
}
}
示例 解决继承Thread类的线程安全问题
大同小异,只要保证"锁"是唯一的就行了,当然,ticket数据要是共享的
public class demo02 {
public static void main(String[] args) {
//创建三个Thread子类对象
new Window1().start();
new Window1().start();
new Window1().start();
}
}
/**
* 售票窗口
*/
class Window1 extends Thread {
//票数
//因为实现Thread方式创建多线程,各线程使用的不是同一个对象,所以为了保证票数,使用static
private static int ticket = 100;
//同一把锁:obj,任何类对象都行
//private static Object obj = new Object();
@Override
public void run() {
while (true) {
//synchronized包裹住操作共享数据的代码
//synchronized (obj) {
//此处也可以使用Window类这个对象,类也是对象
synchronized (Window1.class) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"售卖第" + ticket + "张票");
ticket--;
} else {
System.out.println("售卖完毕");
break;
}
}
}
}
}
方式一同步线程的有点与局限性
- 解决了线程安全问题
- 在同步代码块的区域,只允许一个线程执行,相当于此时是单线程
- 当多个线程尝试获取锁时,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,这样会造成性能消耗
方式一:synchronized同步方法
* 操作共享数据的的代码完整的声明在一个方法中,不妨将方法声明为同步的
* 注意:
* 1. synchronized修饰的非静态方法时,默认的锁为this,叫做方法锁,也叫做对象锁;
* 2. synchronized修饰的静态方法时,默认的锁为Window.class,即类这个对象,叫做类锁
示例 解决实现Runnable接口的线程安全问题
public class demo01 {
public static void main(String[] args) {
//创建三个Thread子类对象,共用一个window对象
Window w = new Window();
new Thread(w).start();
new Thread(w).start();
new Thread(w).start();
}
}
class Window implements Runnable {
private static int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (true) {
if(flag) {
show();
} else {
break;
}
}
}
//将操作共享数据的代码封装到方法里面,声明方法为synchronized
//注意,此时的锁默认为this(称为方法锁,也叫对象锁)
private synchronized void show() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"售卖第" + ticket + "张票");
ticket--;
} else {
flag = false;
System.out.println("售卖完毕");
}
}
}
示例 解决继承Thread类的线程安全问题
public class demo02 {
public static void main(String[] args) {
//创建三个子类对象
new Window().start();
new Window().start();
new Window().start();
}
}
class Window extends Thread {
private static int ticket = 100;
private static boolean flag = true;
@Override
public void run() {
while (true) {
if(flag) {
show();
} else {
break;
}
}
}
//将操作共享数据的代码封装到方法里面,声明方法为synchronized
//注意,此时方法为static,锁默认为Window.class,称为类锁
private static synchronized void show() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"售卖第" + ticket + "张票");
ticket--;
} else {
flag = false;
System.out.println("售卖完毕");
}
}
}
解决懒汉式单例模式的线程安全问题
- 以下为单利模式的懒汉式,此时当多个线程获取
Bank
的同一个示例对象时,调用getInstance()
方法,当A线程执行到if(instance == null)
后cpu切换到B线程,B线程在进行判断if(instance == null)
,后new一个对象,之后得到实例对象,再之后A线程再次被执行,此时也new了一个对象,导致对象不是唯一的,线程不安全
class Bank {
private static Bank instance = null;
private Bank(){}
public static Bank getInstance() {
if(instance == null) {
instance = new Bank();
}
return instance;
}
}
- 使用
synchronized
解决线程安全 方式一,效率略低
多个线程执行getInstance()
时都会发生阻塞,但其实只需要第一个线程执行new Bank()
即可,其他线程直接返回instance
即可,但此时每个线程执行都会阻塞,效率略底
class Bank {
private static Bank instance = null;
private Bank(){}
/* 方式一:但是效率稍低
* 此时解决了线程安全问题,但是效率稍低,当instance不为null时,
* 可以直接返回instance,而不需要进行synchronized里面的代码块,
* 执行synchronized代码块其他线程会阻塞
*/
public static Bank getInstance() {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
return instance;
}
}
- 使用
synchronized
解决线程安全 方式二,效率稍高
此时,即使多个线程都进入了第一个if(instance == null)
内,但是synchronized
代码块线程是安全的,其中一个线程去执行instance = new Bank()
后instance
不为null
,刚开始一起进入的if(instance == null)
内部的线程会进行进行第二次判断,此时不为null
,直接跳出,返回instance
;之后再来的线程直接进行第一个判断即可跳过synchronized
执行最后一条return instance
语句,不需要每个线程每次都去执行synchronized
发生线程阻塞。
class Bank {
private static Bank instance = null;
private Bank(){}
//方式二:效率略高
public static Bank getInstance() {
if(instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
- 指令重排导致线程安全问题扩展
但是,双重检查加锁并不代码百分百一定没有线程安全问题了。因为,这里会涉及到一个指令重排序问题。
instance = new Bank();其实可以分为下面的步骤:
1.申请一块内存空间;
2.在这块空间里实例化对象;
3.instance的引用指向这块空间地址;
指令重排序存在的问题是:
对于以上步骤,指令重排序很有可能不是按上面123步骤依次执行的。比如,先执行1申请一块内存空间,然后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,那么,当它再去执行2步骤,判断instance时,由于instance已经指向了某一地址,它就不会再为null了,因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?
加上volatile关键字,因为volatile可以禁止指令重排序。
多线程死锁问题
描述
- 不同的线程分别占用对方需要的同步资源(需要的锁)不放弃,
- 都在等待对方放弃自己需要的同步资源(需要的锁),就形成了线程的死锁;
- 出现死锁后,不会出现异常,不会提示,只是线程都处于阻塞状态,无法继续执行
解决方法
-
专门的算法、原则
-
尽量减少同步资源的定义
-
尽量避免嵌套同步(嵌套使用synchronized)
死锁示例
- 例子 1
* 示例中使用匿名内部类创建了两个线程,
* A线程执行到外synchronized时获得了s1锁,然后休眠100毫秒,此时CPU执行B线程,
* B线程执行外synchronized时获得了s2锁,然后也休眠100毫秒,此时CPU又回到A线程,
* 执行内synchronized,但是需要的s2锁已经被B线程拿到,于是A线程等待;CPU又切换执行B线程时,
* 执行内synchronized需要s1锁,但是也已经被A线程拿到,于是两个线程进入僵持状态
public class demo01 {
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("aaa");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s2.append(111);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append(222);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s2.append("bbb");
}
}
}
}).start();
}
}
- 例子 2
public class demo01 {
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
new Thread(dl).start();
}
}
class DeadLock implements Runnable {
private StringBuffer s1 = new StringBuffer();
private StringBuffer s2 = new StringBuffer();
@Override
public void run() {
//如果是线程t1,执行methodA(),否则执行methodB()
if(Thread.currentThread().getName().equals("t1")) {
methodA();
} else {
methodB();
}
}
public void methodA() {
synchronized(s1) {
s1.append("111");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
methodB();
}
}
public void methodB() {
synchronized(s2) {
s2.append("222");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
methodA();
}
}
}
方式二:Lock锁
ReentrantLock
类
java.lang.Object
java.util.concurrent.locks.ReentrantLock
步骤
- 创建锁
ReentrantLock lock = new ReentrantLock()
- 获取锁
lock()
- 释放锁
unlock()
注意
-
获取锁与释放锁需要手动
-
如果忘记释放锁就会产生死锁的问题,所以,通常需要在
finally
中进行锁的释放,即需要操作共享数据的代码放在
try
中,在try
中获取锁,最后在finally
中释放锁 -
此时多个线程的锁
ReentrantLock lock
也要保证唯一
示例
- 实现
Runnable
接口使用Lock
锁,此时多个线程共用一个对象,锁唯一
public class demo01 {
public static void main(String[] args) {
Window w = new Window();
new Thread(w).start();
new Thread(w).start();
new Thread(w).start();
}
}
class Window implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
//手动获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ " 票号为:" + ticket);
ticket--;
} else {
System.out.println(Thread.currentThread().getName() + "已售完");
break;
}
} finally {
//手动释放锁
lock.unlock();
}
}
}
}
- 实现继承
Thread
类使用Lock
锁,此时多个线程有多个对象,保证锁唯一可以声明为static
public class demo02 {
public static void main(String[] args) {
new Window().start();
new Window().start();
new Window().start();
}
}
class Window extends Thread {
private int ticket = 100;
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
//手动获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ " 票号为:" + ticket);
ticket--;
} else {
System.out.println(Thread.currentThread().getName() + "已售完");
break;
}
} finally {
//手动释放锁
lock.unlock();
}
}
}
}
synchronized
与Lock
异同对比
- 面试题 解决线程安全的方式有几种?
synchroized
同步代码块synchroized
同步方法Lock
锁
练习
* 有一个银行账户,有两个储户分别存3000元,分三次存储,每次存储完打印余额
* 分析:两个储户为两个线程
共享数据为账户,保证存钱操作时账户余额要线程安全
public class demo02 {
public static void main(String[] args) {
//同一个账户
Account account = new Account(0);
//使用extends Thread方式
//new User(account,"用户1").start();
//new User(account,"用户2").start();
//使用implements Runnable接口方式
new Thread(new User(account,"用户1")).start();
new Thread(new User(account,"用户2")).start();
}
}
/**
* 用户类
*/
class User implements Runnable {
private Account account;
@Override
public void run() {
//存钱3次,每次1000
for (int i = 0; i < 3; i++) {
account.saveMoney(1000);
}
}
public User(Account account, String name) {
this.account = account;
}
}
/**
* 账户类
*/
class Account {
private double money;
private ReentrantLock lock = new ReentrantLock();
public double getMoney() {
return money;
}
public Account(double money) {
this.money = money;
}
/**
* 存钱方法
* @param money
*/
public void saveMoney(double money) {
try {
lock.lock();
Thread.sleep(100);
this.money += money;
System.out.println(Thread.currentThread().getName()
+"存钱成功,余额为:"+this.money);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程通讯
线程通讯涉及到三个方法
wait() | 导致当前线程等待,进入阻塞,释放同步监视器 | |
notify() | 唤醒在此对象监视器上等待的单个线程 | |
notifyAll() | 唤醒在此对象监视器上等待的所有线程 |
注意
-
wait()
、notify()
、notifyAll()
三个方法必须使用在同步方法或同步代码块当中(synchronized
) -
wait()
、notify()
、notifyAll()
三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会报错 -
wait()
、notify()
、notifyAll()
三个方法是定义在Object
类中的
示例
-
使用线程通讯,使两个线程交替打印1-100数字
* 原理: * 打印前将一个等待的线程唤醒,打印后当前线程进入等待阻塞状态,释放同步锁
public class demo01 {
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
new Thread(printNum).start();
new Thread(printNum).start();
}
}
class PrintNum implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
//唤醒一个在等待的线程
this.notify();
if(num < 100) {
System.out.println(Thread.currentThread().getName()+": "+num++);
try {
//阻塞当前线程,释放同步监视器
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
面试题:sleep()
和wait()
方法异同
相同
- 调用这两个方法后,当前线程都会进入阻塞状态
不同
-
方法的声明位置不同
sleep()
声明在Thread
类中wait()
声明在Object
类中
-
调用的要求不一样
sleep()
可以在任何场景下调用wait()
必须在同步代码块或同步方法中调用,调用者必须是同步监视器对象
-
是否释放同步监视器
sleep()
不会释放同步监视器wait()
会释放同步监视器
方式三:实现Callable接口
使用Callable接口和Future接口创建线程JDK1.5新增
具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
步骤
-
创建Callable借口的实现类,重写call()方法,并创建对象
-
创建一个FutureTask类对象,将Callable实现类对象作为构造器的参数传入
-
创建一个Thread类对象,将FutureTask类对象作为参数传入构造器
-
启动Thread线程
-
如果需要此线程的返回值,可以通过FutureTask类对象的get()方法获取
与实现Runnable接口创建的线程的不同点
通过实现Callable接口的方式比实现Runnable接口的方式创建多线程更强大:
- 实现Callable接口,重写call()方法可以有返回值
- 可以抛出异常,被外面的操作捕获,获取异常信息
- Callable支持泛型(正是返回值类型)
实例
- 通过一个线程实现1-100的和,并返回和值
public class demo01 {
public static void main(String[] args) {
//创建一个FutureTask类对象,将实现类对象作为参数传入构造器
FutureTask futureTask = new FutureTask(new NewThreadIC());
//创建一个Thread线程类,并start(),FutureTask类对象作为构造器参数传入
new Thread(futureTask).start();
try {
//通过FutureTask类对象调用get()方法获取线程的call方法的返回值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* 创建Callable借口的实现类,重写call()方法
*/
class NewThreadIC implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
方式四:使用线程池
守护线程
了解
Java分为两种线程:用户线程和守护线程
所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程,JVM不会等待守护线程执行完毕再停止。
使用
将线程转换为守护线程可以通过调用Thread
对象的setDaemon(true)
方法来实现。在使用守护线程时需要注意一下几点:
注意
thread.setDaemon(true)
必须在thread.start()
之前设置,否则会跑出一个IllegalThreadStateException
异常。你不能把正在运行的常规线程设置为守护线程。- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
示例
- 男孩线程一直默默地守护这女孩线程,当女孩线程结束毕业后,男孩线程就也跟着结束了
public class demo01 {
public static void main(String[] args) {
Girl girl = new Girl();
Boy boy = new Boy();
Thread t1 = new Thread(girl);
Thread t2 = new Thread(boy);
//将t2设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
/**
* 女孩
*/
class Girl implements Runnable {
@Override
public void run() {
for (int i = 1; i < 12*3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("再学校的第" + i +"月");
}
System.out.println("毕业");
}
}
/**
* 男孩,一直默默地守护着女孩
*/
class Boy implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\t默默地守护着你");
}
}
}
Thread和Runnable方式总结
实现Runnable接口类去创建多线程,需要将子类实例对象丢到Thread类的实例对象的构造器中,通过Thread实例对象去启动线程,因为实现Runnable接口,没有start()方法,只能借助Thread类中的start()方法启动线程。当然,如果如果多个线程去共享同一份数据,大可把一个子类对象丢到多个Thread类的构造方法中,创建的多个线程,使用的都是同一个对象的数据。
继承Thread类的方式创建多线程,因为继承的Thread类中已有start()方法,所以可以通过子类实例对象的start()方法启动线程,如果需要多个线程使用同一份数据,那么可以将共享数据声明为start类型,这样多个线程就可以使用同一份数据了。
另外,继承Thread类去创建多线程,也可以通过Thread类的实例去启动线程,这样也可以多个线程共用同一个对象的数据,但是继承Thread的方式创建的对象,本身就含有一个线程,完全可以使用自己的线程执行run()方法,如果使用Thread类实例对象的方式去启动线程,此时实际上我们只需要一个线程,但是存在了两线程,但是只有调用start()方法的对象的线程才是启动的,白白的浪费了一个线程。
总结:Thread类的对象以及继承Thread类的对象在new的时候就创建了线程,只需要自身调用start()方法即可启动线程;而实现Runnable接口的子类,new的时候是没有创建线程的,需要通过Thread()的实例对象的线程去执行子类的run()方法;继承Thread类的实例对象不需要借助Thread对象代理,实现Runnable接口的需要借助代理。