------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
一、多线程概述
1.进程与线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
2.多线程
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。
多线程的“同时”执行是人的感觉,在线程之间实际上cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。
二、多线程的使用
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。那在程序中我们怎么使用多线程呢?创建使用多线程有两种方式
1.继承
Java给我们提供了对象线程这类事物的描述。该类是Thread,该类中定义了通过构造函数创建线程对象的方法,提供了run()方法封闭要被线程执行的代码
步骤:
1)继承Thread类。
2)覆盖run方法。将线程要运行的代码定义其中。
3)创建Thread类的子类对象,其实就是在创建线程
4)调用start方法。
//创建线程继承Thread类
class TestThread extends Thread
{
//复写父类中的run方法
public void run()
{
for(int x = 0 ; x < 50 ; x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象,并运行
TestThread t1 = new TestThread();
TestThread t2 = new TestThread();
t1.start();
t2.start();
}
}
运行上述代码每次的结果是不一样的,启动线程的顺序不能保证其执行次序,持续时间也无法保证。哪个线程获取到了cpu的执行权,哪个线程就执行。
2.实现
在学习继承的时候就知道java当中不支持多继承,所以一个类如果有父类的话是不能再继承Thread类的,可类中还有部分代码需要被多个线程同时执行,这时就需要另外一种线程创建方式,实现Runnable接口,将线程要执行的任务封装成了对象。
步骤:
1)定义类实现Runnable接口。
2)覆盖接口中的run方法(用于封装线程要运行的代码)。
3)通过Thread类创建线程对象;
4)将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5)调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
//创建线程实现Runnable接口
class TestThread implements Runnable
{
//复写接口中的run方法
@Override
public void run() {
for(int x = 0 ; x < 50 ; x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//建立Runnable接口子类对象并传递到Thread类对象中,开启线程
TestThread t = new TestThread();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
注意:无论是继承还是实现方式创建线程时,由于父类和接口的run()方法并没有抛出任何异常,所以复写的时候也不能声明异常,如果有异常需要在内部处理或者抛出RuntimeException。
三、同步
现在有一个需求,编写一个多线程程序,每隔30ms输出线程的名字(线程至少3个以上),按照之前学习的创建方式代码如下:
//创建一个PrintThread类,实现Runnable接口,覆盖Runnable接口中的run方法
class PrintThread implements Runnable
{
public void run() {
//循环五次观察结果
for (int x = 0; x < 5; x++) {
//通过System.currentTimeMillis()进行时间验证
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
new RuntimeException("线程休眠被中断");
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
}
}
public class ThreadDemo {
//在主函数中创建4个线程并运行
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintThread pt = new PrintThread();
Thread t1 = new Thread(pt);
Thread t2 = new Thread(pt);
Thread t3 = new Thread(pt);
Thread t4 = new Thread(pt);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
发现结果不符合需求,那为什么会产生这种问题呢?
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行。导致共享数据的错误。
所以解决这个问题的方法就是将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行,这就是同步。在这个需求中每30ms只能输出一个线程的名字,所以需要同步。
1.同步的优缺点
优点:解决了线程安全的问题
缺点:使用同步因为判断锁需要消耗计算机资源,所以相对性能较低
同步嵌套之后容易死锁。
2.使用前提
必须有两个或两个以上的线程,而且这些线程使用的都是一个锁
3.同步的使用方式
分为同步代码块和同步函数,使用synchronized关键字,不能同步变量和类
1)
同步代码块是将被同步的代码封装在代码块中
synchronized(对象锁)
{
(需要被同步的代码)
}
锁可以是任意对象,持有对象锁的可以运行,没有持有锁的就不能运行
将之前程序修改之后如下:
//创建一个PrintThread类,实现Runnable接口,覆盖Runnable接口中的run方法
class PrintThread implements Runnable {
Object obj = new Object();
@Override
public void run() {
//循环五次观察结果
for (int x = 0; x < 5; x++) {
//使用同步代码块,并创建一个Object对象作为锁
synchronized (obj) {
//通过System.currentTimeMillis()进行时间验证
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
new RuntimeException("线程休眠被中断");
}
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
}
}
public class ThreadDemo {
//在主函数中创建4个线程并运行
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintThread pt = new PrintThread();
Thread t1 = new Thread(pt);
Thread t2 = new Thread(pt);
Thread t3 = new Thread(pt);
Thread t4 = new Thread(pt);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
2)
还有一种就是同步函数,就是用关键字修饰函数
同步代码块的锁是一个对象,那同步函数呢?
函数需要被对象调用,那么函数都有一个所属对象引用this。this就是同步函数的锁。
那如果同步函数是静态的,它的锁又是什么呢?
我们都知道java中静态是随着类的加载而加载,这时没有本类对象,但一定有该类对应的字节码文件对象,所以静态同步函数,使用的锁是该函数所在类的字节码文件对象。类名.class
//创建一个PrintThread类,实现Runnable接口,覆盖Runnable接口中的run方法
class PrintThread implements Runnable
{
public void run()
{
show();
}
//使用同步函数
public synchronized void show()
{
//循环五次观察结果
for (int x = 0; x < 5; x++)
{
//通过System.currentTimeMillis()进行时间验证
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
new RuntimeException("线程休眠被中断");
}
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
}
public class ThreadDemo {
//在主函数中创建4个线程并运行
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintThread pt = new PrintThread();
Thread t1 = new Thread(pt);
Thread t2 = new Thread(pt);
Thread t3 = new Thread(pt);
Thread t4 = new Thread(pt);
t1.start();
t2.start();
t3.start();
t4.start();
}
}