并发与并行
- 并发:两个或多个事件在同一事件段内发生
- 并行:两个或多个事件在同一时刻发生(同时发生)
线程与进程
- 进程: 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创
建、运行到消亡的过程。 - 线程: 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之: 一个程序运行后至少有一个进程,一个进程中可以包含多个线程
Thred类
构造方法
-
public Thread() :分配一个新的线程对象。
-
public Thread(String name):分配一个指定名字的新的线程对象。
-
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
-
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() :返回对当前正在执行的线程对象的引用。
使用继承Thread类的方式创建线程对象
public class Main {
public static void main(String[] args){
//创建线程对象
MyThread thread= new MyThread("新线程");
//开启新线程
thread.start();
for (int i = 0; i <10; i++) {
System.out.println("主线程------》"+i);
}
}
}
class MyThread extends Thread{
//定义线程的名称
public MyThread(String name) {
super(name);
}
//线程的执行方法,当线程启动时执行此方法
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(getName()+"------》"+i);
}
}
}
使用实现Runnable接口的方式创建线程对象
public class Main {
public static void main(String[] args){
//创建线程
Thread thread=new Thread(new MyRunnable(),"新线程");
//开启线程
thread.start();
for (int i = 0; i <10; i++) {
System.out.println("主线程------》"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName()+"----》"+i);
}
}
}
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
线程安全
定义: 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
案例:
public class Ticket implements Runnable{
//总共100张票
private int ticket=100;
@Override
public void run() {
//开启窗口卖票
while (true){
//有票可卖
if(ticket>0){
//出票操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name=Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
public class Main {
public static void main(String[] args){
Ticket ticket = new Ticket();
//创建三个窗口进行卖票
Thread thread1=new Thread(ticket,"窗口1");
Thread thread2=new Thread(ticket,"窗口2");
Thread thread3=new Thread(ticket,"窗口3");
//三个窗口开始卖票
thread1.start();
thread2.start();
thread3.start();
}
}
结果(不唯一):
通过结果可以看到几个线程中的票数不同步,这种问题就是线程不安全。
线程同步的实现方式
- 同步代码块
- 同步方法
- 锁机制
同步代码块
格式
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象可以是任意类型。
- 多个线程对象 要使用同一把锁。
案例
public class Ticket implements Runnable{
//总共100张票
private int ticket=100;
//同步锁中的对象
Object lock=new Object();
@Override
public void run() {
//开启窗口卖票
while (true){
//同步代码块
synchronized (lock){
//有票可卖
if(ticket>0){
//出票操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name=Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
}
同步方法
格式
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
案例:
public class Ticket implements Runnable{
//总共100张票
private int ticket=100;
@Override
public void run() {
sellTicket();
}
public synchronized void sellTicket(){
//开启窗口卖票
while (true){
//有票可卖
if(ticket>0){
//出票操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name=Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
Lock锁
常用的方法
- public void lock() :加同步锁。
- public void unlock() :释放同步锁
案例:
public class Ticket implements Runnable{
//总共100张票
private int ticket=100;
//创建锁对象
Lock lock=new ReentrantLock();
@Override
public void run() {
//开启窗口卖票
while (true){
//加锁
lock.lock();
//有票可卖
if(ticket>0){
//出票操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name=Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
lock.unlock();
}
}
}
线程的状态
线程状态 | 描述 |
---|---|
New(新建状态) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(就绪状态) | 线程创建对象后,其他线程调用start()方法,该线程处于就绪状态,资源已经准备就绪,等待CPU资源。 |
Running(运行状态): | 处于就绪状态的线程获取到CPU资源后进入运行状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait |
Teminated(终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
等待唤醒机制
常用方法:
- wait() – 让当前线程处于“等待(阻塞)状态,直到被其他线程唤醒
- wait(long timeout) – 让当前线程处于“等待(阻塞)状态,直到被其他线程唤醒或等待时间到了
- notify() – 唤醒在此对象监视器上等待的单个线程。
- notifyAll() – 唤醒在此对象监视器上等待的所有线程。
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run(){
synchronized(this){
System.out.println(Thread.currentThread().getName()+"运行了");
// 唤醒主线程
notify();
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
MyThread thread = new MyThread("副线程");
synchronized (thread) {
try {
System.out.println(Thread.currentThread().getName() + " 运行了");
// 启动副线程
thread.start();
System.out.println(Thread.currentThread().getName() + "进入阻塞");
// 主线程进入等待状态,释放锁资源
thread.wait();
System.out.println(Thread.currentThread().getName() + "被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果
main 运行了
main进入阻塞
副线程运行了
main被唤醒了