0%

Java多线程

实现多线程

通过继承Thread类或者实现Runnable,Callable接口

继承Thread类

构造方法:Thread(),Thread(String threadname),

​ Thread(Runnable target),Thread(Runnable target,String threadname)

重写run()方法:包含线程运行逻辑

start()方法:开启多线程

1
class aThread extends Thread{
2
    @Override
3
    public void run() {
4
        System.out.println("这是run方法。。。");
5
        for(int i=0;i<10;i++){
6
            System.out.println(i+1+"只狼");
7
        }
8
    }
9
}
1
public static void main(String[] args) {
2
    Thread tt=new aThread();
3
    tt.start();
4
    System.out.println("这是主程序  。。。");
5
    for(int i=0;i<10;i++){
6
        System.out.println(i+1+"只饺子");
7
    }
8
}

实现Runnable接口

重写run()方法,同上

1
Thread tr=new Thread(new bThread());
2
//Thread tr=new Thread(new bThread(),"trname");
3
//加上线程名称,可以在run方法中通过Thread.cunrrentThread.getName()获取
4
tr.start();

实现Runnable接口可以资源共享

1
class bThread implements Runnable{
2
    int count;
3
    @Override
4
    public void run() {
5
        System.out.println("这是实现Runnable接口的run方法。。。");
6
        for(int i=0;i<5;i++){
7
            count++;
8
            System.out.println(Thread.currentThread().getName()+count+"只狗");
9
        }//获取当前线程名+count值
10
    }
11
}
12
//主程序
13
Runnable run=new bThread();
14
Thread tr1=new Thread(run,"线程11111:");
15
tr1.start();
16
Thread tr2=new Thread(run,"线程22222:");
17
tr2.start();

结果:

两个线程共享同一个runnable实现类的count值,各自执行,不影响对方

线程生命周期

新建:进程实例声明并创建,即new对象后

就绪:进程已准备好,可以被运行,即调用start()方法后

运行:线程被调度,分配好资源,即进入run()方法里

阻塞:被挂起或执行输入输出操作,即run()方法临时终止了执行

死亡:自然死亡:完成run()后或者抛出异常

强制死亡:执行stop()方法,线程被kill,断电,CPU爆炸等即线程结束

线程的调度

Java属于抢占式调度模型

线程优先级(1–10)

MAX_PRIORITY –10(最大)

MIN_PRIORITY –1(最小)

NORM_PRIORITY –5(默认)

getPriority():返回线程优先级

setPriority(int newPriority):改变线程优先级

注意:线程创建时继承父线程的优先级,优先级不是绝对,只是较大概率先运行优先级高的

1
Runnable run=new bThread();
2
Thread tr1=new Thread(run,"线程11111:");//设置线程名
3
Thread tr2=new Thread(run,"线程22222:");
4
tr1.setPriority(2);						//优先级大
5
tr2.setPriority(1);						//优先级小
6
tr1.start();
7
tr2.start();

测了很多次终于出来的结果:

常用方法

启动线程start(),(stop():强制停止)

线程逻辑run(),

设置线程名setName(String name),

获取线程名getName(),

返回当前线程的引用currentThread(),

判断线程是否活着isActive()

yield():static void

线程让步,让优先级相同的先执行,但让步多长时间不固定,若没同优先级,此方法无效。

改造for循环:

1
for(int i=0;i<5;i++){
2
    if(i==1){			//当i=1时让步给其他线程
3
        Thread.yield();
4
    }
5
    count++;
6
    System.out.println(Thread.currentThread().getName()+count+"只狗");
7
}

线程2执行完一次i++后,i=1让步给线程1,线程1也执行一次i++后再让给线程2,然后继续

可是有时候测出来不是这样,不知道为什么

join()

当某个线程调用其他线程的join方法时,该线程被阻塞,直到用join()方法加入的线程执行完为止,低优先级也可以获得执行。

在主线程for循环中i=0时插入线程1:

1
System.out.println("这是主线程。。。");
2
for(int i=0;i<3;i++){
3
	System.out.println(i+1+"只饺子");
4
	if(i==0){
5
    	try {
6
			tr1.join();			//先执行完tr1再继续循环
7
		} catch (InterruptedException e) {
8
		e.printStackTrace();
9
		}
10
	}
11
}

可以看到线程1的执行过程在主线程的循环中间,而线程2不受控制,说明主线程在调用了线程1的join方法后转去执行线程1,直到结束再回来执行主线程

sleep(long mills):static void

令当前线程在指定时间段内睡眠,使其它线程有机会被执行,时间过去后恢复原样

1
LocalDateTime start=LocalDateTime.now();
2
System.out.println("即将进入睡眠。。。");
3
try {
4
    Thread.sleep(3000);
5
} catch (InterruptedException e) {
6
    e.printStackTrace();
7
}
8
System.out.println("睡眠完毕。。。");
9
LocalDateTime stop=LocalDateTime.now();
10
Duration duration = Duration.between(start,stop);
11
System.out.println("花费时间:"+duration.toMillis()+"毫秒");

