并行:在同一时刻,多个指令在多个CPU上同时执行
并发:在同一时刻,多个指令在单个CPU上交替执行
进程指在内存中运行的应用程序。
每个进程都有自己独立的一块内存空间。在Windows系统中,一个运行的应用程序就是一个进程。
线程是进程的执行单元,是CPU调度的最小单位。
一个进程可以由多个线程组成,线程共享进程的所有资源。
一个进程如果有多个线程在执行,则称为多线程程序
计算机中的CPU,在任意时刻只能执行一条机器指令。
每个线程只有获得CPU的使用权才能执行代码。
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机一个线程执行,Java使用的是抢占式调度。
步骤:
1.继承Thread类
2.重写run方法,方法体中写要执行的语句
3.创建自定义线程类对象
4.调用start方法,启动线程。JVM会自动执行run方法
//自定义线程类
public class MyThread extends Thread{
//1.继承Thread类
//2.重写run方法,方法体中写要执行的语句
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("thread"+i);
}
}
}
public static void main(String[] args) {
//3.创建自定义线程类对象
MyThread thread = new MyThread();
//4.调用start方法,启动线程。JVM会自动执行run方法
thread.start();
//main线程继续执行
for (int i = 0; i < 10; i++) {
System.out.println("main"+i);
}
}
start方法和run方法的区别:
start是启动线程的方法,线程启动后会自动执行run方法
run方法就是线程要执行的代码
步骤:
1.自定义类实现Runnable接口(也叫做任务类)
2.重写接口的run方法
3.创建任务类的对象
4.创建Thread类的对象,把任务对象作为构造方法的参数。
5.调用start方法,启动线程
//1.自定义类实现Runnable接口(也叫做任务类)
public class MyRun implements Runnable{
//2.重写接口的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("runable"+i);
}
}
}
public static void main(String[] args) {
//3.创建任务类的对象
MyRun myRun = new MyRun();
//4.创建Thread类的对象,把任务对象作为构造方法的参数。
Thread thread = new Thread(myRun);
//5.调用start方法,启动线程
thread.start();
}
方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,子类中可以直接使用Thread类中的方法 | 扩展性较差,子类不能再继承其他的类 |
实现Runnable接口 | 扩展性强,任务类实现接口后还可以继承其他的类 | 编程相对复杂,任务类不能直接使用Thread类中的方法 |
项目 | Value |
---|---|
String getName() | 获取当前线程名称 |
void setName(String name) | 设置线程名称 |
static Thread currentThread() | 获取当前正在执行的线程对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒(休眠时让出CPU执行权) |
void setPriority(int newPriority) | 设置线程优先级(1-10个等级,10为最高,5为默认优先级) |
int getPriority | 获取当前线程优先级 |
public class MyRun implements Runnable{
@Override
public void run() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程名字
String name = t.getName();
for (int i = 0; i < 10; i++) {
System.out.println(name+i);
}
}
}
public static void main(String[] args) {
MyRun myRun = new MyRun();
Thread thread = new Thread(myRun);
//设置线程的名称
thread.setName("线程R");
thread.start();
//main线程的名称
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);//main方法的线程名称就叫做main
}
线程优先级:
线程优先级从低到高有1-10级,通常CPU会优先执行优先级较高的线程任务
但这也不是绝对的,因为线程执行还是有随机性的,只是概率上来说优先级越高的线程越有机会先执行。
public static void main(String[] args) {
//新建两个线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t1"+i);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t2"+i);
}
}
});
//打印线程优先级
System.out.println(thread1.getPriority());
System.out.println(thread2.getPriority());
//设置优先级
thread1.setPriority(1);
thread2.setPriority(10);
thread1.start();
thread2.start();
}
当多个线程访问共享数据,且多个线程对共享数据有更新操作时,就容易出现线程安全问题。
Java中提供了同步机制来解决线程安全问题,实现有三种:
同步机制:对操作共享数据的代码加锁,解决线程安全问题
1.同步代码块
格式:
synchronized(tongbusuo){
有线程安全问题的代码
}
原理:
在多线程环境下,多个线程会抢占同步代码块中的锁对象。当一个线程获取锁,就可以成功进入同步代码块,其他线程获取不到锁,需要在同步代码块外面等待(阻塞)。
获得锁的线程执行完同步代码块后会释放锁,此时所有等待的线程会重新争夺锁对象(释放锁的线程也会再次参与争夺)。
抢到锁的线程就可以进行同步代码块执行。
实例:模拟卖票
//模拟窗口卖票
public class Ticket implements Runnable{
private static int ticket = 100;
@Override
public void run() {
Thread thread = Thread.currentThread();
String name = thread.getName();//获取线程名字
//卖票
while (true) {
//同步代码块
synchronized(Ticket.class){
//在synchronized中存入唯一的对象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设只剩最后一张票,在票数--之前,这时票数还是1符合判断,又会导致很多线程进入。
ticket--;
System.out.println(name + "成功售票,还剩" + ticket);
} else {
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(new Ticket(),"线程1");
Thread thread2 = new Thread(new Ticket(),"线程2");
Thread thread3 = new Thread(new Ticket(),"线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
出现线程安全问题。
加上同步代码块后:
2.同步方法
//模拟窗口卖票
public class Ticket implements Runnable {
private static int ticket = 100;
@Override
public void run() {
//卖票
while (true) {
sale();
if (ticket == 0) {
break;
}
}
}
//定义同步方法,把有安全问题的代码写在方法中
public synchronized void sale() {
//同步方法的锁对象是自动提供的
// 成员方法:锁是this,要保证唯一,得确保当前Ticket类只创建一个对象,若创建多个,无法保证线程安全
//静态方法:锁是当前类字节码Ticket.class
Thread thread = Thread.currentThread();
String name = thread.getName();//获取线程名字
if (ticket > 0) {
//假设只剩最后一张票,在票数--之前,这时票数还是1符合判断,又会导致很多线程进入。
ticket--;
System.out.println(name + "成功售票,还剩" + ticket);
}
}
}
同步代码块和同步方法区别:
同步方法是锁住方法中所有代码,同步代码块可以锁定指定代码,锁的控制粒度更细
同步方法不能指定锁对象,同步代码块可以指定锁对象
建议使用同步代码块,更加灵活
3.Lock锁机制
JDK1.5开始,并发包(java.util.concurrent)中新增了Lock接口和相关实现类来实现锁的功能,它提供了与synchronized关键字类似的同步功能,Lock中提供了获得锁和释放锁的方法:
void lock(): 获得锁
void unlock():释放锁
Lock接口的常用实现类为ReentrantLock
//模拟窗口卖票
public class Ticket implements Runnable {
private static int ticket = 100;
private static Lock lock = new ReentrantLock();
@Override
public void run() {
Thread thread = Thread.currentThread();
String name = thread.getName();//获取线程名字
//卖票
while (true) {
//加锁
//发现程序跟上面两种方式有点区别
//当前程序不会停止,因为锁上之后执行if判断语句,如果执行if可以解锁,
//如果进入else会导致无法解锁,这个程序就停住了,我们在else中也写上unlock
//结论:一定要解锁
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设只剩最后一张票,在票数--之前,这时票数还是1符合判断,又会导致很多线程进入。
ticket--;
System.out.println(name + "成功售票,还剩" + ticket);
} else {
lock.unlock();
break;
}
//解锁
lock.unlock();
//可以用finally使代码更简便
/*try {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设只剩最后一张票,在票数--之前,这时票数还是1符合判断,又会导致很多线程进入。
ticket--;
System.out.println(name + "成功售票,还剩" + ticket);
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}*/
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如果你对本文有疑问,你可以在文章下方对我留言,敬请指正,对于每个留言我都会认真查看。