概述
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
最后
以上就是悲凉豆芽为你收集整理的黑马程序员——java多线程的全部内容,希望文章能够帮你解决黑马程序员——java多线程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复