程序、进程、线程区别
程序:是一段代码,静态的
进程:是程序的一次执行过程;正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。(进程是动态的)是一个动的过程,进程的生命周期:有它自身的产生,存在和消亡的过程
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的
并行和并发
并行:多个CPU同时执行多个任务
并发:一个CPU”同时“执行多个任务(采用时间片切换)
创建线程的三种方式
第一种:继承Thread类
/*
线程类叫:CreateThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
* 现在想要具备能力,要继承一个类:Thread,这样才具备了争抢资源的能力
* */
public class CreateThread extends Thread {
/*
一会线程对象要争抢资源,这个线程要执行的任务是什么?这个任务要放在方法中,
* 并且这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
* 然后线程的任务/逻辑写在run方法中
* */
@Override
public void run() {
//输出1-10
for (int i = 1; i < 11; i++) {
System.out.println(this.getName()+i);
}
}
//构造器
public CreateThread(String name) {
super(name);//调用父类的有参构造器
}
}
public class Test {
public static void main(String[] args) {
//给主线程设置名字
//获取当前线程的方法:Thread.currentThread()
Thread.currentThread().setName("主线程");
//主线程中也要输出10个数
for (int i = 1; i <=10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//制造其他线程,跟主线程争抢资源
//具体的线程对象:子线程
CreateThread ct=new CreateThread("子线程");//2构造器
//ct.setName("子线程");1.set方法
//ct.run();//调用run方法,执行线程中的任务-------->这个run方法不能直接调用,直接调用就会被当作一个普通方法
//想要子线程真正起作用必须要启动线程
ct.start();//start方法是Thread类中的方法
//主线程中也要输出10个数
for (int i = 1; i <=10 ; i++) {
System.out.println(Thread.currentThread().getName()+"2"+i);
}
}
}
运行结果:
设置和读取线程名字
1、通过getName,setName方法来进行设置读取,代码见上
2、通过构造器设置名字,代码见上
习题:买火车票
原理:每个窗口都是一个具体的线程对象
public class BuyTicketThread extends Thread{
//一共10张票
static int ticketNum=10;//这10张票要共享,无论有几张票,无论有几个线程对象,这10张票都要共享,不能多出来
//static的作用就保证了共享
//每个窗口都是一个线程对象:每个对象执行的代码要放入run方法中
//必须重写一个run方法
@Override
public void run() {
//每个窗口后有100个人在抢票
for (int i = 1; i <= 100; i++) {
if (ticketNum > 0) {//票数大于0,我们才抢票
System.out.println("我在"+this.getName()+"买到了第" + ticketNum-- + "张火车票");
}
}
}
//加带参构造器设置线程名字
public BuyTicketThread(String name) {
super(name);
}
}
public class TestBuyTicket {
public static void main(String[] args) {
//多个窗口抢票,三个窗口三个线程对象
BuyTicketThread t1=new BuyTicketThread("窗口1:");
t1.start();
BuyTicketThread t2=new BuyTicketThread("窗口2:");
t2.start();
BuyTicketThread t3=new BuyTicketThread("窗口3:");
t3.start();
}
}
结果:出现了0有问题,后续会说明
public class BuyTicket implements Runnable{
int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
if (ticketNum > 0) {//票数大于0,我们才抢票
System.out.println("我在"+Thread.currentThread().getName()+"买到了第" + ticketNum-- + "张火车票");
}
}
}
}
public class TestTicket {
public static void main(String[] args) {
//线程对象共享一个就可以了
BuyTicket bt=new BuyTicket();
Thread t1=new Thread(bt,"窗口1");
t1.start();
Thread t2=new Thread(bt,"窗口2");
t2.start();
Thread t3=new Thread(bt,"窗口3");
t3.start();
}
}
第二种:实现Runnable接口
/*
* TestRunnable实现了这Runnable接口,才会变成一个线程类
* */
public class TestRunnable implements Runnable{
//在这里run方法不是重写,而是实现
//线程中想要执行的任务要放到run方法中
@Override
public void run() {
//输出1-10
for (int i = 1; i <=10 ; i++) {
//获取当前线程名字的方法
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建子线程对象
TestRunnable tr=new TestRunnable();
//tr没有start方法,必须借助Thread类完成
Thread t=new Thread(tr,"子线程");
t.start();
//主线程:打印1-10
for (int i = 1; i <=10 ; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
运行结果:
第二种方式买火车票,见火车票上
第三种:实现Callable接口
上述两种方式不足的地方:
1、run方法不能有返回值
2、不能抛出异常
public class TestCallable implements Callable<Integer> {
/*
* 1.实现callable接口可以不带泛型,如果不带泛型,那么call方法返回值就是object类型
* 2.如果带泛型,那么call方法的返回值就是泛型对应的类型
* 3.从call方法我们看到,这个方法有返回值,还可以抛出异常
* */
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);//返回10以内的随机数
}
}
public class TestC {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义一个线程对象
TestCallable tc=new TestCallable();
FutureTask ft=new FutureTask(tc);//runnable接口的实现类
Thread t=new Thread(ft);
t.start();
//获取线程得到的返回值,用ft,get抛异常
Object o = ft.get();
System.out.println(o);
}
}
如果想有返回值的话,考虑此种方法
线程的生命周期
1、线程开始----》线程消亡
2、线程经历了哪些阶段;重点
不完全图:
完整图:
线程的常见方法
设置优先级
public class TestThread extends Thread{
@Override
public void run() {
for (int i = 1; i <=10; i++) {
System.out.println(i);
}
}
}
class TestThread01 extends Thread{
@Override
public void run() {
for (int i = 20; i <=30 ; i++) {
System.out.println(i);
}
}
}
class test01{
public static void main(String[] args) {
TestThread tt1=new TestThread();
tt1.setPriority(2);//优先级别低
//System.out.println("priority:"+tt1.getPriority());
tt1.start();
TestThread01 tt2=new TestThread01();
tt2.setPriority(9);//优先级别高
tt2.start();
}
}
join方法
public class TestJoin extends Thread{
@Override
public void run() {
for (int i = 1; i <=10; i++) {
System.out.println(this.getName()+ "--------"+i);
}
}
public TestJoin(String name) {
super(name);
}
}
class Test02{
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <=20; i++) {
if(i==6){
TestJoin tj=new TestJoin("子线程");
tj.start();
tj.join();//半路杀出个程咬金
}
System.out.println(Thread.currentThread().getName()+ "--------"+i);
}
}
}
运行结果:
sleep方法
人为的制造阻塞事件
public class TestSleep {
public static void main(String[] args) {
long startTime=System.currentTimeMillis();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime=System.currentTimeMillis();
System.out.println("等待时间:"+(endTime-startTime));//3014
System.out.println("0000000");
}
}
案例:完成秒表的功能
public class TestClock {
public static void main(String[] args) {
//2.定义时间格式
DateFormat df=new SimpleDateFormat("HH:mm:ss");
while(true){
//1.获取当前时间
Date d=new Date();
//3.按上面定义的格式将date类型转为指定格式的字符串
System.out.println(df.format(d));
//4.等待1秒后在输出
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
setDaemon方法
设置伴随线程
public class SetThread1 extends Thread{
@Override
public void run() {
for (int i = 1; i <=1000 ; i++) {
System.out.println("子线程----"+i);
}
}
}
class set01{
public static void main(String[] args) {
//创建并启动线程
//将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也停止
SetThread1 st=new SetThread1();
st.setDaemon(true);//设置伴随线程,先设置再启动
st.start();
//主线程中输出1-10
for (int i = 1; i <=10 ; i++) {
System.out.println("main----"+i);
}
}
}
stop方法
public class set01{
public static void main(String[] args) {
for (int i = 1; i <=100 ; i++) {
if(i==6){
Thread.currentThread().stop();//过期方法,不建议使用
}
System.out.println("main----"+i);
}
}
}
线程安全问题(⭐)
上面代码出现了问题:出现了重票,错票 -------》线程安全问题
原因:多个线程在争抢资源的过程中,导致共享的资源出现问题;一个线程还没执行玩,另一个线程就参与进来了,开始争抢。
解决:在程序中加个锁----》加同步----》加同步监视器
方法1:同步代码块
public class BuyTicket implements Runnable{
int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
synchronized (this) {//加锁的位置要自己决定;
//把具有安全隐患的代码锁住即可,如果锁多了效率就会低----》this就是这个锁
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
}
}
}
}
}
public class TestTicket {
public static void main(String[] args) {
BuyTicket bt=new BuyTicket();//实际上只有一个对象
Thread t1=new Thread(bt,"窗口1");
t1.start();
Thread t2=new Thread(bt,"窗口2");
t2.start();
Thread t3=new Thread(bt,"窗口3");
t3.start();
}
}
在继承thread类下的买火车票加锁了之后还有问题
public class BuyTicketThread extends Thread{
final String a="abc";//字符串在内存中独一份
static Object o=new Object();//不加static的话仍然是多把锁
static int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//锁必须是多个线程用的是同一把锁!!!(上厕所红绿杯子案例)
//synchronized ("abc") 1.引用数据类型锁
//synchronized (o)也行 ;2.没有任何业务含义的锁
//synchronized (BuyTicketThread.class) 3.共享的资源
//synchronized (a){a="def";} 4.可更改就不行,定义为final可以
//6.final只是不能更改地址,如果final修饰对象,对象的属性依然可以改
synchronized (BuyTicketThread.class) {//this指的是当前调用run方法的那个对象
if (ticketNum > 0) {
System.out.println("我在" + this.getName() + "买到了第" + ticketNum-- + "张火车票");
}
}
}
}
public BuyTicketThread(String name) {
super(name);
}
}
public class TestBuyTicket {
public static void main(String[] args) {
//多个窗口抢票,三个窗口三个线程对象
//这有三个线程对象,锁 this 没有用
BuyTicketThread t1=new BuyTicketThread("窗口1:");
t1.start();
BuyTicketThread t2=new BuyTicketThread("窗口2:");
t2.start();
BuyTicketThread t3=new BuyTicketThread("窗口3:");
t3.start();
}
}
cpu的切换—》线程的切换
方法2:同步方法
public class BuyTicket implements Runnable{
int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
buyTicket();
}
}
public synchronized void buyTicket(){//锁住的是this
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
}
}
}
这个代码对,因为测试时只有一个对象t
但是对于下面代码来说,锁住方法锁住的是t1,t2,t3,不是一把锁没有用(没加static前)
加一个static就可以解决问题
public class BuyTicketThread extends Thread{
//一共10张票
static int ticketNum=10;//这10张票要共享,无论有几张票,无论有几个线程对象,这10张票都要共享,不能多出来
//static的作用就保证了共享
//每个窗口都是一个线程对象:每个对象执行的代码要放入run方法中
//必须重写一个run方法
@Override
public void run() {
//每个窗口后有100个人在抢票
for (int i = 1; i <= 100; i++) {
//锁必须是多个线程用的是同一把锁!!!
buyTicket();
}
}
public static synchronized void buyTicket(){//不加static锁住的还是this,要加static
//加上static锁住的是同步监视器:BuyTicketThread.class
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
}
}
//加带参构造器设置线程名字
public BuyTicketThread(String name) {
super(name);
}
}
static代码里不能用this
方法3:Lock锁
public class BuyTicket implements Runnable{
int ticketNum=10;
//拿来一把锁:lock是一个接口,用实现类来创建,多态,可以使用不同的实现类
Lock lock=new ReentrantLock();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//使用锁、打开锁:
lock.lock();
try{
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
}
}catch(Exception ex){
ex.printStackTrace();
}finally {
//如果内部代码出现问题,这个锁可能就没有得到释放,为了解决这个问题,使用try-catch-finally
//关闭锁---->即使有异常,这个锁也可以得到释放
lock.unlock();
}
}
}
}
线程同步的缺点
线程通信问题
生产者和消费者
共享问题:static;线程同步
分解1:
第一个问题:通过线程通信解决
第二个问题:通过加锁解决
public class Product {//商品类
//属性:品牌和名字
private String name;
private String brand;
//setter getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
public class Producer extends Thread{//生产者线程
//共享的商品
private Product p;
//确保生产者消费者共享的商品是一个,通过定义构造器
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10; i++) {
if(i%2==0){
//生产费列罗巧克力
p.setBrand("费列罗");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("巧克力");
}else{
//生产哈尔滨啤酒
p.setBrand("哈尔滨");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("啤酒");
}
//将生产信息做一个打印
System.out.println("生产者生产了:"+p.getBrand()+"----"+p.getName());
}
}
}
public class Consumer extends Thread{//消费者线程
//共享商品:
private Product p;
public Consumer(Product p) {//确保商品从外界传过来
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println("消费者消费了:"+p.getBrand()+"----"+p.getName());
}
}
}
public class Test {
public static void main(String[] args) {
//共享的商品
Product p=new Product();
//创建生产者和消费者线程
Producer pt=new Producer(p);
Consumer ct=new Consumer(p);
pt.start();
ct.start();
}
}
分解2:解决了第二个问题
利用同步代码块解决问题
public class Producer extends Thread{//生产者线程
//共享的商品
private Product p;
//确保生产者消费者共享的商品是一个,通过定义构造器
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10; i++) {
synchronized (p){//这个锁需要是生产者和消费者共享的,而p正是共享的
if(i%2==0){
//生产费列罗巧克力
p.setBrand("费列罗");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("巧克力");
}else{
//生产哈尔滨啤酒
p.setBrand("哈尔滨");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("啤酒");
}
//将生产信息做一个打印
System.out.println("生产者生产了:"+p.getBrand()+"----"+p.getName());
}
}
}
}
public class Consumer extends Thread{//消费者线程
//共享商品:
private Product p;
public Consumer(Product p) {//确保商品从外界传过来
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
synchronized (p){
System.out.println("消费者消费了:"+p.getBrand()+"----"+p.getName());
}
}
}
}
利用同步方法解决问题
在商品里面设置同步方法,生产和消费直接调用
public class Product {//商品类
//属性:品牌和名字
private String name;
private String brand;
//setter getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//生产商品
public synchronized void setProduct(String brand,String name){
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
//将生产信息做一个打印
System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
}
//消费商品
public synchronized void getProduct(){
System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());
}
}
public class Producer extends Thread{//生产者线程
//共享的商品
private Product p;
//确保生产者消费者共享的商品是一个,通过定义构造器
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10; i++) {
if(i%2==0){
//生产费列罗巧克力
p.setProduct("费列罗", "巧克力");
}else{
p.setProduct("哈尔滨", "啤酒");
}
}
}
}
public class Consumer extends Thread{//消费者线程
//共享商品:
private Product p;
public Consumer(Product p) {//确保商品从外界传过来
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
p.getProduct();
}
}
}
分解3:
原理:
public class Product {//商品类
//属性:品牌和名字
private String name;
private String brand;
//引入一个灯,ture 红色 false 绿色
boolean flag=false;//默认情况下没有商品,生产者需要先生产 消费者再消费
//setter getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//生产商品
public synchronized void setProduct(String brand,String name){
if(flag==true){//灯是红色,有商品,生产者不生产,等着消费者消费
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//灯是绿色,就生产
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
//将生产信息做一个打印
System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
//生产完之后,灯变红色
flag=true;
//告诉消费者赶紧来消费
notify();
}
//消费商品
public synchronized void getProduct(){
if(!flag){//flag==false没有商品,等待生产者生产
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有商品,消费
System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());
//消费完,变绿色
flag=false;
//通知生产者
notify();
}
}
consumer和producer,test里面不变和分解2一样
※线程通信原理
沿着wait之后的方法,所以不把代码放入else内
注意:wait 和 notify 是必须放在同步方法或代码块里才生效的(因为是在同步的基础上进行线程的通信才是有效的)
注意:sleep 和 wait 的区别:sleep 进入阻塞状态,但没有释放锁;wait进入阻塞状态,但同时释放了锁
Lock锁情况下的线程通信
多个生产者,多个消费者
把等待队列分开,把生产者和消费者放入不同的等待池中
一个锁池,多个等待池
public class Product {//商品类
//属性:品牌和名字
private String name;
private String brand;
//声明一个Lock锁
Lock lock=new ReentrantLock();
//生产者等待队列:
Condition pc=lock.newCondition();
//消费者等待队列:
Condition cc=lock.newCondition();
//引入一个灯,ture 红色 false 绿色
boolean flag=false;//默认情况下没有商品,生产者需要先生产 消费者再消费
//setter getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//生产商品
public void setProduct(String brand,String name){
lock.lock();
try{
if(flag==true){//灯是红色,有商品,生产者不生产,等着消费者消费
try {
//wait();必须结合synchronized
//生产者阻塞,生产者进入等待队列
pc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//灯是绿色,就生产
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
//将生产信息做一个打印
System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
//生产完之后,灯变红色
flag=true;
//告诉消费者赶紧来消费
//notify();
cc.signal();
}finally {
lock.unlock();//为了防止开不了锁,使用try-finally
}
}
//消费商品
public void getProduct(){
lock.lock();
try{
if(!flag){//flag==false没有商品,等待生产者生产
try {
//wait();消费者等待,消费者线程进入等待队列
cc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有商品,消费
System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());
//消费完,变绿色
flag=false;
//通知生产者
//notify();
pc.signal();
}finally {
lock.unlock();
}
}
}