黑马程序员——java多线程

android培训java培训、期待与您交流! ----------


java——多线程学习总结

    1、进程和线程的含义
        1)、在多任务系统中,每个独立的执行的程序称为进程。
        2)、每个进程里面含有一个或多个线程,一个线程就是一个程序中的执行过程,如果一个程序中有多个代码要同时交替运行,就会产生多个线程,并指定每个线程上面运行的代码,这就是多线程。

一、首先我们看看单线程的编程

public class ThreadTest{
	public static void main(String[] args){
		new Run().run();
		while(true){
			System.out.println("main--------->"+Thread.currentThread().getName());
		}	
	}
}

class Run{
	public void run(){
		while(true){
			System.out.println("Run--------->"+Thread.currentThread().getName());
		}
	}
}
我们看看打印结果

Run--------->main
Run--------->main
Run--------->main
Run--------->main
Run--------->main
Run--------->main
结果只打印出了run---->,这说明当我们的main方法在执行new Run().run()的时候进入了死循环,且由于我们是单线程编程,所以没有其他线程去执行下面的System.out.println("main")。


二、下面我们改改代码,看看多线程编程的结果。

public class ThreadTest{
	public static void main(String[] args){
		new Run().start();
		while(true){
			System.out.println("main--------->"+Thread.currentThread().getName());
		}	
	}
}

class Run extends Thread {
	public void run(){
		while(true){
			System.out.println("Run--------->"+Thread.currentThread().getName());
		}
	}
}
打印结果:


Run--------->Thread-0
main--------->main
main--------->main
main--------->main
main--------->main
main--------->main
Run--------->Thread-0
Run--------->Thread-0
Run--------->Thread-0
Run--------->Thread-0
我们看到下面的代码的Run类是继承了线程类Thread,然后调用Thread里面的start方法,从而调用自己的run方法。当主线程main执行到new Run().start()的时候新开了一个线程去执行RUN线程,主线程继续往下面执行。


三、前台线程和后台线程(也称为用户线程和守护线程),在java中只有前台线程在,java程序就不会结束,那么像上面的代码,把Run r = new Run(); r.setDaemon(true)的时候,就是把这个线程设置为后台线程(守护线程)。当main线程运行完的时候,这个run线程也随之结束。典型的java垃圾回收线程就是一个守护线程,当程序中没有其他线程的时候,这个垃圾回收线程也就自动结束了。

四、join方法,join方法是合并线程,比如上面的代码run.join(),这个时候就只有一个线程了,只有当run运行结束后,才可以执行main线程。我们查看APIjoin方法有3个重载

void join()
等待该线程终止。
void join(long millis)
等待该线程终止的时间最长为millis毫秒。
void join(long millis, int nanos)
等待该线程终止的时间最长为millis毫秒 +nanos纳秒。

五、常用的生成线程的方法,1、继承Thread类,实现Runnable接口,然后调用new Thread(Runnable).start();两者区别

     1)、使用继承Thread的线程类,有一定的局限性,不能再继承其他类。

     2)、在需要多个线程访问同一个资源的时候,实现Runnable接口要比Thread方便。下面我们用买火车票去模拟看看

     在买火车票的时候,我们启动4个线程,首先就必须要new4个Thread对象,

public class ThreadTest{
 public static void main(String[] args){
     new Run().start(); //这里new了4个Thread对象,所以产生了4个线程,但运行结果发现4个线程都有自己的资源,都有自己的那100张票,而不是共有的100张票
     new Run().start();
     new Run().start();
     new Run().start();
 }
}


