一、线程相关概念
1.进程:进程是程序的一次执行过程,或是正在运行的一个程序,进程是动态的,有它自己的产生、存在和消亡的过程。
2.线程:线程是由进程创建的,是进程的一个实体。
单线程:同一时刻,只能执行一个线程。
多线程:同一时刻,允许执行多个线程。
并发:同一时刻,多个线程交替执行,给人一种多个线程同时执行的错觉,单核cpu实现的任务就是并发。
并行:同一时刻,多个线程同时执行,多核cpu可以实现并行。
二、线程的基本使用
1.示例如下(通过继承Thread来创建线程):
public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();//线程产生
cat.start();//启动线程
System.out.println("主线程"+Thread.currentThread().getName()+"继续");// main线程的名字
for (int i = 0; i < 10; i++) {
System.out.println("main线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Cat extends Thread{
int times=0;
@Override
public void run() {
while(true){
System.out.println("Cat hello"+(times++)+Thread.currentThread().getName());
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times==8){
break;
}
}
}
}
1)当一个类继承了Thread类时,这个类就可以当作线程使用,我们可以重写run()方法,实现自己的业务逻辑,以上示例Cat继承了Thread。
2)Cat.run()方法只是一个普通的方法,我们要启动线程,得调用start()方法。
3)当启动子线程时,主线程不会阻塞,会继续执行,此时主线程和子线程交替执行。
4)java是单继承的,所以当一个类继承了Thread后别的类就不能继承了,所以有另外一个创建线程的方法,即通过继承Runnable接口来创建线程。
2.通过实现接口Runnable接口来开发线程:
public class Thread02 {
public static void main(String[] args) {
Apple apple = new Apple();//这里不能直接apple.start()
Thread thread=new Thread(apple);
thread.start();
}
}
class Apple implements Runnable{
int count=10;
@Override
public void run() {
while(count>0){
System.out.println(Thread.currentThread().getName()+"苹果数量:"+(count--));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
3.多线程执行
继承Thread和实现Runnable的区别:
在本质上,继承Thread或实现Runnable没有区别,Thread在底层也实现了Runnable接口。实现Runnable接口方式更加适合多个线程共享一个资源,并且可以避免单继承的限制。
模拟三个窗口同时售票100张:
public class Thread03 {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
new Thread(sellTicket,"窗口1").start();
new Thread(sellTicket,"窗口2").start();
new Thread(sellTicket,"窗口3").start();
}
}
class SellTicket implements Runnable{
private static int ticketNum=100;//共享资源
@Override
public void run() {
while (true){
if(ticketNum<=0){
System.out.println("票已经售完");
break;
}
System.out.println(Thread.currentThread().getName()+"正在卖出第"+(ticketNum--)+"张票");
}
}
}
4.线程常用方法
1)interrupt()中断线程,并没有真正结束线程,一般用于中断正在休眠的线程。
2)yield:线程的礼让,线程礼让的时间不确定,所以不一定能礼让成功。
3)join:线程的插队,线程一旦插队成功,则一定先进行插队线程的所有任务。
5.用户线程和守护线程
用户线程:任务线程,当线程的任务执行完或通知方式结束。
守护线程:为用户线程服务,当用户线程执行完,守护线程自动结束,常见的守护线程有:垃圾回收机制。
6.线程同步机制
1)在多线程编程,有一些敏感数据不允许被多个线程同时访问,此时就用到线程同步访问,保证数据在任何时刻,最多有一个线程访问,从而保证数据完整性。
2)当有一个线程对内存地址操作时,其他线程都不能对此内存地址进行操作,直到该线程完成访问,其他线程才能对该内存进行操作。
同步代码块:
sychronized(对象){//只有得到对象的锁才能操作同步代码
//需要被同步的代码块
}
还可以放在方法声明中,表示整个方法为同步代码块:
public synchronized void m(String name){
//需要被同步的代码块
}
7.分析同步原理
1)在java语言中引入了互斥锁的概念,来保证共享数据操作的完整性。
2)每个对象都对应于一个可称为互斥锁的标记,这个标记保证在任意时刻,只有一个线程访问该对象。
3)关键字sychronized来与互斥锁联系,当一个对象被sychronized修饰时,表明该对象在任意时刻只能由一个线程访问。
4)同步方法的局限性:降低了程序执行效率。
5)非静态的同步方法可以为this,也可以是其他对象,要求是同一个对象。
6)静态的同步方法的锁为当前类本身。
8.线程的死锁
多个线程都占用了对方的锁资源不肯相让,导致了死锁。
模拟死锁:
public class DeadLock_ {
public static void main(String[] args) {
example A = new example(true);
A.setName("线程A");
example B = new example(false);
B.setName("线程B");
A.start();
B.start();
}
}
class example extends Thread{
Object o1=new Object();
Object o2=new Object();
boolean loop;
public example(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
if(loop){
synchronized (o1) {
System.out.println("进入1" + Thread.currentThread().getName());
synchronized (o2) {
System.out.println("进入2" + Thread.currentThread().getName());
}
}
}else{
synchronized (o2) {
System.out.println("进入3" + Thread.currentThread().getName());
synchronized (o1) {
System.out.println("进入4" + Thread.currentThread().getName());
}
}
}
}
}
分析:当loop为true时,A线程先获取o1线程锁,当获取不到o2线程锁时会阻塞。当loop为false时,B线程先获取o2线程锁,获取不到o1线程锁时会阻塞。造成线程死锁。
8.释放锁
以下情况会释放锁:
1)当前线程的同步代码、同步方法块执行结束。
2)当前线程的同步代码块、同步方法执行时遇到break或return。
3)当前线程的同步代码块、同步方法执行时遇到了线程对象的wait()方法,当前程序暂停,锁释放。
4)当前线程的同步代码、同步方法块遇到未处理的Error或Exception,导致异常结束。
以下情况不会释放锁:
1)线程执行同步代码块或同步方法时,调用sleep()或yield()暂停当前程序的执行。
2)线程执行同步代码块或同步方法时,调用该线程的suspend()方法将该线程挂起。