Java多线程入门
进程/线程是什么?
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统执行的基本单元,在传统的操作系统中,进程即时基本的分配但愿,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
白话:
进程就是操作系统中运行的一个程序,QQ.exe,music.exe,word.exe,这就是多个进程
线程:每个进程中都存在一个或者多个线程,比如用word写文章时,这样记忆才是最方便的。
并发/并行是什么?
做并发编程前,必须首先理解什么是并行,什么是并发。
并发和并行是两个非常容易混淆的概念。它们都可以表示两个或多个任务一起执行,但是偏中点有点不同。并发偏重于任务交替执行。而多个任务之间有可能还是串行的。并发是逻辑上同时发生,而并行是物理的同时发生。
严格以上来说,并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交替的,一会运行任务一,一会又运行任务二,系统会不停的在两者之间切换。但对于外部观察者来说,即时多个任务是串行并发的,也会造成是对多个任务并行执行的错觉。
实际上,如果系统只有一个CPU,而现在使用多线程或者多线程任务,那么真实环境中这些人不可能真是并行的,毕竟一个CPU一次只能执行一条指令,这种情况下多线程或者多线程任务就是并发的,而并不是并行的,操作系统会不停的切换任务。真正的并发也只能够出现在拥有多个CPU的系统中(多核CPU)。
**并发的动机:**在计算能力恒定的情况下处理更多的任务,就像我们的大脑,计算能力相对恒定,要在一天中处理更多的问题,我们就必须具备多任务的能力,现实工作中有很多事情可能会中断你的当前任务,处理这种多任务的能力就是你的并发能力。
**并行的动机:**用更多的CPU核心更快的完成任务,就像一个团队,一个脑袋不够用了,,一个团队来一起处理一个任务。
线程的状态
java的线程有6种状态
public enum State {
//线程刚创建
NEW,
//在jvm中正在运行的线程
RUNNABLE,
//线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行
BLOCKED,
//等待状态
WAITING,
//调用sleep() join() wait()方法可能导致线程处于等待状态
TIMED_WAITING,
//线程执行完毕,已经推出
TERMINATED;
}
创建一个线程的方法
Thread
实现了Runnable
接口,我们创建一个线程只需要创建一个类实现Runnable即可,或者继承Thread,重写run方法。实例化这个对象并调用start()
方法
public class demo01 {
public static void main(String[] args) {
new MyThread2().start();
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"这是一个实现了Runnable的线程");
}
}
class MyThread2 extends Thread {
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"这是一个继承Thread的线程");
}
}
线程通信wait、sleep、notify
- wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或者notifyAll()方法。
- notify() 唤醒正在等待对象监视器的单个线程。
- sleep(long milles) 使当前线程以指定的毫秒暂停。
wait和sleep的区别
1、来自不同的类
这两个方法来自不同的类,sleep来自Thread类,而wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
2、有没有释放锁(释放资源)
最主要的区别是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep是线程被调用时,占用cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入了等待池等待,让出系统资源,其他线程可以占用cpu。
sleep(100L)是占用cpu,线程休眠100毫秒,其他线程不能再占用cpu资源,wait(100L)是进入等待池中等待,交出cpu等系统资源供其他进程使用,在这100毫秒中,该线程可以被其他线程notify,但不同的是其他在等待池中的线程不被notify不会出来,但这个线程在等待100毫秒后自动进入就绪队列等待系统分配资源,换句话说,sleep(100)在100毫秒后肯定会运行,但wait在100毫秒后还有等待os调用分配资源,所以wait(100)的停止运行时间是不确定的,但至少是100毫秒。
就是说sleep有时间限制的就像闹钟一样到时候就叫了,而wait是无限期的除非用户主动notify。
3、使用范围不同
wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
Lock锁
传统的synchronized
public class SaleTicketTest1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}
}, "C").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}
}, "C").start();
}
}
class Ticket{
private int number = 100;
public synchronized void saleTicket(){
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + (number--) + "票,还剩下:" + number);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用juc.locks包下的类操作Lock锁+Lambda表达式
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketTest2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(() -> {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
}
}, "C").start();
}
}
class Ticket2{
private Lock lock = new ReentrantLock();
private int number = 100;
public void saleTicket(){
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + (number--) + "票,还剩下:" + number);
}
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
synchronized和lock区别
- 首先synchronized是Java内置关键字,在jvm层面,Lock是个Java类;
- synchronized无法判断是否获取所得状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a线程执行完同步代码块会释放锁;b线程执行过程中发生异常会释放锁),Lock需在finally中手动释放锁(unlock()方法释放锁),否则容易造成死锁;
- 用synchronized关键字的两个线程1和线程2,,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)
- Lock锁适合大量同步代码的同步问题,synchronized锁适合代码少量的同步问题。