在即将进入睡眠语句输出后,隔了3000毫秒,睡眠完毕输出,并输出时间差,验证成功。

如果在main方法中,如果new了一个Thread对象,然后调用了.sleep方法,其实睡眠的还是主线程,因为sleep是静态方法,对象.sleep相当于是类名.sleep,没有区别。

interrupt()

依靠异常处理机制,打断休眠,抛出异常,继续运行接下来的代码

线程同步与死锁

同步问题(数据不唯一)

两个线程同时向一个账户取钱,余额出现负数:

1
//线程逻辑
2
@Override
3
public void run() {
4
	acount.get(m);
5
}
6
//取钱操作
7
public void get(int m){
8
    String tname=Thread.currentThread().getName()+"操作---";
9
    System.out.println(tname+"账户原有:"+money+",提款:"+m);
10
    if(money>=m){		//余额充足
11
        System.out.println("要取钱拉");	//这四条纯粹为了加大出错的概率
12
        System.out.println("取钱拉");
13
        System.out.println("钱拉");
14
        System.out.println("拉");	
15
        money-=m;		//余额减少
16
        System.out.println(tname+"提款成功,账户余额:"+money);
17
    }else {
18
        System.out.println(tname+"账户余额不足。。。");
19
    }
20
    System.out.println(tname+"结束-----------------------");
21
}

原因:当两个线程同时对一个账户进行取钱操作时,线程1判断余额充足后,继续接下来的操作,在还没到对余额进行修改时,线程2可能在此时判断余额充足,这就导致了两个线程同时完成了余额减少操作,金额变为负数,而这是不允许出现的。

解决办法:在一个线程执行对数据的操作时,其他线程不能执行。

1,synchronized代码块(推荐使用)

将需要同步的代码放在synchronized(这里是加锁的对象){这里是代码}代码块中

这样这一段代码只有一个线程能执行,当t1执行到这段代码时,就会去找该对象的对象锁,如果找到,就进入代码块中执行,执行完后,归还对象锁。如果在t2在执行过程中也遇到了这段代码,也去找对象锁,发现该对象锁被t1持有,只能等待t1归还对象锁。

1
public void get(){
2
    synchronized(被锁对象){
3
        //取钱
4
    }
5
}

2,synchronized关键字

在取钱操作get()方法上加上synchronized关键字

1
public synchronized void get(){
2
    //取钱
3
}

synchronized关键字锁的不是方法,而是对象。

比如新加一个同样的get2()方法,然后改造run()方法

1
if("wechat".equals(Thread.currentThread().getName())){
2
    acount.get2(m);
3
}else {
4
    acount.get(m);
5
}

当线程为wechat时执行get2(),线程为alipay时执行get(),发现其中一个还是只能等待另一个线程结束才能执行。

当两个线程操作的对象不一样时,就锁不了。

测试结果

加上关键字后,发现微信执行完之后,支付宝才能执行,此时就达到了目的。

注意:在静态方法上加锁时,对于所有的对象都是同一个锁,这个锁就称为类锁,即该类所有对象都是同一把锁。当执行到带synchronized关键字的静态方法时,会去找类锁,哪怕操作不同对象时也要等待拥有类锁的线程执行完。

死锁问题(永远等待)

著名的哲学家吃饭问题,n个人围成圆桌,总共有n根筷子,与哲学家插空放置。哲学家想要吃饭,必须拿两根筷子,当每个哲学家拿起左边的筷子时,再去拿右边的筷子,此时筷子已经被右边的哲学家拿走,此时陷入死锁状态。

线程T1先拿到o1的对象锁,然后想接着拿o2的对象锁,而线程T2相反。由于两个线程同步执行,所以会出现,线程T1拿到了o1的锁,同时线程T2拿到了o2的锁,形成了互相等待对方执行完成的局面,而双方都不可能执行完成。

1
class T1 implements Runnable{
2
    Object o1;
3
    Object o2;
4
    public T1(Object o1,Object o2){
5
        this.o1=o1;
6
        this.o2=o2;
7
    }
8
    @Override
9
    public void run() {
10
        synchronized (o1){		//线程T1先拿到o1对象锁
11
            System.out.println(Thread.currentThread().getName()+"锁住了o1");
12
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }			//短暂延时,引起问题
13
            synchronized (o2){	//接着拿o2对象锁
14
            }
15
        }
16
    }
17
}

线程T2与线程T1类似,只是先拿o2再拿o1。

守护线程

所有的用户线程(以上都是)结束生命周期,守护线程才会结束,例如Java中的垃圾回收器。

守护线程一般都是无限执行的。

setDaemon(true)方法可以将用户线程设置为守护线程

定时器

Timer:每隔一段固定时间执行任务

schedule(TimeTask task,Date firstTime,long perlod):指定定时任务

task:任务,firstTime:起始时间,perlod,间隔时间

1
class Test extends TimeTask{
2
    public void run(){
3
        System.out.pritln("...")
4
    }
5
}

总结

头发-1