进程:计算机中特定功能的程序在数据集上的一次运行
线程:线程是进程的一个单元
多线程:一个进程中允许多个线程同时运行
线程的创建方式
1.继承Thread类--->子类要重写run方法--->通过start方法来启动线程
package com.hut;
/**
* @author 茶茶
* @date 2020-04-05 21:21 新建
*/
public class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
/**
* 这是线程执行的逻辑体
*/
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(name + "下载了" + i + "%");
}
}
}
package com.hut;
/**
* @author 茶茶
* @date 2020-04-05 21:25 新建
*/
public class ThreadTest {
/**
* 主方法本身就是一个线程
* @param args
*/
public static void main(String[] args) {
//创建一个线程对象
MyThread myThread = new MyThread("肖申克的救赎");
//启动该线程用start方法,然后会自己调用run方法
myThread.start();
MyThread myThread1 = new MyThread("当幸福来敲门");
myThread1.start();
}
}
2.实现Runnable接口--->重写run方法--->通过start()方法启动线程
package com.hut;
/**
* @author 茶茶
* @date 2020-04-05 21:39 新建
*/
public class MyThread1 implements Runnable{
private String name;
public MyThread1(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
System.out.println(name + "下载了" + i + "%");
}
}
}
package com.hut;
/**
* @author 茶茶
* @date 2020-04-05 21:43 新建
*/
public class ThreadTest1 {
public static void main(String[] args) {
//创建线程对象
Thread thread = new Thread(new MyThread1("肖申克的救赎"));
Thread thread1 = new Thread(new MyThread1("当幸福来敲门"));
//启动线程
thread.start();
thread1.start();
}
}
两者的区别在于:
如果MyThread继承Thread类:创建对象:MyThread thread = new MyThread();
如果MyThread实现Runnable接口:创建对象:Thread thread = new Thread(new MyThread());
注意:实现Runnable接口这种方式,因为Runnable接口里就只有一个run的抽象方法,其他方法都没有,所以实现了Runnable接口,不能用实现类直接创建实例来调用start方法,因为实现类根本就没有start方法,要借用Thread类来调用start方法,Thread类重载了构造器,在实例Thread对象的时候,就可以把实现了Runnable类的实例作为参数传递进去,调用Thread对象的start方法,就可以执行run方法里的逻辑体了。
线程的执行原理
线程的并发执行通过多个线程不断的切换CPU的资源,这个速度非常快,我们感知不到,我们能够感知到的就是多个线程在并发的进行。
这里解释两个概念:
线程同步:线程排队,先来的先执行,安全
线程异步:线程同时进行,不安全
线程的生命周期
1.新建new
2.准备就绪start
3.运行run
4.阻塞sleep,wait,唤醒阻塞的状态称为准备就绪状态:休眠时间到,notify
5.销毁:线程的对象执行完毕变成垃圾,被回收
静态属性
假如四个窗口同时卖票,票共有100张,票是共享资源,卖完停止
继承Thread类
package com.hut;
/**
* 这个类是卖票的窗口,四个实例就是四个窗口
* @author 茶茶
* @date 2020-04-05 22:25 新建
*/
public class SaleTicketsThread extends Thread{
//用户共享的票:100张
static int tickets = 100;
//创建一个锁对象,是所有线程共享的,synchronized是把锁,要锁住对象,这里锁的是线程对象,当该对象在执行的时候,其他的对象就不能使用该共享资源
static Object obj = new Object();
//窗口名
private String name;
public SaleTicketsThread(String name){
this.name = name;
}
@Override
public void run() {
//买票的动作是持续的,有票就卖
while (true){
synchronized (obj){
if (tickets > 0){
System.out.println(name + "卖出的座位是:" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "卖票结束");
}
}
package com.hut;
/**
* @author 茶茶
* @date 2020-04-05 22:34 新建
*/
public class SalesTest {
public static void main(String[] args) {
SaleTicketsThread s1 = new SaleTicketsThread("窗口1");
SaleTicketsThread s2 = new SaleTicketsThread("窗口2");
SaleTicketsThread s3 = new SaleTicketsThread("窗口3");
SaleTicketsThread s4 = new SaleTicketsThread("窗口4");
s1.start();
s2.start();
s3.start();
s4.start();
}
}
结果
图解:
由于Java只支持类的单继承,接口的多实现,所以以实现接口优先原则
实现Runnable接口
package com.hut;
/**
* 模拟实现Runnable接口创建多线程类,用来卖票
* @author 茶茶
* @date 2020-04-06 10:45 新建
*/
public class SaleTicketsThread1 implements Runnable{
static int tickets = 100;//定义共享资源100张票
static Object obj = new Object();//定义一个锁对象,是共享的,锁住共享资源,其他线程就会被阻塞
private String name;
public SaleTicketsThread1(String name){
this.name = name;
}
@Override
public void run() {
//有票就卖
while (true){
synchronized (obj){
if (tickets > 0){
System.out.println(name + "卖出座位:" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//跳出循环体之后,就相当于票卖完了
System.out.println(name + "票卖完了");
}
}
package com.hut;
/**
* @author 茶茶
* @date 2020-04-06 10:50 新建
*/
public class SalesTest1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new SaleTicketsThread1("窗口1"));
Thread thread2 = new Thread(new SaleTicketsThread1("窗口2"));
Thread thread3 = new Thread(new SaleTicketsThread1("窗口3"));
Thread thread4 = new Thread(new SaleTicketsThread1("窗口4"));
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
结果
图解:
实现Runnable接口的第二种方式
package com.hut;
/**
* @author 茶茶
* @date 2020-04-06 13:20 新建
*/
public class SaleTicketsThread implements Runnable{
int tickets = 100;//100张票
Object obj = new Object();//锁对象
@Override
public void run() {
//有票就卖
while (true){
synchronized (obj){
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "卖票结束");
}
}
package com.hut;
/**
* @author 茶茶
* @date 2020-04-06 13:25 新建
*/
public class SaleTest {
public static void main(String[] args) {
SaleTicketsThread st = new SaleTicketsThread();//创建一个共享对象,共享给四个线程
Thread t1 = new Thread(st,"窗口1");//后面为为t1命名
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
Thread t4 = new Thread(st,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
图解:
在执行这一段代码块的时候,锁着该对象的obj将对象,如果CPU切换线程,调用run方法时,就会去判断obj是否可用,如果obj被锁住了,其他线程就被阻塞,无法执行,当CPU再次切换回来时,执行完这一段代码块,释放锁着的obj对象,这个时候CPU再切换线程,切换到哪个线程,哪个线程就被执行run方法,并再次将obj锁住,知道该线程执行完这段代码块后,接着是释放obj对象,就是这样一个循环反复的过程,加锁能保证数据安全,但是执行效率变低。
synchronized (obj){
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
}else {
break;
}
}
除了锁住obj对象,锁住代码块之外,还可以将代码块当都放在一个方法里。
改写SaleTicketsThread
package com.hut;
/**
* @author 茶茶
* @date 2020-04-06 13:20 新建
*/
public class SaleTicketsThread implements Runnable{
int tickets = 100;//100张票
Object obj = new Object();//锁对象
@Override
public void run() {
//有票就卖
while (true){
if (finishSale()){//返回isFinish,如果为真,代表卖票结束,跳出循环体
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "卖票结束");
}
/**
* 卖票逻辑体
* @return
*/
public boolean finishSale(){
synchronized (obj){
boolean isFinish = false;//为true代表卖完了
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
}else {
//tickets == 0
isFinish = true;
}
return isFinish;
}
}
}
图解:
再次改写SaleTicketThread方法
如果方法里全是要锁的代码,就可以改写,将synchronized加到方法上,这个时候就不需要obj对象了,那这又是锁住了什么对象呢?
package com.hut;
/**
* @author 茶茶
* @date 2020-04-06 13:20 新建
*/
public class SaleTicketsThread implements Runnable{
int tickets = 100;//100张票
@Override
public void run() {
//有票就卖
while (true){
if (finishSale()){//返回isFinish,如果为真,代表卖票结束,跳出循环体
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "卖票结束");
}
/**
* 卖票逻辑体
* @return
*/
public synchronized boolean finishSale(){
boolean isFinish = false;//为true代表卖完了
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
}else {
//tickets == 0
isFinish = true;
}
return isFinish;
}
}
图解:
切记:第一synchronized锁住的是对象,第二该对象必须是一个共享的对象,要么创建一个锁对象(static Object obj = new Object()),存放在数据共享区中,要么就用本身对象,但是多线程操作的线程对象操作的对象就必须是本身对象,就像st对象一样,
比如下面的分析,如果下面用synchronized(this),就错了,锁this的话,就锁的new SaleTicketThread()对象了,该对象一共有四个,并且都是调用自己run方法,执行的都是自己run方法里synchronized的代码块,互不相关,锁this就没有意义了,其实下面run方法后应该画出每个对象一个synchronized代码块的,但是位置不够了,就指到一起了,应该每个obj指向数据共享区的那个obj
上面图片更改:
总结:
由于局限性,接口实现优先于继承类,所以最好用实现Runnable接口,
实现Runnable接口,最好就定义一个实现类对象,分别给多个线程共享着使用,节约资源嘛,分别给多个线程不同的名字就好了
给名字:Thread t1 = new Thread(实现类对象,"名字");
然后保证数据的安全性,由于这种实现方式操作的永远是一个对象,所以这种方式用synchronized锁代码块可以,锁方法也可以,
锁代码块的时候,用synchronized(obj)可以,用synchronized(this)也可以,再次说明,因为操作的一直一直是同一个对象,是一个共享对象,所以锁obj可以,锁this也可以。
如果用继承Thread类,就必须锁obj对象哦,因为是多个子类对象操纵多个run方法,执行多个锁代码块,所以锁的条件就必须是静态obj对象哦。