欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java学习day18~19---多线程

程序员文章站 2022-04-04 09:03:38
...

一.线程和进程(单核)
    进程:CPU中有多个程序“同时”在使用,CPU在一个时间点上只能执行一件事情,而CPU分片段执行不同程序,
            但是切换速率十分快,给人感觉是在同时使用
          进程的作用不是提高执行速度,而是提高CPU的使用率
    线程:一个进程中的执行场景,一个进程有多个线程,如淘宝抢购,都在使用百度搜索
            线程的作用不是提高执行速度,而是提高应用程序的使用率

    线程的存在:
    线程之间的堆内存和方法区内存是共享的,但不同线程都是不同的栈内存,
二.线程的创建和启动
方式1:子类继承Thread,重写里面的run方法,调用start方法启动(也可以不用子类,直接new一个Thread,用匿名内部类的方式)
注意:run方法不能抛出异常

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Processer();
        t.start();   //这个代码告诉JVM给t线程分配一个新的栈,启动之后自动调用run方法
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

class Processer extends Thread
{
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("thread---" + i);
        }
    }
}


//匿名内部类的方式

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                for(int i = 0; i < 100; i++) {
                    System.out.println("thread---" + i);
                }
            }
        };

        t.start();
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

//拉姆达表达式简化后

public class ThreadWork
{
    public static void main(String[] args) {
        new Thread(() -> {
            for(int i = 0; i < 100; i++) {
                System.out.println("thread---" + i);
            }
        }).start();

        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

//运行结果:两个循环随机交互执行
//结论:main方法结束之后只是主线程结束,但是其他线程还有栈帧,所以main方法结束,程序可能还在运行
//注意:如果只调用run方法,那么这就只是普通的方法调用,整个程序只有一个主线程,等到run方法执行结束之后,才会执行main方法中的循环

方式2:子类继承Runnable接口,实现里面的run方法(推荐使用)

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Thread(new Processer());   //Thread t = new Thread(Runnable接口);
        t.start();   //这个代码告诉JVM给t线程分配一个新的栈,启动之后自动调用run方法
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

class Processer implements Runnable
{
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("thread---" + i);
        }
    }
}

三.线程的生命周期
java学习day18~19---多线程

进入就绪状态之后,线程有权利去抢夺CPU时间片,这个时间片就是执行权,抢到之后就可以进入运行状态,当时间
用完而运行还没有结束之后,就会继续返回到就绪状态,继续抢夺时间片,然后继续接着执行run方法,直至方法执行完毕

用第一个创建线程的例子来讲,得到的结果是两个循环交替输出,这就是因为两个线程抢夺时间片,先抢到的先执行,
抢到的时间不够返回来继续抢夺执行,直至线程结束

四.常用的方法

getName();   获取线程名字
currentThread();  获取当前的线程
sleep();   让当前线程休眠一段指定时间,阻塞线程,让CPU腾出时间片,让其他线程执行,是一个静态方法
getPriority();   获取线程优先级
join();  等待某个线程执行结束
interrupt() 可以打断正在等待的线程(包括sleep, join的等待)
yield()      让位,给同一个优先级的线程让位,但是让位时间不固定,也是静态方法

不推荐使用的方法
stop() 让线程停止
suspend() 让线程暂停
resume() 让线程继续


sleep()详解

public class ThreadWork
{
    public static void main(String[] args) throws Exception{
        Thread t = new Thread(() -> {
            for(int i = 0; i < 30; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
        });
        
        t.setName("t1");    //将线程命名为t1
        t.start();   //让线程运行
        t.sleep(5000);  //此时调用sleep方法会让t线程休眠5秒吗   

        System.out.println("hello");
    }
}

解析:不会,因为sleep是静态方法,与对象无关,t.start()相当于是Thread.start(), 而Thread当前是主线程
       所以t线程不会休眠,而是主线程休眠

//interrupt()详解

public class ThreadWork
{
    public static void main(String[] args) throws Exception{
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000000000000L);  //很长时间段的休眠
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            
            for(int i = 0; i < 30; i++) {
                System.out.println("thread" + i);
            }
        });
        
        t.start();   //让线程运行
        Thread.sleep(5000);  //主线程休眠五秒
        t.interrupt();  //五秒后打断t线程的休眠

        System.out.println("hello");
    }
}

