1、线程与进程
-
进程
进程指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
进程的内存空间(堆空间和栈空间)是独立的。 -
线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程,也可以包含多个线程。
线程的堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小。 -
线程的状态:
(1)初始化(NEW) 创建线程,但未调用start()方法 (2)运行(RUNNABLE) Java线程中将就绪(ready)和运行中(running)两种状态称为运行(RUNNABLE)状态。调用了start()方法后线程状态变为就绪,就绪状态的 线程获得CPU时间后变为运行。 (3)阻塞(BLOCKED):表示线程阻塞于锁。当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程获得锁时,该线程将变成Runnable状态。 (4)等待(WAITING):一个线程在等待另一个线程执行一个(唤醒)动作时,该线程处于Waiting状态。进入这个状态后必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 (5)超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。 (6)终止(TERMINATED):表示该线程已经执行完毕,因为run方法正常退出而死亡,或被没有捕获的异常终止而死亡。
2、线程的创建方式
-
线程类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码,Java使用线程执行体来代表这段程序流。
-
Thread类
构造方法(1)public Thread() 分配一个新的线程对象 (2)public Thread(String name) 分配一个指定名字的新的线程对象 (3)public Thread(Runnable target) 分配一个带有指定目标新的线程对象 (4)public Thread(Runnable target,String name) 分配一个带有指定目标新的线程对象并指定名字
常用方法
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
(1)、创建线程的方式一(继承Thread类)
1、创建一个类继承Thread类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,所以 run()方法就是线程执行体。
2、在test类的main方法中创建Thread子类的实例,即创建线程对象。
3、调用线程对象的start()方法来启动该线程。
public class MyThread extends Tread{
//重写Thread类的run方法,设置线程任务
@Override
public void run(){
方法体
}
}
public class Test(){
public static void main(){
MyThread mt = new MyThread();
mt.start();
//主线程开启新线程之后继续执行的代码
for (int i = 0;i<1000;i++){
System.out.println("main"+i);
}
}
}
(2)、创建线程的方式二(实现Runnable接口创建线程)
1、创建一个类实现Runnable接口
2、重写Runnable接口中的run方法,该run()方法的方法体就代表了线程需要完成的任务,所以 run()方法就是线程执行体。
3、在test类的main方法中创建Runnable接口的实现类对象
4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5、调用Thread类中的start()方法,开启新的线程,执行run方法
public class RunnableImpl implements Runnable{
//重写Runnable接口中的run方法,设置线程任务
@Override
public void run() {
//新线程执行的代码
}
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl r = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(r);
//调用Thread类中的start方法,开启新的线程,执行run方法
t.start();
//主线程开启新线程之后继续执行的代码
for (int i = 0; i <20; i++) {
System.out.println(i);
}
}
使用Runnable接口的好处
- 避免单继承的局限性,一个类继承了Thread类就不能继承其他的类,但是一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口(伪多继承)。
- 增强了程序的扩展性,降低程序的耦合度,使用Runnable接口把设置线程任务和开启线程相分离,实现类当中,重写run方法,设置线程任务,创建Thread类对象,调用start()方法,开启新线程。
(3)、创建线程的方式三(匿名内部类创建线程)
创建线程有2种方式,所以匿名内部类创建线程也有两种方式:
公式一(继承Thread类):
public static void main(String[] args) {
new Thread(){
//new匿名的继承Thread类的类
//重写父类中的run()方法
@Override
public void run() {
//新线程执行的代码
}
}.start();
}
例:
public static void main(String[] args) {
new Thread(){
//new 匿名的继承Thread类的类
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(i);
}
}
}.start();
}
公式二(实现Runnable接口):
public static void main(String[] args) {
new Thread(new Runnable(){
//new 匿名的实现Runnable接口的类
//重写父类中的run()方法
@Override
public void run() {
//新线程执行的代码
}
}).start();
}
例:
public static void main(String[] args) {
new Thread(new Runnable() {
//new没有名称的实现了Runnable接口的类
//重写接口中的run方法
@Override
public void run() { //实现接口当中run方法
for (int i = 0; i <20 ; i++) {
System.out.println(i);
}
}
}).start();
}
3、线程安全
多个线程同时访问共享的数据,可能会产生安全问题
例:多个窗口同时卖一种票,如果不进行控制, 可以会出现卖重复的现象
public class TicketRunnableImpl implements Runnable {
//初始化票数
private int ticket = 10;
//卖票
@Override
public void run() {
while (ticket > 0) {
/*为了提高线程安全问题出现的几率
让线程睡眠10毫秒,放弃cpu的执行权*/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
开启多线程同时执行
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl r = new RunnableImpl();
//创建3个线程
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//启动新的线程
t0.start();
t1.start();
t2.start();
}
**解决线程安全问题的方法
1、同步代码块synchronized
格式:
synchronized(锁对象){
出现安全问题的代码(访问了共享数据的代码)
}
注意:
1.锁对象可以是任意对象 new Person、new Student …
2.必须保证多个线程使用的是同一个锁对象
3.锁对象的作用:把{}中代码锁住,只让一个线程进去执行
例:
public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private int ticket = 100;
private Object obj = new Object(); //锁对象
//线程任务:卖票
@Override
public void run() {
synchronized (obj){
while (ticket > 0) {
/*为了提高线程安全问题出现的几率
让线程睡眠10毫秒,放弃cpu的执行权*/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
说明:
- 同步中的线程如果没有执行完毕,不会释放锁对象。同步外的线程没有锁对象无法进入同步代码块,此时进入阻塞状态
- 出了同步后,线程会归还锁对象。同步锁保证了在同一时间只能有一个线程访问共享数据
- 程序频繁获得锁、释放锁会降低效率。
2、同步方法synchronized
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
出现安全问题的代码(访问了共享数据的代码)
}
例:
@Override
public void run() {
ticketMethods();
}
public synchronized void ticketMethods(){
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
还有另一种写法(两种写法其实相同,只不过下面这种体现出同步方法中锁的对象为this):
public void ticketMethods(){
synchronized(this){
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
3、静态同步方法
格式:
修饰符 static synchronized 返回值类型 方法名(参数列表){
出现安全问题的代码(访问了共享数据的代码)
}
此处锁对象是谁?
答:当前方法所在类的字节码对象(类名.class)
例:
public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private static int ticket = 10;
private Object obj = new Object(); //锁对象
//线程任务:卖票
@Override
public void run() {
ticketMethods();
}
public static synchronized void ticketMethods(){
while (ticket > 0) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
4、Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中最主要用的方法:
void lock() 获取锁。(用在要锁的代码之前)
void unlock() 释放锁。(用在要锁的代码之后)
用法:
1、创建一个Lock接口的实现类对象ReentrantLock
2、在需要同步锁的代码前使用Lock接口的实现类对象调用lock方法获取锁对象
3、在需要同步锁的代码后Lock接口的实现类对象调用unlock方法释放锁对象
例:
public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private int ticket = 100;
//1.创建一个Lock接口的实现类对象ReentrantLock
Lock l = new ReentrantLock();
//线程任务:卖票
@Override
public void run() {
while (true) {
//获得锁对象
l.lock();
if (ticket > 0){
try {
Thread.sleep(5);
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock(); //释放锁对象
}
}
}
}
}
以上内容查阅了网上资料,参考了其他文章,仅作为自己学习笔记。
参考文章连接:
https://www.kuangstudy.com/bbs/1373202797771706370
https://blog.csdn.net/pange1991/article/details/53860651