文章目录
一、并发和并行
并行:两个或多个任务在同一时间点执行
并发:两个或多个任务在同一个时间段执行
单核CPU只能实现并发
二、进程和线程
进程:
正在运行中的程序;
作用:用来封装线程,为线程提供资源(比如:内存资源)
线程:
线程是进程中独立的执行单元
作用:来执行独立的任务(执行代码)
三、线程的创建
创建步骤:
- 创建子类集成Thread类
- 重写Thread类中的run方法
使用步骤:
- new一个上面写好的对象
- 调用start方法(线程会自动调用run方法)
public class test {
public static void main(String[] args) {
one one = new one();
one.start();
}
}
public class one extends Thread{
@Override
public void run() {
System.out.println("heiheihei");
}
}
1.主线程和子线程
主线程:
程序启动时内部自动创建执行main方法的线程称为主线程
子线程:
除了主线程以外的所有线程被称为子线程
区分:
根据线程名称来判断,主线程名称是main,并且执行main方法
子线程默认名字是:Thread-序号
2.线程的运行模式和原理
2.1 分时式模式:
所有线程轮流获得CPU的使用权,并且平均分配给每个线程使用
2.2 抢占式模式:
优先级较高的线程先获得CPU的概率更高,谁抢到谁执行,但不一定会执行完,就会再次被抢走,执行别的线程代码;
Java中的线程就属于这个模式。
注意要点:
- 在同一个线程中,代码是从上往下执行的
- 每个线程都会有自己的独立栈空间
- 所有线程共享进程的堆空间
3.Thread常用方法
public String getName();
获取线程名
public void setName();
设置线程名
pulbic static void sleep(long millis);
让当前线程休眠,单位:毫秒
public static Thread currentThread();
获取当前线程对象
public class test {
public static void main(String[] args) {
one one = new one();
one.start();
System.out.println(one.getName());
one.setName("123456");
System.out.println(one.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
for (int i = 0; i <10 ; i++) {
System.out.println("main"+"\t"+i);
}
}
}
public class one extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("run"+"\t"+i);
}
}
}
4.线程的创建方式
实现Runnable接口:
步骤:
- 创建类实现runnable接口
- 实现run方法
- 创建Thread类,封装实现了runnable接口的对象
- 调用Thread对象的start方法
public class test {
public static void main(String[] args) {
Thread th = new Thread(new two());
th.start();
}
}
public class two implements Runnable{
@Override
public void run() {
System.out.println("实现Runnalble接口");
}
}
继承Thread:
public class test {
public static void main(String[] args) {
one one = new one();
one.start();
}
}
public class one extends Thread{
@Override
public void run() {
System.out.println("heiheihei");
}
}
实现Runnable接口的好处:
- 解除Java类单继承的局限性
- 可以更好的在多个线程之间共享数据
- 为线程池提供前提条件
匿名内部类创建线程:
匿名内部类:
作用:
1.直接创建已知类的子类对象
2.直接创建已知接口的实现类对象
语法:
1.直接创造已知类的子类对象:
new 类名 (){
}
2.直接创造已知接口的实现类对象:
new 接口名 (){
}
代码演示:
public class test {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
System.out.println("匿名内部类");
}
};
t.start();
}
}
public class test {
public static void main(String[] args) {
Runnable run = new Runnable() {
@Override
public void run() {
//这样产生的不属于Thread的子类,所以需要使用
//Thread.currentThread()获取调用本方法的线程
System.out.println("匿名内部类,接口方式");
}
};
Thread thread = new Thread(run);
thread.start();
}
}
5.线程安全
线程安全的概念:
指两个或多个线程操作同一个共享资源的时候,仍然能够得到正确的结果则称为线程安全
实现线程安全的方法:
线程同步:让多个线程按顺序执行
实现线程同步的方式:
1.同步代码块
2.同步方法
3.Lock接口
缺点:
由于同一个时间只能有一个线程操作代码,所以线程安全会导致效率低下
5.1 同步代码块
格式:
synchronized(锁对象){
//在这里写代码块
}
效果:
可以保证同一时间只能有一个线程操作代码块中的代码,但是其中一般只存放操作共享资源的代码块
注意事项:
任意类型的对象都可以充当锁对象;
锁对象必须被所有线程共享(共有一把锁);
代码
public class test {
public static void main(String[] args) {
SaleCollection collection = new SaleCollection();
Thread th = new Thread(collection);
Thread thread = new Thread(collection);
th.start();
thread.start();
}
}
public class SaleCollection implements Runnable {
private static int Collection = 10;
Object object = new Object();
@Override
public void run() {
while (true) {
//这里也可以用成员变量 Object,只要是共享的且只有一个的对象都可以
//但是为了简便和,this完全可以胜任
synchronized (this) {
if (Collection > 0) {
System.out.println(Thread.currentThread().getName() +
"卖了一张票,还剩" + (--Collection));
continue;
}
}
System.out.println("票卖完了!");
break;
}
}
}
5.2 同步方法
格式:
修饰符 synchronized 返回值类型 方法名 (参数列表){
}
效果同上面一样,
能够保证同一时间只有一个线程可以操作方法体中的代码
注意事项:
同步方法的锁对象其实也是有的,只不过都是默认的,而且无法修改。
非静态同步方法的锁对象是this
静态同步方法的锁对象是类名.class
其实同步方法和同步代码块没有太大的区别:
只是同步方法把需要同步的代码块单独拿出来写了一个方法
代码:
public class test {
public static void main(String[] args) {
SaleCollection collection = new SaleCollection();
Thread th = new Thread(collection);
Thread thread = new Thread(collection);
th.start();
thread.start();
}
}
public class SaleCollection implements Runnable {
private static int Collection = 10;
@Override
public void run() {
while (Collection > 0) {
sale();
}
System.out.println("票卖完了!");
}
public synchronized void sale() {
if (Collection > 0) {
System.out.println(Thread.currentThread().getName() +
"卖了一张票,还剩" + (--Collection));
}
}
}
5.3 Lock接口
常用方法:
void lock();//获取锁
void unlock();//释放锁
Lock接口常用实现类:ReentrantLock互斥锁
注意事项:
获取锁和释放锁的方法必须成对出现
格式:
public class ok{
private static int cake=100;
private Lock lock=new ReentrantLock();
public run(){
lock.lock();
//操作共享资源的代码
lock.unlock():
}
}
代码:
public class test {
public static void main(String[] args) {
SaleCollection collection = new SaleCollection();
Thread th = new Thread(collection);
Thread thread = new Thread(collection);
th.start();
thread.start();
}
}
public class SaleCollection implements Runnable {
private static int Collection = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (Collection > 0) {
lock.lock();
if (Collection > 0) {
System.out.println(Thread.currentThread().getName() +
"卖了一张票,还剩" + (--Collection));
}
lock.unlock();
}
System.out.println("票卖完了!");
}
}
6.线程等待和唤醒
方法:
void wait()//等待,让线程释放CPU的使用权,进入无限等待状态,需要替他线程调用 notify() 唤醒
void notify()//唤醒,随机唤醒一个正在等待的线程,让线程进入可执行状态
使用注意事项:
1.必须由锁对象调用
2.必须在同步方法或同步代码块中调用
代码:
public class test {
public static void main(String[] args) {
//容器,用来存放包子
ArrayList<String> list = new ArrayList<>();
//把容器传入线程,共给使用和相互唤醒
Thread bao = new Thread(new baozi(list));
Thread chi = new Thread(new chihuo(list));
bao.start();
chi.start();
}
}
public class chihuo implements Runnable {
//存放包子的容器
private ArrayList list;
//接受包子容器
public chihuo(ArrayList list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (list){
//包子容器有了包子
if (list.size() == 1) {
//吃货吃掉包子
list.remove(0);
//打印信息
System.out.println("吃货吃完包子了,店主快点做包子");
//唤醒另一个携带包子容器的线程
//因为只有一个在等待的线程,所以刚好可以唤醒
list.notify();
}else {
try {
//做好包子了或者包子包子还在
//那么就让这个线程等待
list.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
public class baozi implements Runnable {
//存放包子的容器
private ArrayList list;
//接受包子容器
public baozi(ArrayList list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
list.add("baozi");
System.out.println("店主做好包子了,等待吃货来吃");
list.notify();
} else {
try {
list.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
7.线程的状态(生命周期)
NEW : 新建状态,刚刚创建好,还没有调用start方法;
RUNNABLE: 可运行状态,只有在这种状态下,线程才能去抢占CPU资源,抢到就执行,否则就一直抢
BLOCKED: 阻塞状态,没有获得锁对象
WAITING: 无限等待状态,调用wait()方法后进入,需要其他线程唤醒
TIMED_WAITING: 计时等待状态,调用sleep方法或者wait(long time)进入的状态
TERMINATED: 死亡状态,任务正常结束或者调用了stop方法进入的状态
8.Sleep和wait的区别
sleep在进入睡眠的时候,不会释放锁。
wait在进入等待状态之后,会释放锁。
而线程想要执行,要先获得锁对象,然后才能去抢夺CPU资源执行;
线程池在下篇,已经写好:JAVA学习笔记之线程拓展 - 线程池