//结果:五秒后t线程执行语句,因为休眠被打断
五.线程同步
同步编程模型:t1和t2线程必须等待一个执行完毕之后,再执行另一个
异步编程模型:t1和t2线程各自执行各自的,谁也不等谁引入线程同步的原因:为了数据安全
使用线程同步的条件:
    1.多线程环境    2.共享一个数据        3.共享的数据涉及到修改操作

public class ThreadWork
{
    public static void main(String[] args) {
        Account a = new Account("张三", 5000);
        Thread t1 = new Thread(new Processor(a));
        Thread t2 = new Thread(new Processor(a));
        t1.start();
        t2.start();
    }
}

class Processor implements Runnable
{
    Account a;

    public Processor(Account a) {
        this.a = a;
    }
    public void run() {
        a.withdraw(200);
        System.out.println("取了200,还剩" + a.getBalance());
    }
}

//账户类
class Account
{
    private String actno;
    private int balance;

    public Account(String actno, int balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public String getActno() {
        return actno;
    }

    public void setBalance(int balance) {
        this.balance = balance; 
    }

    public int getBalance() {
        return balance;
    }

    //提供一个取款方法
    public void withdraw(int money) {
        int after = balance - money;
        this.setBalance(after);
    }
}

//运行结果:随机输出4600,4800
//因为两个线程交互了,一个取款完毕之后,还没有更新,第二个有可能也已经执行,所以余额有问题

此时需要两个线程分别执行,引入线程同步

//其他代码不变,修改取款操作

public void withdraw(int money) {   
    synchronized(this) {   //用锁将取款的具体操作锁住
        try{
            Thread.sleep(1000);
        } catch(Exception e) {
        }
        int after = balance - money;
        this.setBalance(after);
    }
}

用synchronized(this)锁住,那么程序执行的时候,就回去寻找this对象锁,找到则执行,执行完毕归还对象锁,否则等待,直到获取对象锁

六.类锁
关于类锁:类锁只有一把,且与对象无关

虽然t1和t2是两个不同的线程,但是公用一个myClass对象,而该类中的方法用的是类锁,类锁只有一把,与对象没有关系

public class ThreadWork {
	public static void main(String[] args) throws Exception {
		myClass mc = new myClass();
		Processor p1 = new Processor(mc);
		Processor p2 = new Processor(mc);

		Thread t1 = new Thread(p1);
		Thread t2 = new Thread(p2);
		t1.setName("t1");
		t2.setName("t2");

		t1.start();
		Thread.sleep(1000); //为了保证t1线程先执行
		t2.start();
	}
}

class Processor implements Runnable {
	myClass mc;
	Processor(myClass mc) {
		this.mc = mc;
	} 
	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mc.m1();
		}
		if(Thread.currentThread().getName().equals("t1")) {
			mc.m2();
		}
	}
}

class myClass
{
	public synchronized static void m1() {
		try{
			Thread.sleep(5000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("m1...");
	}
	
	public synchronized static void m2() {
		System.out.println("m2...");
	}
}

七.死锁

两个线程互相锁住,如两个线程t1,t2,两个对象o1,o2,t1线程先锁住o1对象,然后锁o2对象,t2线程先锁o2对象,再锁o1对象,但是因为两个线程谁都不肯先放,所以进入了僵持状态,形成死锁

public class ThreadWork 
{
	public static void main(String[] args) {
		Object o1 = new Object();
		Object o2 = new Object();

		Thread t1 = new Thread(new T1(o1, o2));
		Thread t2 = new Thread(new T2(o1, o2));

		t1.start();
		t2.start();
	}
}

class T1 implements Runnable
{
	Object o1;
	Object o2;
	T1(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	public void run() {
		synchronized(o1) {
			try {
				Thread.sleep(1000);
			} catch(Exception e) {
					e.printStackTrace();
				}
			synchronized(o2) {}
		}
	}
}

class T2 implements Runnable
{
	Object o1;
	Object o2;
	T2(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	public void run() {
		synchronized(o2) {
			try {
				Thread.sleep(1000);
			} catch(Exception e) {
					e.printStackTrace();
				}
			synchronized(o1) {}
		}
	}
}