- 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process)是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。程序是静态的,进程是动态的
- 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的
何时需要使用多线程?
当我们需要执行两个或两个以上的任务时,就需要我们开启多线程。
一 如何开启多线程?
第一种继承Thread
第二种实现Runnable接口
第三种实现Callable接口
第四种调用线程池
Runnable与Callable实现方法相似,只是Callable有返回值,就不做细说了,后面再介绍
先对比一下继承Thread 和 实现 Callable
第一种 :继承Thread
①创建一个类并继承Thread
②重写Thread中的run方法
③创建Thread子类的对象
④调用Thread子类对象的start方法
第二种 : 实现Runnable接口
①实现Runnable接口
②重写run方法
③创建Runnable接口实现类的对象
④创建Thread对象并将Runnable实现类的对象作为参数传递给Thread
⑤调用Thread对象的start方法
注意:在调用start()方法的时候不能改为直接调用run方法,因为这样并没有开启一个新的线程start方法的作用:
①开启一个新的线程
②调用run方法
在线程使用的时候有几个方法不容易理解,并且这几个方法对线程的生命周期起到关键作用
- static void yield():线程让步暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程若队列中没有同优先级的线程,忽略此方法
- join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 低优先级的线程也可以获得执行
- static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常
- stop(): 强制线程生命期结束
- boolean isAlive():返回boolean,判断线程是否还活着
注:一般在继承需实现的时候一般会选用实现Runnable接口的方式,因为Java是单继承多实现
并且在操作共享数据的时候,Runnable中的数据只有一份。Thread中的数据每个线程拥有一份。
二、线程安全问题解决:同步锁synchronized四种、Lock
同步锁之synchronized
无论是实现还是继承都存在线程安全问题,而在继承多线程的时候每次都要创建类对象,即对象会产生多个,这就不利于同步方法解决线程的安全问题了,因为监视器是不同的对象了,所以也不能用this做为监视器了,但可以在同步方法里面加上static关键字起到限制作用。而实现Runnable接口的时候创建多线程只需要创建一个子类对象即可,所以可以使用this作为监视器,所以在同步方法里面需要注意:
1.实现Runnable接口 实现的同步方法 监视器默认的是this
2.继承Thread类 实现的同步方法,必须加static。因为原来默认的是this所以不行。
解决线程安全问题方法一:同步代码块
格式:synchronized (对象(同步锁 、监视器)){
同步代码;
}
1.监视器可以是任意类的对象(多个线程必须是同一个对象)。
2.实现Runnable接口的方式 监视器可以使用this
继承Thread的方式监视器不可以使用this
解决线程安全问题方法二:同步方法
public synchronized void setAge(){
同步代码
}
先看一下同步代码块在这两种方式下的体现:
1)实现Runnable接口
public class SynRunnTest {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
// 共享数据
private int tick = 100;
private Object obj = new Object();
private String str = new String("aaa");
public void run() {
// 同步代码块
while (true) {
synchronized (str) {
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick);
// try {
// Thread.currentThread().sleep(50);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
tick--;
} else
break;
}
}
}
}
2)继承Thread
public class SynThrTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
/*
* 一个类的多个对象共同拥有一份类变量
*/
class MyThread extends Thread {
// 共享数据
private static int tick = 100;
private static Object obj = new Object();
public void run() {
while (true) {
/*
* 监视器也可以写成 当前类.class
*/
synchronized (obj) {
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick);
tick--;
} else
break;
}
}
}
}
再来看一下使用同步方法在这两种方式下的体现:
1)实现Runnable接口
public class SynMetRunTest {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
// 共享数据
private int tick = 100;
private Object obj = new Object();
public void run() {
while (true) {
if(!buy()){
break;
}
}
}
//同步方法
public synchronized boolean buy(){
if (tick > 0) {
System.out.println(Thread.currentThread().getName()
+ "售出车票,tick号为:" + tick);
tick--;
return true;
}else{
return false;
}
}
}
2)继承Thread
public class SynMetThrTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
/*
* 一个类的多个对象共同拥有一份类变量
*/
class MyThread extends Thread {
// 共享数据
private static int tick = 100;
private static Object obj = new Object();
public void run() {
while (true) {
buy();
}
}
//同步方法 - 如果是继承Thread的方式那么同步方法需要加static
public static synchronized void buy(){
if (tick > 0) {
System.out.println(Thread.currentThread().getName()
+ "售出车票,tick号为:" + tick);
tick--;
}
}
}
通过上面的四种情况需要注意的几点:
①获取当前线程名的三种方式:
- 掉用Thread的setName()方法
- 使用Thread的有参构造Thread(String threadname),创建线程并指定线程实例名
- 使用默认线程名:Thread.currentThread().getName()
③监视器可以用类名.class的模板对象来替代原有对象或默认的this,但Thread不可以用this作为锁,上面已解释过来,就不做赘述了
④实现Thread,共享数据源要加上static修饰,防止多个锁产生多个相同的数据源,同步方法必须是static的
⑤实现Thread,锁不可以是this,但可以是类名.class或Object(static)的对象,即所有对象的父类对象,也可以实现统一锁
而懒汉单例模式也存在着线程安全问题,所以可以使用同步代码块解决这个问题:
class Bank{
private Bank(){}
private static Bank bank = null;
public static Bank getInstance(){
if(bank == null){
synchronized (Bank.class) {
if(bank == null){
bank = new Bank();
}
}
}
return bank;
}
}//这里面的索就是类名.class
同步锁之Lock
上面的synchronized是java1开始的内置锁,从Java 5开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
Lock是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁),可以显式加锁、释放锁。
public class LockTest {
public static void main(String[] args) {
MyRunna m = new MyRunna();
new Thread(m).start();
new Thread(m).start();
}
}
class MyRunna implements Runnable {
private final ReentrantLock lock = new ReentrantLock();
private int tick = 1000;
@Override
public void run() {
while(true){
lock.lock(); //锁住 执行共享数据的代码
try {
// 保证线程安全的代码;
if(tick > 0){
System.out.println(
Thread.currentThread().getName() + "======" + tick);
tick--;
}
} finally {
lock.unlock(); //解锁
}
}
}
}
三、线程通信(wait(/notify()/notifyAll()):synchronized四种方式
Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
- wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
实现线程通信使用的是同步代码块与同步方法,所有仍有四种方式,不过我把它们写在两个类里面方便对比
1)实现Runnable接口
public class NotifyTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class MyRunnable implements Runnable{
private int number = 100;
/*
* 使用同步代码块
*/
@Override
public void run() {
while(true){
synchronized (this) {
if(number > 0){
this.notify();
System.out.println(Thread.currentThread().getName() + "==" + number);
number--;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
buy();
}
}
/*
* 使用同步方法:默认的监视器是this
*
*/
public synchronized void buy(){
if(number > 0){
this.notify();
System.out.println(Thread.currentThread().getName() + "==" + number);
number--;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2)继承Thread
public class NotifyTest02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.start();
}
}
class MyThread extends Thread{
private static int number = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
if(number > 0){
//obj.notify();
System.out.println(Thread.currentThread().getName() + "==" + number);
number--;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
public static synchronized void buy(){
if(number > 0){
MyThread.class.notify();
System.out.println(Thread.currentThread().getName() + "==" + number);
number--;
try {
MyThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面wait()/notify()/notifyAll()三个方法实际上是调用的监视器对象的
通过上面的四种情况需要注意的几点:
①实现Runnable接口的同步代码块/同步方法的线程通信锁默认都是this,所以实现Runnable可以省略锁,即this.wait()/this.notify()/this.notify(),当然也可以指定类名.class或线程对象(代码块中)
②继承Thread不可以使用默认this,而且锁也不可以省略,可以使用Object对象(代码块中)或者类名.class
③继续Thread的同步方法必须声明为static,而且共享资源也必须是static的
四、死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class DeadLock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread() {
public void run() {
synchronized (s1) {
s2.append("A");
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (s2) {
s2.append("B");
System.out.print(s1);
System.out.print(s2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (s2) {
s2.append("C");
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (s1) {
s1.append("D");
System.out.print(s2);
System.out.print(s1);
}
}
}
}.start();
}
}
五、线程池
线程池的中类
newFixedThreadPool(n):创建一个定长线程池,里面存放n个线程
newCachedThreadPool():创建一个缓存线程池
newSingleThreadExecutor():创建一个执行器,使用一个单一的线程操作关闭一个无限的队列
newScheduledThreadPool(n);创建一个线程池,可以调度命令在一个给定n的延迟后运行,或周期性的执行
使用线程池执行线程任务的步骤如下:
1.调用Executors 类的静态方法newFixedThreadPool(int nThreads),创建一个可重用的、具有固定线程数的线程池ExecutorService对象
2.创建Runnable实例,作为线程执行任务
3.调用ExecutorService对象的submit()提交Runnable实例
4.调用ExecutorService对象的shutDown()方法关闭线程池。
// 创建一个缓存线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//创建一个执行器,使用一个单一的工作线程操作关闭一个无限的队列。
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
//创建一个线程池,可以调度命令在一个给定的延迟后运行,或周期性地执行。
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(20);
// 创建一个定长线程池 池里只有10个线程
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
//开启一个分线程
newFixedThreadPool.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(
Thread.currentThread().getName() + " " + i);
}
}
});
//关闭线程池
newFixedThreadPool.shutdown();
从上面的代码可以看到每使用一次就要调用一次线程池,这样就很麻烦了,所以可以创建一个单例线程池,里面返回线程池对象,而且一个项目就创建一次就够了,每次使用只需要调用单例线程池就行了,而且还有一个好处就是,如果要更改线程池只需要在单例里面改线程池类型,整个项目的线程池类型就可以更改了
代码:
/*
* 创建一个单例 用来管理线程池 (看看)
*/
public class ThreadPoolSingle {
private ThreadPoolSingle(){}
private static ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
public static ExecutorService getThreadPool(){
return newCachedThreadPool;
}
}
主方法调用单例:
ThreadPoolSingle.getThreadPool().execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
当然上面的单例还是不完善仍有弊端,比如更改线程池的话在主方法里面的execute方法名也会改变,即不是统一的,每次执行的话也要更改线程池的方法,那么就需要对方法进行二次封装了,和封装线程池一下,如果后期要是更改线程池和对应的方法,只需要在单例里面更改线程池与方法就可以了。
注意:上面的execute()是线程池的方法 ,下面的execute()是单例对象的
/*
* 创建一个单例 用来管理线程池
*/
public class ThreadPoolSingle2 {
private ThreadPoolSingle2(){}
private static ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
private static ThreadPoolSingle2 s = new ThreadPoolSingle2();
public static ThreadPoolSingle2 getThreadPool(){
return s;
}
public void execute(Runnable r){
//newCachedThreadPool.execute(r);
newCachedThreadPool.submit(r);
}
}
ThreadPoolSingle2.getThreadPool().execute(new Runnable() {
@Override
public void run() {
}
});
六、实现Callable接口(有返回值)
第一步 实现Callable接口
第二步 重写call方法
第三步 创建Callable的实现类的对象
第四步 创建FutureTask对象 并将Callable实现类的对象作为实参传入
第五步 创建Thread的对象并调用start方法
public class ThreadTest02 {
public static void main(String[] args) throws Exception, ExecutionException {
//第三步 创建Callable的实现类的对象
MyCall myCall = new MyCall();
//第四步 创建FutureTask对象 并将Callable实现类的对象作为实参传入
FutureTask<Integer> ft = new FutureTask<>(myCall);
//第五步 创建Thread的对象并调用start方法
Thread thread = new Thread(ft);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
/*
* 调用get方法,会阻塞当前的线程(主线程),等到分线程返回 返回值 再继续向下执行。
*/
Integer integer = ft.get();
System.out.println(integer);
}
}
/*
* 第一步 实现Callable接口
* 第二步 重写call方法
* 第三步 创建Callable的实现类的对象
* 第四步 创建FutureTask对象 并将Callable实现类的对象作为实参传入
* 第五步 创建Thread的对象并调用start方法
*/
//第一步 实现Callable接口
class MyCall implements Callable<Integer>{
//第二步 重写call方法
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
return 101;
}
}
。。。。。。。