class Run extends Thread{
    int i = 100;
    public void run(){
     while(true){
         if(i>0){
             System.out.println(Thread.currentThread().getName()+"--------------->"+i);
             i--;
         }
     }
}
我们把上面的代码更改成实现Runnable接口看看效果。


public class ThreadTest{
     public static void main(String[] args){
         Run r = new Run();//这里利用实现Runnalbe接口来实现,下面虽然也new了4个Thread,但他们都公用了一个RUN对象,都是公用的一个资源
         new Thread(r).start();
         new Thread(r).start();
         new Thread(r).start();
         new Thread(r).start();
     }
}


class Run implements Runnable{
    int i = 100;
    public void run(){
     while(true){
         if(i>0){
             System.out.println(Thread.currentThread().getName()+"--------------->"+i);
             i--;
         }
     }
}
这里需要给大家特别提示点,对于变量定义的位置,尽量不要定义在run方法里面,因为这个run方法是线程执行的具体代码,如果把变量定义在了run方法里面,那么就达不到共享资源了。


六、多线程同步问题

像我们上面的代码其实会出现不同问题,运行结果可以看见我们卖出的票有可能卖到负数了。这就是我们的4个线程在访问同一个资源的时候,并没有实现同步问题。

实现同步问题有两种方式1、synchronized 代码块  2、synchronized 方法。

我们先看看synchronized 代码块的写法

public class ThreadTest{
	public static void main(String[] args){
		Run r = new Run();
		new Thread(r).start();
		new Thread(r).start();
		new Thread(r).start();
		new Thread(r).start();
	}
}

class Run implements Runnable{
	int i = 1000;
	String str="";
	public void run(){
		while(true){
			synchronized(str){
				if(i>0){
					try{Thread.sleep(10);}catch(Exception e){}//如果这里的sleep的时间太长或者上面的I定义太小的话可能会出现运行的时候你只能看见一个线程在卖票,原因是当可能在睡眠的时候CPU让其他3个线程执行的时候我们的锁没有释放,其他3个线程运行不了,刚刚当我们的代码快运行完后,CPU又刚好执行到我们第一个线程。
					System.out.println(Thread.currentThread().getName()+"--------------->"+i--);
				}
			}
		}
	}
}

下面是采用Synchronized方法

public class ThreadTest{
	public static void main(String[] args){
		Run r = new Run();
		new Thread(r).start();
		new Thread(r).start();
		new Thread(r).start();
		new Thread(r).start();
	}
}

class Run implements Runnable{
	int i = 1000;
	String str="";
	public void run(){
		while(true){
				sale();
		}
	}
	public synchronized void sale(){
	    if(i>0){
					try{Thread.sleep(10);}catch(Exception e){}//如果这里的sleep的时间太长或者上面的I定义太小的话可能会出现运行的时候你只能看见一个线程在卖票,原因是当可能在睡眠的时候CPU让其他3个线程执行的时候我们的锁没有释放,其他3个线程运行不了,刚刚当我们的代码快运行完后,CPU又刚好执行到我们第一个线程。
					System.out.println(Thread.currentThread().getName()+"--------------->"+i--);
				}
	}
	
}
synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。

synchronized关键字是不能继承的,继承时子类的覆盖方法必须显示定义成synchronized。   

七、线程死锁问题

下面就是典型的死锁问题,都在等待对方的锁资源,从而造成都没有释放自己的锁,而一直在等待别人的锁。

public class ThreadTest{
	public static void main(String[] args){
		Run r = new Run();
		new Thread(r).start();
		new Thread(r).start();
	}
}

class Run implements Runnable{
	String str = "";
	public void run(){
		while(true){
			synchronized(str){//这里获取到str这个对象锁后
			try{Thread.sleep(10);}catch(Exception e){}
				synchronized(this){//第一次运行到这里可以获取this对象锁,但第二次就被下面的run2得到了this对象锁且没释放
					System.out.println("RUN1------->"+Thread.currentThread().getName());
				}
			}
			run2();//这里执行run2方法,run2方法中获取了this对象。但当他要往下面执行的时候需要str这个锁,但上面的的run已经获取到了str锁。并且在等待this对象锁。
		}
		
	}	
	
	public synchronized void run2(){
	try{Thread.sleep(100);}catch(Exception e){}
		synchronized(str){
			System.out.println("run2----------------"+Thread.currentThread().getName());
		}
	}
}

八、售卖火车票多线程访问不同锁定问题

大家可以从下面的代码开出来,虽然我们看是采取了线程同步机制,而且是运用了synchronized代码块,和synchronized方法,却没有达到一个真正的线程同步问题。

主要原因在于,我们的两个同步代码,采用的锁并不是同一个锁。所以在多线程编程中当多个线程访问同一个资源的时候,我们要主要他们公用的所对象也要一致。

/*
需求:简单的卖票程序
多个窗口同时卖票

创建线程的第二种方式:
通过实现Runnable接口的子类对象作为实际参数传递给Thread的构造函数。
然后通过Thread类的start()方法去启用Runnable接口中run方法。


继承Thread和实现Runnable接口的区别:
1、继续Thread的方式有一定的局限性,而通过实现Runnable接口可以实现多继承多扩展。
2、实现Runnable接口可以让资源共享。


注意:new Thread()类就是创建一个线程,然后调用new Thread().start()方法
才是告诉cpu我这个线程已经准备就绪,你可以来调用我了。

我们为了解决线程同步的问题,可以采用:同步代码块。


我们今天把同步代码块,改为同步函数试试。
同步函数使用的所对象是this

通过该程序进行验证,
使用两个线程来卖票,一个线程在同步代码块中,一个线程在同步函数中。
*/

class Ticket implements Runnable{
    private int tick = 1000;
	Object obj = new Object();//这里生产一个obj对象,来作为同步代码块的对象。
	public boolean flag = true;
	public void run(){
		if(flag){
			synchronized (obj){//这里用的obj锁
				while(true){
					if(tick>0){
					try{Thread.sleep(100);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"sale : "+tick--);
					}
				}
			}
		}else{
			while(true){
				show();
			}
		}
	}
	//我们在此封装一个方法,用于卖票
	public synchronized void show(){//这里是this锁就造成 上下两个方法的锁不一样。就造成不同步
		if(tick>0){
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"show : "+tick--);
		}
	}
}

class ThreadThis{
	public static void main(String[] args)throws Exception{
		Ticket t = new Ticket();
		Thread thread1 = new Thread(t);
		Thread thread2 = new Thread(t);
		thread1.start();
		Thread.sleep(10);//让主线程睡10毫秒,好让线程分开执行到
		t.flag = false;
		thread2.start();
	}
}


经过上面的这一些代码,我们来总结下线程编程应该注意到问题。

1、通过是实现Runnbale接口和继承Thread的区别

    继承:线程代码存放在Thread子类run方法中,就是子类重写父类的run方法。

    实现:线程代码存放在接口顶接口Runnable的实现类run方法中,实现接口中的run方法。

    相对比较,采取实现Runnable接口避免了单继承的局限性,在多线程编程的时候建议使用实现方式

2、直接调用run方法和调用start()方法,然后通过start方法调用run方法有什么区别?

    1、如果直接运行run的话,其实就跟我们调用普通方法一样,根本就和线程没什么关系,他就是一个单线程顺序执行而已。

    2、而调用start方法的时候,他是通过Thread线程类来调用的,从而在start里面调用run方法,从而作为一个Thread方法执行下去,实现多线程。

3、

转载于:https://my.oschina.net/u/158350/blog/99319

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值